/**CFile****************************************************************

  FileName    [cmdUtils.c]

  SystemName  [ABC: Logic synthesis and verification system.]

  PackageName [Command processing package.]

  Synopsis    [Various utilities of the command package.]

  Author      [Alan Mishchenko]
  
  Affiliation [UC Berkeley]

  Date        [Ver. 1.0. Started - June 20, 2005.]

  Revision    [$Id: cmdUtils.c,v 1.00 2005/06/20 00:00:00 alanmi Exp $]

***********************************************************************/

#include "base/abc/abc.h"
#include "base/main/mainInt.h"
#include "cmdInt.h"
#include <ctype.h>

ABC_NAMESPACE_IMPL_START
    // proper declaration of isspace

////////////////////////////////////////////////////////////////////////
///                        DECLARATIONS                              ///
////////////////////////////////////////////////////////////////////////

static int CmdCommandPrintCompare( Abc_Command ** ppC1, Abc_Command ** ppC2 );

////////////////////////////////////////////////////////////////////////
///                     FUNCTION DEFINITIONS                         ///
////////////////////////////////////////////////////////////////////////

/**Function*************************************************************

  Synopsis    []

  Description []
               
  SideEffects []

  SeeAlso     []

***********************************************************************/
int cmdCheckShellEscape( Abc_Frame_t * pAbc, int argc, char ** argv)
{
    int RetValue;
    if (argv[0][0] == '!') 
    {
        const int size = 4096;
        int i;
        char * buffer = ABC_ALLOC(char, 10000);
        strncpy (buffer, &argv[0][1], size);
        for (i = 1; i < argc; ++i)
        {
                strncat (buffer, " ", size);
                strncat (buffer, argv[i], size);
        }
        if (buffer[0] == 0) 
            strncpy (buffer, "/bin/sh", size);
        RetValue = system (buffer);
        ABC_FREE( buffer );

        // NOTE: Since we reconstruct the cmdline by concatenating
        // the parts, we lose information. So a command like
        // `!ls "file name"` will be sent to the system as
        // `ls file name` which is a BUG

        return 1;
    }
    else
    {
        return 0;
    }
}

/**Function*************************************************************

  Synopsis    [Executes one command.]

  Description []
               
  SideEffects []

  SeeAlso     []

***********************************************************************/
int CmdCommandDispatch( Abc_Frame_t * pAbc, int * pargc, char *** pargv )
{
    int argc = *pargc;
    char ** argv = *pargv;
    char ** argv2;

    Abc_Ntk_t * pNetCopy;
    int (*pFunc) ( Abc_Frame_t *, int, char ** );
    Abc_Command * pCommand;
    char * value;
    int fError;
    double clk;

    if ( argc == 0 )
        return 0;

    if ( cmdCheckShellEscape( pAbc, argc, argv ) == 1 )
        return 0;

    // get the command
    if ( ! st__lookup( pAbc->tCommands, argv[0], (char **)&pCommand ) )
    {   // the command is not in the table
        // if there is only one word with an extension, assume this is file to be read
        if ( argc == 1 && strstr( argv[0], "." ) )
        {
            // add command 'read' assuming that this is the file name
            argv2 = CmdAddToArgv( argc, argv );
            CmdFreeArgv( argc, argv );
            argc = argc+1;
            argv = argv2;
            *pargc = argc;
            *pargv = argv;
            if ( ! st__lookup( pAbc->tCommands, argv[0], (char **)&pCommand ) )
                assert( 0 );
        }
        else
        {
            fprintf( pAbc->Err, "** cmd error: unknown command '%s'\n", argv[0] );
            fprintf( pAbc->Err, "(this is likely caused by using an alias defined in \"abc.rc\"\n" );
            fprintf( pAbc->Err, "without having this file in the current or parent directory)\n" );
            return 1;
        }
    }

    // get the backup network if the command is going to change the network
    if ( pCommand->fChange ) 
    {
        if ( pAbc->pNtkCur && Abc_FrameIsFlagEnabled( "backup" ) )
        {
            pNetCopy = Abc_NtkDup( pAbc->pNtkCur );
            Abc_FrameSetCurrentNetwork( pAbc, pNetCopy );
            // swap the current network and the backup network 
            // to prevent the effect of resetting the short names
            Abc_FrameSwapCurrentAndBackup( pAbc );
        }
    }

    // execute the command
    clk = Extra_CpuTimeDouble();
    pFunc = (int (*)(Abc_Frame_t *, int, char **))pCommand->pFunc;
    fError = (*pFunc)( pAbc, argc, argv );
    pAbc->TimeCommand += Extra_CpuTimeDouble() - clk;

    // automatic execution of arbitrary command after each command 
    // usually this is a passive command ... 
    if ( fError == 0 && !pAbc->fAutoexac )
    {
        if ( st__lookup( pAbc->tFlags, "autoexec", &value ) )
        {
            pAbc->fAutoexac = 1;
            fError = Cmd_CommandExecute( pAbc, value );
            pAbc->fAutoexac = 0;
        }
    }
    return fError;
}

