/** \file
 *
 *  Contains the Command class declaration.
 *
 *  Copyright (c) 2007,2008,2009 MBARI
 *  MBARI Proprietary Information.  All Rights Reserved
 */

#ifndef COMMAND_H_
#define COMMAND_H_

#include <stdio.h>
#include <string.h>

#include "data/ElementURI.h"
#include "logger/DecimationLogWriter.h"
#include "logger/Logger.h"
#include "utils/FlexArray.h"
#include "utils/Str.h"

#define MAX_ARGS 9

enum CommandArgType
{
    ARG_NONE,
    ARG_COMMAND,
    ARG_COMPONENT,
    ARG_CONFIG_DIR,
    ARG_CONFIG_VARIABLE,
    ARG_LIST,
    ARG_DECIMATION_TYPE,
    ARG_FLOAT,
    ARG_INT,
    ARG_KEYWORD,
    ARG_MISSION,
    ARG_QUOTED_STRING,
    ARG_REGEX,
    ARG_SECONDS,
    ARG_SERVICE_TYPE,
    ARG_STRING,
    ARG_TIMESTAMP,
    ARG_TOKEN,
    ARG_UNIT,
    ARG_UNIVERSAL,
    ARG_VARIABLE,
};

enum RequiredType
{
    NOT_REQUIRED,
    REQUIRED,
    REQUIRED_AFTER_PREVIOUS,
};

enum MatchType
{
    MATCH_NONE,
    MATCH_UNIQUE,
    MATCH_MANY,
};

class CommandArg
{
public:
    CommandArg( const char* keyword, const RequiredType required )
        : argType_( ARG_KEYWORD ),
          keyword_( keyword ),
          required_( required ),
          keywordLen_( strlen( keyword_ ) )
    {}
    CommandArg( const CommandArgType argType, const RequiredType required, const char* altName = NULL )
        : argType_( argType ),
          keyword_( altName ),
          required_( required ),
          keywordLen_( NULL == altName ? 0 : strlen( altName ) )
    {}
    CommandArgType getArgType() const
    {
        return argType_;
    }
    const char* getKeyword() const
    {
        return keyword_;
    }
    const int getKeywordLen() const
    {
        return keywordLen_;
    }
    RequiredType getRequired() const
    {
        return required_;
    }
    bool equals( CommandArg* commandArg ) const
    {
        if( argType_ != commandArg->argType_
                || keywordLen_ != commandArg->keywordLen_ )
        {
            return false;
        }
        if( keywordLen_ == 0 )
        {
            return true;
        }
        return 0 == strncmp( keyword_, commandArg->keyword_, keywordLen_ );
    }
    void help( FILE* out ) const
    {
        if( argType_ != ARG_KEYWORD && keywordLen_ > 0 )
        {
            fprintf( out, "<%s>", keyword_ );
            return;
        }
        switch( argType_ )
        {
        case ARG_NONE:
            break;
        case ARG_COMMAND:
            fprintf( out, "<commandName>" );
            break;
        case ARG_COMPONENT:
            fprintf( out, "<componentName>" );
            break;
        case ARG_CONFIG_DIR:
            fprintf( out, "<configDir>" );
            break;
        case ARG_CONFIG_VARIABLE:
            fprintf( out, "<configVariable>" );
            break;
        case ARG_LIST:
            fprintf( out, "<numericValues>" );
            break;
        case ARG_DECIMATION_TYPE:
            fprintf( out, "<decimationType>" );
            break;
        case ARG_FLOAT:
            fprintf( out, "<number>" );
            break;
        case ARG_INT:
            fprintf( out, "<integer>" );
            break;
        case ARG_KEYWORD:
            fprintf( out, "%s", keyword_ );
            break;
        case ARG_MISSION:
            fprintf( out, "<missionPath>" );
            break;
        case ARG_QUOTED_STRING:
            fprintf( out, "<\"quotedString\">" );
            break;
        case ARG_REGEX:
            fprintf( out, "<regularExpression>" );
            break;
        case ARG_SECONDS:
            fprintf( out, "<#seconds>" );
            break;
        case ARG_SERVICE_TYPE:
            fprintf( out, "<serviceType>" );
            break;
        case ARG_STRING:
            fprintf( out, "<anyString>" );
            break;
        case ARG_TIMESTAMP:
            fprintf( out, "<timestamp>" );
            break;
        case ARG_TOKEN:
            fprintf( out, "<anyWord>" );
            break;
        case ARG_UNIT:
            fprintf( out, "<unit>" );
            break;
        case ARG_UNIVERSAL:
            fprintf( out, "<universal>" );
            break;
        case ARG_VARIABLE:
            fprintf( out, "<variable>" );
            break;
        }
    }
protected:
    const CommandArgType argType_;
    const char* keyword_;
    const RequiredType required_;
    int keywordLen_;

private:
    // Note that the copy constructor below is private and not given a body.
    // Any attempt to call it will return a compiler error.
    CommandArg( const CommandArg& old ); // disallow copy constructor
};

