/*==LICENSE==*

CyanWorlds.com Engine - MMOG client, server and tools
Copyright (C) 2011  Cyan Worlds, Inc.

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.

You can contact Cyan Worlds, Inc. by email legal@cyan.com
 or by snail mail at:
      Cyan Worlds, Inc.
      14617 N Newport Hwy
      Mead, WA   99021

*==LICENSE==*/
//////////////////////////////////////////////////////////////////////////////
//                                                                          //
//  pfConsoleCmd Functions                                                  //
//                                                                          //
//////////////////////////////////////////////////////////////////////////////

#include "pfConsoleCmd.h"
#include "hsUtils.h"


//////////////////////////////////////////////////////////////////////////////
//// pfConsoleCmdGroup Stuff /////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////

pfConsoleCmdGroup   *pfConsoleCmdGroup::fBaseCmdGroup = nil;
UInt32              pfConsoleCmdGroup::fBaseCmdGroupRef = 0;


//// Constructor & Destructor ////////////////////////////////////////////////

pfConsoleCmdGroup::pfConsoleCmdGroup( char *name, char *parent )
{
    fNext = nil;
    fPrevPtr = nil;
    fCommands = nil;
    fSubGroups = nil;
    
    if( name == nil )
    {
        /// Create base
        hsStrncpy( fName, "base", sizeof( fName ) );
        fParentGroup = nil;
    }
    else
    {
        pfConsoleCmdGroup   *group = GetBaseGroup();

        if( parent != nil && parent[ 0 ] != 0 )
        {
            group = group->FindSubGroupRecurse( parent );
            hsAssert( group != nil, "Trying to register group under nonexistant group!" );
        }

        hsStrncpy( fName, name, sizeof( fName ) );
        group->AddSubGroup( this );
        fParentGroup = group;
    }

}

pfConsoleCmdGroup::~pfConsoleCmdGroup()
{
    if( this != fBaseCmdGroup )
    {
        Unlink();

        DecBaseCmdGroupRef();
    }
}

//// GetBaseGroup ////////////////////////////////////////////////////////////

pfConsoleCmdGroup   *pfConsoleCmdGroup::GetBaseGroup( void )
{
    if( fBaseCmdGroup == nil )
    {
        /// Initialize base group
        fBaseCmdGroup = TRACKED_NEW pfConsoleCmdGroup( nil, nil );
    }

    return fBaseCmdGroup;
}

//// DecBaseCmdGroupRef //////////////////////////////////////////////////////

void    pfConsoleCmdGroup::DecBaseCmdGroupRef( void )
{
    fBaseCmdGroupRef--;
    if( fBaseCmdGroupRef == 0 )
    {
        delete fBaseCmdGroup;
        fBaseCmdGroup = nil;
    }
}

//// Add Functions ///////////////////////////////////////////////////////////

void    pfConsoleCmdGroup::AddCommand( pfConsoleCmd *cmd )
{
    cmd->Link( &fCommands );
    fBaseCmdGroupRef++;
}

void    pfConsoleCmdGroup::AddSubGroup( pfConsoleCmdGroup *group )
{
    group->Link( &fSubGroups );
    fBaseCmdGroupRef++;
}

//// FindCommand /////////////////////////////////////////////////////////////
//  No longer recursive.

pfConsoleCmd    *pfConsoleCmdGroup::FindCommand( char *name )
{
    pfConsoleCmd    *cmd;


    hsAssert( name != nil, "nil name passed to FindCommand()" );

    /// Only search locally
    for( cmd = fCommands; cmd != nil; cmd = cmd->GetNext() )
    {
        if( strcmp( cmd->GetName(), name ) == 0 )
            return cmd;
    }

    return nil;
}

//// FindNestedPartialCommand ////////////////////////////////////////////////
//  Okay. YAFF. This one searches through the group and its children looking
//  for a partial command based on the string given. The counter determines
//  how many matches it skips before returning a match. (That way you can
//  cycle through matches by sending 1 + the last counter every time).

pfConsoleCmd    *pfConsoleCmdGroup::FindNestedPartialCommand( char *name, UInt32 *counter )
{
    pfConsoleCmd        *cmd;
    pfConsoleCmdGroup   *group;


    hsAssert( name != nil, "nil name passed to FindNestedPartialCommand()" );
    hsAssert( counter != nil, "nil counter passed to FindNestedPartialCommand()" );

    // Try us
    for( cmd = fCommands; cmd != nil; cmd = cmd->GetNext() )
    {
        if( _strnicmp( cmd->GetName(), name, strlen( name ) ) == 0 )
        {
            if( *counter == 0 )
                return cmd;

            (*counter)--;
        }
    }

    // Try children
    for( group = fSubGroups; group != nil; group = group->GetNext() )
    {
        cmd = group->FindNestedPartialCommand( name, counter );
        if( cmd != nil )
            return cmd;
    }

    return nil;
}