/**Function*************************************************************

  Synopsis    [Splits the command line string into individual commands.]

  Description []
               
  SideEffects []

  SeeAlso     []

***********************************************************************/
const char * CmdSplitLine( Abc_Frame_t * pAbc, const char *sCommand, int *argc, char ***argv )
{
    const char *p, *start;
    char c;
    int i, j;
    char *new_arg;
    Vec_Ptr_t * vArgs;
    int single_quote, double_quote;

    vArgs = Vec_PtrAlloc( 10 );

    p = sCommand;
    for ( ;; )
    {
        // skip leading white space 
        while ( isspace( ( int ) *p ) )
        {
            p++;
        }

        // skip until end of this token 
        single_quote = double_quote = 0;
        for ( start = p; ( c = *p ) != '\0'; p++ )
        {
            if ( c == ';' || c == '#' || isspace( ( int ) c ) )
            {
                if ( !single_quote && !double_quote )
                {
                    break;
                }
            }
            if ( c == '\'' )
            {
                single_quote = !single_quote;
            }
            if ( c == '"' )
            {
                double_quote = !double_quote;
            }
        }
        if ( single_quote || double_quote )
        {
            ( void ) fprintf( pAbc->Err, "** cmd warning: ignoring unbalanced quote ...\n" );
        }
        if ( start == p )
            break;

        new_arg = ABC_ALLOC( char, p - start + 1 );
        j = 0;
        for ( i = 0; i < p - start; i++ )
        {
            c = start[i];
            if ( ( c != '\'' ) && ( c != '\"' ) )
            {
                new_arg[j++] = isspace( ( int ) c ) ? ' ' : start[i];
            }
        }
        new_arg[j] = '\0';
        Vec_PtrPush( vArgs, new_arg );
    }

    *argc = vArgs->nSize;
    *argv = (char **)Vec_PtrReleaseArray( vArgs );
    Vec_PtrFree( vArgs );
    if ( *p == ';' )
    {
        p++;
    }
    else if ( *p == '#' )
    {
        for ( ; *p != 0; p++ ); // skip to end of line 
    }
    return p;
}

/**Function*************************************************************

  Synopsis    [Replaces parts of the command line string by aliases if given.]

  Description []
               
  SideEffects []

  SeeAlso     []

***********************************************************************/
int CmdApplyAlias( Abc_Frame_t * pAbc, int *argcp, char ***argvp, int *loop )
{
    int i, argc, stopit, added, offset, did_subst, subst, fError, newc, j;
    const char *arg;
    char **argv, **newv;
    Abc_Alias *alias;

    argc = *argcp;
    argv = *argvp;
    stopit = 0;
    for ( ; *loop < 200; ( *loop )++ )
    {
        if ( argc == 0 )
            return 0;
        if ( stopit != 0 || st__lookup( pAbc->tAliases, argv[0],  (char **) &alias ) == 0 )
        {
            return 0;
        }
        if ( strcmp( argv[0], alias->argv[0] ) == 0 )
        {
            stopit = 1;
        }
        ABC_FREE( argv[0] );
        added = alias->argc - 1;

        /* shift all the arguments to the right */
        if ( added != 0 )
        {
            argv = ABC_REALLOC( char *, argv, argc + added );
            for ( i = argc - 1; i >= 1; i-- )
            {
                argv[i + added] = argv[i];
            }
            for ( i = 1; i <= added; i++ )
            {
                argv[i] = NULL;
            }
            argc += added;
        }
        subst = 0;
        for ( i = 0, offset = 0; i < alias->argc; i++, offset++ )
        {
            arg = CmdHistorySubstitution( pAbc, alias->argv[i], &did_subst );
            if ( arg == NULL )
            {
                *argcp = argc;
                *argvp = argv;
                return ( 1 );
            }
            if ( did_subst != 0 )
            {
                subst = 1;
            }
            fError = 0;
            do
            {
                arg = CmdSplitLine( pAbc, arg, &newc, &newv );
                /*
                 * If there's a complete `;' terminated command in `arg',
                 * when split_line() returns arg[0] != '\0'.
                 */
                if ( arg[0] == '\0' )
                { /* just a bunch of words */
                    break;
                }
                fError = CmdApplyAlias( pAbc, &newc, &newv, loop );
                if ( fError == 0 )
                {
                       fError = CmdCommandDispatch( pAbc, &newc, &newv );
                }
                CmdFreeArgv( newc, newv );
            }
            while ( fError == 0 );
            if ( fError != 0 )
            {
                *argcp = argc;
                *argvp = argv;
                return ( 1 );
            }
            added = newc - 1;
            if ( added != 0 )
            {
                argv = ABC_REALLOC( char *, argv, argc + added );
                for ( j = argc - 1; j > offset; j-- )
                {
                    argv[j + added] = argv[j];
                }
                argc += added;
            }
            for ( j = 0; j <= added; j++ )
            {
                argv[j + offset] = newv[j];
            }
            ABC_FREE( newv );
            offset += added;
        }
        if ( subst == 1 )
        {
            for ( i = offset; i < argc; i++ )
            {
                ABC_FREE( argv[i] );
            }
            argc = offset;
        }
        *argcp = argc;
        *argvp = argv;
    }

    fprintf( pAbc->Err, "** cmd warning: alias loop\n" );
    return 1;
}