class ParsedCommand;

class CommandSyntax
{
public:
    CommandSyntax( const char* help, void( *syntaxCall )( ParsedCommand*, int ), const int mode )
        : args_( true ),
          argCount_( 0 ),
          help_( help ),
          syntaxCall_( syntaxCall ),
          mode_( mode ),
          altSyntax_( NULL )
    {}
    void addArg( CommandArg* arg )
    {
        args_.push( arg );
        ++argCount_;
    }
    CommandArg* getArg( int index ) const
    {
        return args_.get( index );
    }
    int getRequiredArgSize( unsigned int currentArgs )
    {
        if( args_.size() == 0 )
        {
            return 0;
        }
        int required = currentArgs;
        for( unsigned int i = required; i < args_.size(); ++i )
        {
            RequiredType requiredType = args_.get( i )->getRequired();
            if( requiredType == REQUIRED || requiredType == REQUIRED_AFTER_PREVIOUS )
            {
                ++required;
            }
            else
            {
                break;
            }
        }
        return required;
    }
    int getArgCount() const
    {
        return argCount_;
    }

    void call( ParsedCommand* parsedCommand )
    {
        syntaxCall_( parsedCommand, mode_ );
    }

    // Get an entry in a linked list of possible syntaxes
    CommandSyntax* getAltSyntax()
    {
        return altSyntax_;
    }

    // Set an entry in a linked list of possible syntaxes
    void setAltSyntax( CommandSyntax* altSyntax )
    {
        altSyntax_ = altSyntax;
    }

    void help( const char* keyword, FILE* out ) const
    {
        fprintf( out, "%s\n    ", help_ );
        fprintf( out, "%s", keyword );
        for( int i = 0; i < argCount_; ++i )
        {
            CommandArg* arg =  args_.get( i );
            RequiredType req = arg->getRequired();
            fputc( ' ', out );
            if( req == NOT_REQUIRED )
            {
                fprintf( out, "[" );
            }
            arg->help( out );
            if( req == REQUIRED_AFTER_PREVIOUS
                    || ( req == NOT_REQUIRED
                         && ( i + 1 >= argCount_
                              || ( i + 1 < argCount_
                                   && args_.get( i + 1 )->getRequired() != REQUIRED_AFTER_PREVIOUS ) ) ) )
            {
                fprintf( out, "]" );
            }
        }
        fprintf( out, "\n" );
    }

protected:
    FlexArray<CommandArg*> args_;
    int argCount_;
    const char* help_;
    void( *syntaxCall_ )( ParsedCommand*, int );
    int mode_;
    // Used for forming a linked list of possible syntaxes
    CommandSyntax* altSyntax_;

private:
    // Note that the copy constructor below is private and not given a body.
    // Any attempt to call it will return a compiler error.
    CommandSyntax( const CommandSyntax& old ); // disallow copy constructor
};