//// FindSubGroup ////////////////////////////////////////////////////////////

pfConsoleCmdGroup   *pfConsoleCmdGroup::FindSubGroup( char *name )
{
    pfConsoleCmdGroup   *group;


    hsAssert( name != nil, "nil name passed to FindSubGroup()" );

    /// Only search locally
    for( group = fSubGroups; group != nil; group = group->GetNext() )
    {
        if( strcmp( group->GetName(), name ) == 0 )
            return group;
    }

    return nil;
}

//// FindSubGroupRecurse /////////////////////////////////////////////////////
//  Resurces through a string, finding the final subgroup that the string
//  represents. Parses with spaces, _ or . as the separators. Copies string.

pfConsoleCmdGroup   *pfConsoleCmdGroup::FindSubGroupRecurse( const char *name )
{
    char                *ptr, *string;
    pfConsoleCmdGroup   *group;
    static char         seps[] = " ._";


    string = TRACKED_NEW char[ strlen( name ) + 1 ];
    hsAssert( string != nil, "Cannot allocate string in FindSubGroupRecurse()" );
    strcpy( string, name );

    /// Scan for subgroups
    group = pfConsoleCmdGroup::GetBaseGroup();
    ptr = strtok( string, seps );
    while( ptr != nil )
    {
        // Take this token and check to see if it's a group
        group = group->FindSubGroup( ptr );
        hsAssert( group != nil, "Invalid group name to FindSubGroupRecurse()" );

        ptr = strtok( nil, seps );
    }

    delete [] string;
    return group;
}

//// FindCommandNoCase ///////////////////////////////////////////////////////
//  Case-insensitive version of FindCommand.

pfConsoleCmd    *pfConsoleCmdGroup::FindCommandNoCase( char *name, UInt8 flags, pfConsoleCmd *start )
{
    pfConsoleCmd    *cmd;


    hsAssert( name != nil, "nil name passed to FindCommandNoCase()" );

    /// Only search locally
    if( start == nil )
        start = fCommands;
    else
        start = start->GetNext();

    if( flags & kFindPartial )
    {
        for( cmd = start; cmd != nil; cmd = cmd->GetNext() )
        {
            if( _strnicmp( cmd->GetName(), name, strlen( name ) ) == 0 )
                return cmd;
        }
    }
    else
    {
        for( cmd = start; cmd != nil; cmd = cmd->GetNext() )
        {
            if( stricmp( cmd->GetName(), name ) == 0 )
                return cmd;
        }
    }

    return nil;
}

//// FindSubGroupNoCase //////////////////////////////////////////////////////

pfConsoleCmdGroup   *pfConsoleCmdGroup::FindSubGroupNoCase( char *name, UInt8 flags, pfConsoleCmdGroup *start )
{
    pfConsoleCmdGroup   *group;


    hsAssert( name != nil, "nil name passed to FindSubGroupNoCase()" );

    /// Only search locally
    if( start == nil )
        start = fSubGroups;
    else
        start = start->GetNext();

    if( flags & kFindPartial )
    {
        for( group = start; group != nil; group = group->GetNext() )
        {
            if( _strnicmp( group->GetName(), name, strlen( name ) ) == 0 )
                return group;
        }
    }
    else
    {
        for( group = start; group != nil; group = group->GetNext() )
        {
            if( stricmp( group->GetName(), name ) == 0 )
                return group;
        }
    }

    return nil;
}

//// Link & Unlink ///////////////////////////////////////////////////////////

void    pfConsoleCmdGroup::Link( pfConsoleCmdGroup **prevPtr )
{
    hsAssert( fNext == nil && fPrevPtr == nil, "Trying to link console group that's already linked!" );

    fNext = *prevPtr;
    if( *prevPtr )
        (*prevPtr)->fPrevPtr = &fNext;
    fPrevPtr = prevPtr;
    *fPrevPtr = this;
}

void    pfConsoleCmdGroup::Unlink( void )
{
    hsAssert( fNext != nil || fPrevPtr != nil, "Trying to unlink console group that isn't linked!" );

    if( fNext )
        fNext->fPrevPtr = fPrevPtr;
    *fPrevPtr = fNext;
}