/**Function*************************************************************

  Synopsis    [Performs history substitution (now, disabled).]

  Description []
               
  SideEffects []

  SeeAlso     []

***********************************************************************/
char * CmdHistorySubstitution( Abc_Frame_t * pAbc, char *line, int *changed )
{
    // as of today, no history substitution 
    *changed = 0;
    return line;
}

/**Function*************************************************************

  Synopsis    [Opens the file with path (now, disabled).]

  Description []
               
  SideEffects []

  SeeAlso     []

***********************************************************************/
FILE * CmdFileOpen( Abc_Frame_t * pAbc, char *sFileName, char *sMode, char **pFileNameReal, int silent )
{
    char * sRealName, * sPathUsr, * sPathLib, * sPathAll;
    FILE * pFile;
    
    if (strcmp(sFileName, "-") == 0) {
        if (strcmp(sMode, "w") == 0) {
            sRealName = Extra_UtilStrsav( "stdout" );
            pFile = stdout;
        }
        else {
            sRealName = Extra_UtilStrsav( "stdin" );
            pFile = stdin;
        }
    }
    else {
        sRealName = NULL;
        if (strcmp(sMode, "r") == 0) {
            
            /* combine both pathes if exist */
            sPathUsr = Cmd_FlagReadByName(pAbc,"open_path");
            sPathLib = Cmd_FlagReadByName(pAbc,"lib_path");
            
            if ( sPathUsr == NULL && sPathLib == NULL ) {
                sPathAll = NULL;
            }
            else if ( sPathUsr == NULL ) {
                sPathAll = Extra_UtilStrsav( sPathLib );
            }
            else if ( sPathLib == NULL ) {
                sPathAll = Extra_UtilStrsav( sPathUsr );
            }
            else {
                sPathAll = ABC_ALLOC( char, strlen(sPathLib)+strlen(sPathUsr)+5 );
                sprintf( sPathAll, "%s:%s",sPathUsr, sPathLib );
            }
            if ( sPathAll != NULL ) {
                sRealName = Extra_UtilFileSearch(sFileName, sPathAll, "r");
                ABC_FREE( sPathAll );
            }
        }
        if (sRealName == NULL) {
            sRealName = Extra_UtilTildeExpand(sFileName);
        }

        if ((pFile = fopen(sRealName, sMode)) == NULL) {
            if (! silent) {
//                perror(sRealName);
                Abc_Print( 1, "Cannot open file \"%s\".\n", sRealName );
            }
        }
        else
        {
            // print the path/name of the resource file 'abc.rc' that is being loaded
            if ( !silent && strlen(sRealName) >= 6 && strcmp( sRealName + strlen(sRealName) - 6, "abc.rc" ) == 0 )            
                Abc_Print( 1, "Loading resource file \"%s\".\n", sRealName );
        }
    }
    if ( pFileNameReal )
        *pFileNameReal = sRealName;
    else
        ABC_FREE(sRealName);
    
    return pFile;
}