class Command
{
public:
    Command( const char* keyword, const char* description, bool advanced )
        : syntaxes_( true ),
          syntaxCount_( 0 ),
          keyword_( keyword ),
          description_( description ),
          advanced_( advanced ),
          keywordLen_( strlen( keyword_ ) )
    {}
    Command& addSyntax( const char* syntaxHelp, void( *syntaxCall )( ParsedCommand*, int ), const int mode = 0 )
    {
        CommandSyntax* syntax = new CommandSyntax( syntaxHelp, syntaxCall, mode );
        syntaxes_.push( syntax );
        ++syntaxCount_;
        return *this;
    }
    Command& addArg( CommandArg* arg )
    {
        CommandSyntax* syntax = syntaxes_.peek();
        if( syntax != NULL )
        {
            syntax->addArg( arg );
        }
        return *this;
    }
    Command& addArg( const char* keyword, const RequiredType required = REQUIRED )
    {
        return addArg( new CommandArg( keyword, required ) );
    }
    Command& addArg( const CommandArgType argType, const RequiredType required = REQUIRED, const char* altName = NULL )
    {
        return addArg( new CommandArg( argType, required, altName ) );
    }
    const char* getKeyword()
    {
        return keyword_;
    }
    const int getKeywordLen()
    {
        return keywordLen_;
    }
    CommandSyntax* getSyntax( int index )
    {
        return syntaxes_.get( index );
    }
    int getSyntaxCount()
    {
        return syntaxCount_;
    }
    void help( bool full, FILE* out ) const
    {
        fprintf( out, "%s: %s\n", keyword_, description_ );
        if( full )
        {
            for( int i = 0; i < syntaxCount_; ++i )
            {
                syntaxes_.get( i )->help( keyword_, out );
            }
        }
    }
protected:
    FlexArray<CommandSyntax*> syntaxes_;
    int syntaxCount_;
    const char* keyword_;
    const char* description_;
    bool advanced_;
    int keywordLen_;

private:
    // Note that the copy constructor below is private and not given a body.
    // Any attempt to call it will return a compiler error.
    Command( const Command& old ); // disallow copy constructor

};

class CodedStr;
class CommandLine;
class ElementURI;
class Unit;
class UniversalURI;


class ParsedCommand
{
public:
    ParsedCommand();

    /// Returns the parsed arg count
    int getArgCount()
    {
        return argCount_;
    }

    /// Undefine top arg on the stack
    void undefTopArg();

    /// Clears the top arg on the stack
    void clearTopArg();

    /// Clears everything
    void clearAll();

    /// Returns first argStart
    int getStart()
    {
        return start_;
    }

    /// Sets first argStart
    void setStart( int start )
    {
        start_ = start;
    }

    /// Returns argStart at the specified index
    /// Negative indices count backwards from the top
    int getArgStart( int index )
    {
        if( index < 0 )
        {
            index = argCount_ + index;
        }
        if( index < 0 || index >= argCount_ )
        {
            return -1;
        }
        return argStart_[index];
    }

    /// Returns argLen at the specified index
    /// Negative indices count backwards from the top
    int getArgLen( int index )
    {
        if( index < 0 )
        {
            index = argCount_ + index;
        }
        if( index < 0 || index >= argCount_ )
        {
            return -1;
        }
        return argStart_[index];
    }

    const Command* getCommand() const
    {
        return command_;
    }

    const CommandSyntax* getSyntax() const
    {
        return syntax_;
    }

    const Command* getCommandArg() const
    {
        return commandArg_;
    }

    const CodedStr* getComponentArg() const
    {
        return componentArg_;
    }

    const Str& getConfigDirArg() const
    {
        return configDirArg_;
    }

    const DecimationLogWriter::DecimationType getDecimationTypeArg() const
    {
        return decimationTypeArg_;
    }

    const Str& getMissionArg() const
    {
        return missionArg_;
    }

    const Service::ServiceType getServiceTypeArg() const
    {
        return serviceTypeArg_;
    }

    const Str& getStringArg() const
    {
        return stringArg_;
    }

    const bool isInQuotedString() const
    {
        return inQuotedString_;
    }

    void setStringArg( const Str& stringArg )
    {
        stringArg_ = stringArg;
    }

    const Str& getRegexArg() const
    {
        return regexArg_;
    }

    int getIntegerArg() const
    {
        return integerArg_;
    }

    float getNumberArg() const
    {
        return numberArg_;
    }

    const Timestamp& getTimestampArg() const
    {
        return timestampArg_;
    }

    const Str& getTokenArg() const
    {
        return tokenArg_;
    }

    const Unit* getUnitArg() const
    {
        return unitArg_;
    }

    const UniversalURI* getUniversalArg() const
    {
        return universalArg_;
    }

    const ElementURI* getVariableArg() const
    {
        return variableArg_;
    }

    /// If the last word in buffer is a valid arg, deal with it.
    /// If we need to toss the last character, return false.
    bool parseBufferEnd( FlexArray<Command*>& commands, const char* buffer, int index );