int  pfConsoleCmdGroup::IterateCommands(pfConsoleCmdIterator* t, int depth)
{
    pfConsoleCmd *cmd;

    cmd = this->GetFirstCommand();
    while(cmd)
    {
        t->ProcessCmd(cmd,depth);
        cmd = cmd->GetNext();
    }

    pfConsoleCmdGroup *grp;

    grp = this->GetFirstSubGroup();
    while(grp)
    {
        if(t->ProcessGroup(grp, depth))
            grp->IterateCommands(t, depth+1);
        grp = grp->GetNext();
    }
    return 0;
}



//////////////////////////////////////////////////////////////////////////////
//// pfConsoleCmd Functions //////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////

char    pfConsoleCmd::fSigTypes[ kNumTypes ][ 8 ] = { "int", "float", "bool", "string", "char", "void", "..." };

pfConsoleCmd::pfConsoleCmd( char *group, char *name, char *paramList, char *help, 
                            pfConsoleCmdPtr func, hsBool localOnly )
{
    fNext = nil;
    fPrevPtr = nil;

    fFunction = func;
    fLocalOnly = localOnly;
    
    hsStrncpy( fName, name, sizeof( fName ) );
    fHelpString = help;

    ICreateSignature( paramList );
    Register( group, name );
}

pfConsoleCmd::~pfConsoleCmd()
{
    int     i;


    for( i = 0; i < fSigLabels.GetCount(); i++ )
    {
        if( fSigLabels[ i ] != nil )
            delete [] fSigLabels[ i ];
    }
    Unregister();
    
    fSignature.Reset();
    fSigLabels.Reset();
}

//// ICreateSignature ////////////////////////////////////////////////////////
//  Creates the signature and sig labels based on the given string.

void    pfConsoleCmd::ICreateSignature( char *paramList )
{
    static char seps[] = " :-";

    char    params[ 256 ];
    char    *ptr, *nextPtr, *tok, *tok2;
    int     i;


    /// Simple check
    if( paramList == nil )
    {
        fSignature.Push( kAny );
        fSigLabels.Push( (char *)nil );
        return;
    }

    /// So we can do stuff to it
    hsAssert( strlen( paramList ) < sizeof( params ), "Make the (#*$& params string larger!" );
    hsStrcpy( params, paramList );

    fSignature.Empty();
    fSigLabels.Empty();

    /// Loop through all the types given in the list
    ptr = params;
    do
    {
        /// Find break
        nextPtr = strchr( ptr, ',' );
        if( nextPtr != nil )
        {
            *nextPtr = 0;
            nextPtr++;
        }

        /// Do this param
        tok = strtok( ptr, seps );
        if( tok == nil && ptr == params )
            break;

        hsAssert( tok != nil, "Bad parameter list for console command!" );
        tok2 = strtok( nil, seps );

        if( tok2 != nil )
        {
            // Type and label: assume label second
            fSigLabels.Push( hsStrcpy( tok2 ) );            
        }
        else
            fSigLabels.Push( (char *)nil );

        // Find type
        for( i = 0; i < kNumTypes; i++ )
        {
            if( strcmp( fSigTypes[ i ], tok ) == 0 )
            {
                fSignature.Push( (UInt8)i );
                break;
            }
        }

        hsAssert( i < kNumTypes, "Bad parameter type in console command parameter list!" );

    } while( ( ptr = nextPtr ) != nil );
}

//// Register ////////////////////////////////////////////////////////////////
//  Finds the group this command should be in and registers it with that
//  group.

void    pfConsoleCmd::Register( char *group, char *name )
{
    pfConsoleCmdGroup   *g;


    if( group == nil || group[ 0 ] == 0 )
    {
        g = pfConsoleCmdGroup::GetBaseGroup();
        g->AddCommand( this );
    }
    else
    {
        g = pfConsoleCmdGroup::FindSubGroupRecurse( group );
        hsAssert( g != nil, "Trying to register command under nonexistant group!" );
        g->AddCommand( this );
    }

    fParentGroup = g;
}

//// Unregister //////////////////////////////////////////////////////////////

void    pfConsoleCmd::Unregister( void )
{
    Unlink();
    pfConsoleCmdGroup::DecBaseCmdGroupRef();
}

//// Execute /////////////////////////////////////////////////////////////////
//  Run da thing!