/**Function*************************************************************

  Synopsis    [Frees the previously allocated argv array.]

  Description []
               
  SideEffects []

  SeeAlso     []

***********************************************************************/
void CmdFreeArgv( int argc, char **argv )
{
    int i;
    for ( i = 0; i < argc; i++ )
        ABC_FREE( argv[i] );
    ABC_FREE( argv );
}
char ** CmdDupArgv( int argc, char **argv )
{
    char ** argvNew = ABC_ALLOC( char *, argc );
    int i;
    for ( i = 0; i < argc; i++ )
        argvNew[i] = Abc_UtilStrsav( argv[i] );
    return argvNew;
}

/**Function*************************************************************

  Synopsis    [Frees the previously allocated argv array.]

  Description []
               
  SideEffects []

  SeeAlso     []

***********************************************************************/
char ** CmdAddToArgv( int argc, char ** argv )
{
    char ** argv2;
    int i;
    argv2 = ABC_ALLOC( char *, argc + 1 ); 
    argv2[0] = Extra_UtilStrsav( "read" ); 
//    argv2[0] = Extra_UtilStrsav( "&r" ); 
    for ( i = 0; i < argc; i++ )
        argv2[i+1] = Extra_UtilStrsav( argv[i] ); 
    return argv2;
}

/**Function*************************************************************

  Synopsis    [Frees the previously allocated command.]

  Description []
               
  SideEffects []

  SeeAlso     []

***********************************************************************/
void CmdCommandFree( Abc_Command * pCommand )
{
    ABC_FREE( pCommand->sGroup );
    ABC_FREE( pCommand->sName );
    ABC_FREE( pCommand );
}


/**Function*************************************************************

  Synopsis    [Prints commands alphabetically by group.]

  Description []
               
  SideEffects []

  SeeAlso     []

***********************************************************************/
void CmdCommandPrint( Abc_Frame_t * pAbc, int fPrintAll, int fDetails )
{
    const char *key;
    char *value;
    st__generator * gen;
    Abc_Command ** ppCommands;
    Abc_Command * pCommands;
    int nCommands, iGroupStart, i, j;
    char * sGroupCur;
    int LenghtMax, nColumns, iCom = 0;
    FILE *backupErr = pAbc->Err;

    // put all commands into one array
    nCommands = st__count( pAbc->tCommands );
    ppCommands = ABC_ALLOC( Abc_Command *, nCommands );
    i = 0;
    st__foreach_item( pAbc->tCommands, gen, &key, &value )
    {
        pCommands = (Abc_Command *)value;
        if ( fPrintAll || pCommands->sName[0] != '_' )
            ppCommands[i++] = pCommands;
    }
    nCommands = i;

    // sort command by group and then by name, alphabetically
    qsort( (void *)ppCommands, nCommands, sizeof(Abc_Command *), 
            (int (*)(const void *, const void *)) CmdCommandPrintCompare );
    assert( CmdCommandPrintCompare( ppCommands, ppCommands + nCommands - 1 ) <= 0 );

    // get the longest command name
    LenghtMax = 0;
    for ( i = 0; i < nCommands; i++ )
        if ( LenghtMax < (int)strlen(ppCommands[i]->sName) )
             LenghtMax = (int)strlen(ppCommands[i]->sName); 
    // get the number of columns
    nColumns = 79 / (LenghtMax + 2);

    // print the starting message 
    fprintf( pAbc->Out, "      Welcome to ABC compiled on %s %s!", __DATE__, __TIME__ );

    // print the command by group
    sGroupCur = NULL;
    iGroupStart = 0;
    pAbc->Err = pAbc->Out;
    for ( i = 0; i < nCommands; i++ )
        if ( sGroupCur && strcmp( sGroupCur, ppCommands[i]->sGroup ) == 0 )
        { // this command belongs to the same group as the previous one
            if ( iCom++ % nColumns == 0 )
                fprintf( pAbc->Out, "\n" ); 
            // print this command
            fprintf( pAbc->Out, " %-*s", LenghtMax, ppCommands[i]->sName );
        }
        else
        { // this command starts the new group of commands
            // start the new group
            if ( fDetails && i != iGroupStart )
            { // print help messages for all commands in the previous groups
                fprintf( pAbc->Out, "\n" );
                for ( j = iGroupStart; j < i; j++ )
                {
                    char *tmp_cmd;
                    fprintf( pAbc->Out, "\n" );
                    // fprintf( pAbc->Out, "--- %s ---\n", ppCommands[j]->sName );
                    tmp_cmd = ABC_ALLOC(char, strlen(ppCommands[j]->sName)+4);
                    (void) sprintf(tmp_cmd, "%s -h", ppCommands[j]->sName);
                    (void) Cmd_CommandExecute( pAbc, tmp_cmd );
                    ABC_FREE(tmp_cmd);
                }
                fprintf( pAbc->Out, "\n" );
                fprintf( pAbc->Out, "   ----------------------------------------------------------------------" );
                iGroupStart = i;
            }
            fprintf( pAbc->Out, "\n" );
            fprintf( pAbc->Out, "\n" );
            fprintf( pAbc->Out, "%s commands:\n", ppCommands[i]->sGroup );
            // print this command
            fprintf( pAbc->Out, " %-*s", LenghtMax, ppCommands[i]->sName );
            // remember current command group
            sGroupCur = ppCommands[i]->sGroup;
            // reset the command counter
            iCom = 1;
        }
    if ( fDetails && i != iGroupStart )
    { // print help messages for all commands in the previous groups
        fprintf( pAbc->Out, "\n" );
        for ( j = iGroupStart; j < i; j++ )
        {
            char *tmp_cmd;
            fprintf( pAbc->Out, "\n" );
            // fprintf( pAbc->Out, "--- %s ---\n", ppCommands[j]->sName );
            tmp_cmd = ABC_ALLOC(char, strlen(ppCommands[j]->sName)+4);
            (void) sprintf(tmp_cmd, "%s -h", ppCommands[j]->sName);
            (void) Cmd_CommandExecute( pAbc, tmp_cmd );
            ABC_FREE(tmp_cmd);
        }
    }
    pAbc->Err = backupErr;
    fprintf( pAbc->Out, "\n" );
    ABC_FREE( ppCommands );
}
 