    bool completeArg( FlexArray<Command*>& commands, CommandLine* commandLine, const char* buffer, int index );

    void execute( Logger& logger );

    Str toString();

    bool hasArgIndex( int argIndex );

    CommandArgType getCurrentArgType();

protected:
    int start_;
    int argCount_;
    int argStart_[MAX_ARGS];
    int argIndex_[MAX_ARGS];
    int argLen_[MAX_ARGS];
    MatchType argMatch_[MAX_ARGS];
    CommandArgType argType_[MAX_ARGS];

    Command* command_;
    CommandSyntax* syntax_;
    Command* commandArg_;
    CommandArg* currentArg_;
    const CodedStr* componentArg_;
    Str configDirArg_;
    DecimationLogWriter::DecimationType decimationTypeArg_;
    Str missionArg_;
    Service::ServiceType serviceTypeArg_;
    Str stringArg_;
    Str regexArg_;
    int integerArg_;
    float numberArg_;
    Timestamp timestampArg_;
    Str tokenArg_;
    const Unit* unitArg_;
    const UniversalURI* universalArg_;
    const ElementURI* variableArg_;
    ElementURI scratchPadArg_;
    bool inQuotedString_;

    /// Report if a valid arg matches the supplied arg.
    /// If so, deal with it.
    bool findArg( FlexArray<Command*>& commands, const char *buffer, int len );

    void implySyntax();

    bool syntaxOk();

    MatchType orMatch( MatchType mt1, MatchType mt2 )
    {
        if( mt1 == MATCH_NONE && mt2 == MATCH_NONE ) return MATCH_NONE;
        if( ( mt1 == MATCH_NONE && mt2 == MATCH_UNIQUE )
                || ( mt1 == MATCH_UNIQUE && mt2 == MATCH_NONE ) ) return MATCH_UNIQUE;
        return MATCH_MANY;
    }

    MatchType matchArg( FlexArray<Command*>& commands, const CommandArg* arg, int argIndex, const char *buffer, int len );

    MatchType matchUnique( int synArgIndex, CommandArgType argType );

    /// Report if a command name matches the supplied arg.
    /// If so, deal with it.
    MatchType matchCommand( FlexArray<Command*>& commands, Command*& command, int synArgIndex, const char *buffer, int len );

    bool completeCommand( FlexArray<Command*>& commands, CommandLine* commandLine, const char *buffer, int len );

    MatchType matchComponent( int synArgIndex, const char *buffer, int len );

    MatchType matchConfigDir( int synArgIndex, const char *buffer, int len );

    MatchType matchConfigSubDir( const Str& subDir, int synArgIndex, const char *buffer, int len );

    MatchType matchConfigVariable( int synArgIndex, const char *buffer, int len );

    MatchType matchList( int synArgIndex, const char *buffer, int len );

    MatchType matchDecimationType( int synArgIndex, const char *buffer, int len );

    MatchType matchFloat( int synArgIndex, const char *buffer, int len );

    MatchType matchInteger( int synArgIndex, const char *buffer, int len );

    MatchType matchKeyword( const char* keyword, int keywordLen, int synArgIndex, const char *buffer, int len );

    MatchType matchMission( int synArgIndex, const char *buffer, int len );

    MatchType matchQuotedString( int synArgIndex, const char *buffer, int len );

    MatchType matchRegex( int synArgIndex, const char *buffer, int len );

    MatchType matchSeconds( int synArgIndex, const char *buffer, int len );

    MatchType matchServiceType( int synArgIndex, const char *buffer, int len );

    MatchType matchString( int synArgIndex, const char *buffer, int len );

    MatchType matchTimestamp( int synArgIndex, const char *buffer, int len );

    MatchType matchToken( int synArgIndex, const char *buffer, int len );

    MatchType matchUnit( int synArgIndex, const char *buffer, int len );

    MatchType matchUniversal( int synArgIndex, const char *buffer, int len );

    MatchType matchVariable( int synArgIndex, const char *buffer, int len );


private:
    // Note that the copy constructor below is private and not given a body.
    // Any attempt to call it will return a compiler error.
    ParsedCommand( const ParsedCommand& old ); // disallow copy constructor

};

#endif /*COMMANDLINE_H_*/