void    pfConsoleCmd::Execute( Int32 numParams, pfConsoleCmdParam *params, void (*PrintFn)( const char * ) )
{
    fFunction( numParams, params, PrintFn );
}

//// Link & Unlink ///////////////////////////////////////////////////////////

void    pfConsoleCmd::Link( pfConsoleCmd **prevPtr )
{
    hsAssert( fNext == nil && fPrevPtr == nil, "Trying to link console command that's already linked!" );

    fNext = *prevPtr;
    if( *prevPtr )
        (*prevPtr)->fPrevPtr = &fNext;
    fPrevPtr = prevPtr;
    *fPrevPtr = this;
}

void    pfConsoleCmd::Unlink( void )
{
    hsAssert( fNext != nil || fPrevPtr != nil, "Trying to unlink console command that isn't linked!" );

    if( fNext )
        fNext->fPrevPtr = fPrevPtr;
    *fPrevPtr = fNext;
}

//// GetSigEntry /////////////////////////////////////////////////////////////

UInt8   pfConsoleCmd::GetSigEntry( UInt8 i )
{
    if( fSignature.GetCount() == 0 )
        return kNone;

    if( i < fSignature.GetCount() )
    {
        if( fSignature[ i ] == kEtc )
            return kAny;

        return fSignature[ i ];
    }

    if( fSignature[ fSignature.GetCount() - 1 ] == kEtc )
        return kAny;

    return kNone;
}

//// GetSignature ////////////////////////////////////////////////////////////
//  Gets the signature of the command as a string. Format is:
//      name [ type param [, type param ... ] ]
//  WARNING: uses a static buffer, so don't rely on the contents if you call
//  it more than once! (You shouldn't need to, though)

const char  *pfConsoleCmd::GetSignature( void )
{
    static  char    string[ 256 ];
    
    int     i;
    char    pStr[ 128 ];


    strcpy( string, fName );
    for( i = 0; i < fSignature.GetCount(); i++ )
    {
        if( fSigLabels[ i ] == nil )
            sprintf( pStr, "%s", fSigTypes[ fSignature[ i ] ] );
        else
            sprintf( pStr, "%s %s", fSigTypes[ fSignature[ i ] ], fSigLabels[ i ] );

        hsAssert( strlen( string ) + strlen( pStr ) + 2 < sizeof( string ), "Not enough room for signature string" );
        strcat( string, ( i > 0 ) ? ", " : " " );

        strcat( string, pStr );
    }

    return string;
}


//////////////////////////////////////////////////////////////////////////////
//// pfConsoleCmdParam Functions /////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////

//// Conversion Functions ////////////////////////////////////////////////////

const int & pfConsoleCmdParam::IToInt( void ) const
{
    hsAssert( fType == kInt || fType == kAny, "Trying to use a non-int parameter as an int!" );

    static int  i;
    if( fType == kAny )
    {
        hsAssert( fValue.s != nil, "Weird parameter during conversion" );
        i = atoi( fValue.s );
        return i;
    }
    
    return fValue.i;
}

const float &   pfConsoleCmdParam::IToFloat( void ) const
{
    hsAssert( fType == kFloat || fType == kAny, "Trying to use a non-float parameter as a float!" );

    static float f;
    if( fType == kAny )
    {
        hsAssert( fValue.s != nil, "Weird parameter during conversion" );
        f = (float)atof( fValue.s );
        return f;
    }
    
    return fValue.f;
}

const bool &    pfConsoleCmdParam::IToBool( void ) const
{
    hsAssert( fType == kBool || fType == kAny, "Trying to use a non-bool parameter as a bool!" );

    static bool b;
    if( fType == kAny )
    {
        hsAssert( fValue.s != nil, "Weird parameter during conversion" );
        if( atoi( fValue.s ) > 0 || stricmp( fValue.s, "true" ) == 0 )
            b = true;
        else
            b = false;

        return b;
    }
    
    return fValue.b;
}

const pfConsoleCmdParam::CharPtr &  pfConsoleCmdParam::IToString( void ) const
{
    hsAssert( fType == kString || fType == kAny, "Trying to use a non-string parameter as a string!" );

    return fValue.s;
}

const char &    pfConsoleCmdParam::IToChar( void ) const
{
    hsAssert( fType == kChar || fType == kAny, "Trying to use a non-char parameter as a char!" );

    static char     c;
    if( fType == kAny )
    {
        hsAssert( fValue.s != nil, "Weird parameter during conversion" );
        c = fValue.s[ 0 ];
        return c;
    }
    
    return fValue.c;
}