/**Function*************************************************************

  Synopsis    [Comparision function used for sorting commands.]

  Description []
                
  SideEffects []

  SeeAlso     []

***********************************************************************/
int CmdCommandPrintCompare( Abc_Command ** ppC1, Abc_Command ** ppC2 )
{
    Abc_Command * pC1 = *ppC1;
    Abc_Command * pC2 = *ppC2;
    int RetValue;

    RetValue = strcmp( pC1->sGroup, pC2->sGroup );
    if ( RetValue < 0 )
        return -1;
    if ( RetValue > 0 )
        return 1;
    // the command belong to the same group

    // put commands with "_" at the end of the list
    if ( pC1->sName[0] != '_' && pC2->sName[0] == '_' )
        return -1;
    if ( pC1->sName[0] == '_' && pC2->sName[0] != '_' )
        return 1;

    RetValue = strcmp( pC1->sName, pC2->sName );
    if ( RetValue < 0 )
        return -1;
    if ( RetValue > 0 )
        return 1;
     // should not be two indentical commands
    assert( 0 );
    return 0;
}
 
/**Function*************************************************************

  Synopsis    [Comparision function used for sorting commands.]

  Description []
                
  SideEffects []

  SeeAlso     []

***********************************************************************/
int CmdNamePrintCompare( char ** ppC1, char ** ppC2 )
{
    return strcmp( *ppC1, *ppC2 );
}

/**Function*************************************************************

  Synopsis    [Comparision function used for sorting commands.]

  Description []
                
  SideEffects []

  SeeAlso     []

***********************************************************************/
void CmdPrintTable( st__table * tTable, int fAliases )
{
    st__generator * gen;
    const char ** ppNames;
    const char * key;
    char* value;
    int nNames, i;

    // collect keys in the array
    ppNames = ABC_ALLOC( const char *, st__count(tTable) );
    nNames = 0;
    st__foreach_item( tTable, gen, &key, &value )
        ppNames[nNames++] = key;

    // sort array by name
    qsort( (void *)ppNames, nNames, sizeof(char *), 
        (int (*)(const void *, const void *))CmdNamePrintCompare );

    // print in this order
    for ( i = 0; i < nNames; i++ )
    {
        st__lookup( tTable, ppNames[i], &value );
        if ( fAliases )
            CmdCommandAliasPrint( Abc_FrameGetGlobalFrame(), (Abc_Alias *)value );
        else
            fprintf( stdout, "%-15s %-15s\n", ppNames[i], value );
    }
    ABC_FREE( ppNames );
}

////////////////////////////////////////////////////////////////////////
///                       END OF FILE                                ///
////////////////////////////////////////////////////////////////////////
ABC_NAMESPACE_IMPL_END

