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

#include "Command.h"

#include <cstddef>
#include <ctype.h>
#include <dirent.h>
#include <math.h>
#include <stdlib.h>
#include <unistd.h>

#include "data/ConfigDataElement.h"
#include "data/ElementURI.h"
#include "data/Slate.h"
#include "data/StrValue.h"
#include "data/UniversalDataElement.h"
#include "supervisor/CommandLine.h"
#include "units/UnitRegistry.h"

ParsedCommand::ParsedCommand( )
    : start_( -1 ),
      argCount_( 0 ),
      command_( NULL ),
      syntax_( NULL ),
      commandArg_( NULL ),
      currentArg_( NULL ),
      componentArg_( NULL ),
      configDirArg_(),
      decimationTypeArg_( DecimationLogWriter::UNDEFINED_DECIMATION ),
      missionArg_(),
      serviceTypeArg_( Service::SERVICE_UNSPECIFIED ),
      stringArg_(),
      regexArg_(),
      integerArg_( -1 ),
      numberArg_( nanf( "" ) ),
      timestampArg_( Timestamp::NOT_SET_TIME ),
      tokenArg_( Str::EMPTY_STR ),
      unitArg_( NULL ),
      universalArg_( NULL ),
      variableArg_( NULL ),
      scratchPadArg_( Str::EMPTY_STR, "#scratch" ),
      inQuotedString_( false )
{
    for( int i = 0; i < MAX_ARGS; ++i )
    {
        argStart_[i] = -1;
        argLen_[i] = 0;
        argIndex_[i] = -1;
        argMatch_[i] = MATCH_NONE;
        argType_[i] = ARG_NONE;
    }
}

/// Undefine top arg on the stack
void ParsedCommand::undefTopArg()
{
    if( argCount_ > 0 )
    {
        switch( argType_[argCount_ - 1] )
        {
        case ARG_COMMAND:
            commandArg_ = NULL;
            break;
        case ARG_COMPONENT:
            componentArg_ = NULL;
            break;
        case ARG_CONFIG_DIR:
            configDirArg_ = Str::EMPTY_STR;
            break;
        case ARG_CONFIG_VARIABLE:
            variableArg_ = NULL;
            break;
        case ARG_LIST:
            stringArg_ = Str::EMPTY_STR;
            break;
        case ARG_DECIMATION_TYPE:
            decimationTypeArg_ = DecimationLogWriter::UNDEFINED_DECIMATION;
            break;
        case ARG_FLOAT:
            numberArg_ = nanf( "" );
            break;
        case ARG_INT:
            integerArg_ = -1;
            break;
        case ARG_KEYWORD:
            break;
        case ARG_MISSION:
            missionArg_ = Str::EMPTY_STR;
            break;
        case ARG_NONE:
            break;
        case ARG_QUOTED_STRING:
            stringArg_ = Str::EMPTY_STR;
            break;
        case ARG_REGEX:
            regexArg_ = Str::EMPTY_STR;
            break;
        case ARG_SECONDS:
            numberArg_ = nanf( "" );
            break;
        case ARG_SERVICE_TYPE:
            serviceTypeArg_ = Service::SERVICE_UNSPECIFIED;
            break;
        case ARG_STRING:
            stringArg_ = Str::EMPTY_STR;
            break;
        case ARG_TIMESTAMP:
            timestampArg_ = Timestamp::NOT_SET_TIME;
            break;
        case ARG_TOKEN:
            tokenArg_ = Str::EMPTY_STR;
            break;
        case ARG_UNIT:
            unitArg_ = NULL;
            break;
        case ARG_UNIVERSAL:
            universalArg_ = NULL;
            break;
        case ARG_VARIABLE:
            variableArg_ = NULL;
            break;
        }
    }
}

/// Clears the top arg on the stack
void ParsedCommand::clearTopArg()
{
    if( argCount_ > 0 )
    {
        undefTopArg();
        --argCount_;
        argStart_[argCount_] = -1;
        argLen_[argCount_] = 0;
        argIndex_[argCount_] = -1;
        argMatch_[argCount_] = MATCH_NONE;
        argType_[argCount_] = ARG_NONE;
    }
    if( argCount_ <= 1 )
    {
        syntax_ = NULL;
    }
    if( argCount_ == 0 )
    {
        command_ = NULL;
    }
    if( tokenArg_ != Str::EMPTY_STR )
    {
        tokenArg_ = Str::EMPTY_STR;
    }
    currentArg_ = NULL;
}

/// Clears everything
void ParsedCommand::clearAll()
{
    while( argCount_ > 0 )
    {
        clearTopArg();
    }
    start_ = -1;
}

bool ParsedCommand::parseBufferEnd( FlexArray<Command*>& commands, const char* buffer, int index )
{
    char theChar =  buffer[ index ];
    bool wasDelete = ( 0 == theChar );

    // First deal with total arg deletions, etc.
    if( wasDelete )
    {
        --index;
        theChar =  buffer[ index ];
        for( int i = argCount_ - 1; i >= 1; --i )
        {
            if( argStart_[i] > index )
            {
                clearTopArg();
            }
        }
    }
    else
    {
        // Maybe we've got a new command starting up?
        if( index == 0 && argCount_ == 0 )
        {
            argStart_[argCount_] = index;
            argLen_[argCount_] = 0;
            ++argCount_;
        }
    }

    if( argCount_ == 0 )
    {
        // Should not get here
        return false;
    }

    // Now we do the test...
    int start = argStart_[argCount_ - 1];
    argLen_[argCount_ - 1] = index - start + 1;
    if( findArg( commands, buffer + start, index - start + 1 ) )
    {
        // We found an arg, and we are happy
        return true;
    }

    // Seems to be something other than an arg,  Maybe a space or a ';'
    argLen_[argCount_ - 1] = index - start;

    // Re-parse the top of the stack
    findArg( commands, buffer + start, index - start );

    if( argType_[argCount_ - 1] != ARG_STRING
            && ( argType_[argCount_ - 1] != ARG_QUOTED_STRING
                 || argMatch_[argCount_ - 1] == MATCH_UNIQUE ) )
    {
        // To end with a space, must have a complete enough argument.
        if( argType_[argCount_ - 1] != ARG_NONE && theChar == ' ' )
        {
            if( NULL == syntax_ || ( int )syntax_->getArgCount() + 1 > argCount_ )
            {
                argStart_[argCount_] = index + 1;
                ++argCount_;
                return true;
            }
            return false;
        }

        // To end with a semicolon, must have a complete syntax.
        if( theChar == ';' )
        {
            implySyntax();
            return syntaxOk();
        }
    }
    return false;
}

bool ParsedCommand::completeArg( FlexArray<Command*>& commands, CommandLine* commandLine, const char* buffer, int index )
{
    int start = argStart_[argCount_ - 1];
    const char* argBuf = buffer + start;
    int argLen = argLen_[argCount_ - 1];
    if( argCount_ == 1 )
    {
        return completeCommand( commands, commandLine, argBuf, argLen );
    }

    return true;
}

void ParsedCommand::execute( Logger& logger )
{
    implySyntax();
    if( syntaxOk() )
    {
        syntax_->call( this );
    }
    else if( NULL != command_ )
    {
        logger.syslog( "Incomplete syntax. Try: help ", command_->getKeyword(), Syslog::FAULT );
    }
    else if( argLen_[0] > 0 )
    {
        logger.syslog( "Incomplete syntax. Try: help", Syslog::FAULT );
    }
    else
    {
        logger.syslog( "Syntax error. Try: help", Syslog::FAULT );
    }
}

Str ParsedCommand::toString()
{
    Str buffer;
    for( int i = 0; i < argCount_; ++i )
    {
        if( i > 0 )
        {
            buffer += " ";
        }
        switch( argType_[i] )
        {
        case ARG_COMMAND:
            if( i > 0 && commandArg_ != NULL )
            {
                buffer += commandArg_->getKeyword();
            }
            else if( i == 0 && command_ != NULL )
            {
                buffer += command_->getKeyword();
            }
            break;
        case ARG_COMPONENT:
            if( componentArg_ != NULL )
            {
                buffer += *componentArg_;
            }
            break;
        case ARG_CONFIG_DIR:
            buffer += configDirArg_;
            break;
        case ARG_CONFIG_VARIABLE:
            if( variableArg_ != NULL )
            {
                buffer += *variableArg_;
            }
            break;
        case ARG_LIST:
            buffer += tokenArg_;
            break;
        case ARG_DECIMATION_TYPE:
            buffer += DecimationLogWriter::DecimationType2Str( decimationTypeArg_ );
            break;
        case ARG_FLOAT:
            buffer += numberArg_;
            break;
        case ARG_INT:
            buffer += integerArg_;
            break;
        case ARG_KEYWORD:
            if( NULL != syntax_ )
            {
                buffer += syntax_->getArg( argIndex_[i] )->getKeyword();
            }
            break;
        case ARG_MISSION:
            buffer += missionArg_;
            break;
        case ARG_NONE:
            break;
        case ARG_QUOTED_STRING:
            buffer += "\"" + stringArg_ + "\"";
            break;
        case ARG_REGEX:
            buffer += regexArg_;
            break;
        case ARG_SECONDS:
            buffer += numberArg_;
            break;
        case ARG_SERVICE_TYPE:
            buffer += Service::ServiceType2Str( serviceTypeArg_ );
            break;
        case ARG_STRING:
            buffer += stringArg_;
            break;
        case ARG_TIMESTAMP:
            buffer += timestampArg_.toSmallString();
            break;
        case ARG_TOKEN:
            buffer += tokenArg_;
            break;
        case ARG_UNIT:
            if( unitArg_ != NULL )
            {
                buffer += unitArg_->getName();
            }
            break;
        case ARG_UNIVERSAL:
            if( universalArg_ != NULL )
            {
                buffer += *universalArg_;
            }
            break;
        case ARG_VARIABLE:
            if( variableArg_ != NULL )
            {
                buffer += *variableArg_;
            }
            break;
        }
    }
    return buffer;
}

bool ParsedCommand::hasArgIndex( int argIndex )
{
    for( int i = 0; i < argCount_; ++i )
    {
        if( argIndex_[i] == argIndex )
        {
            return true;
        }
    }
    return false;
}

CommandArgType  ParsedCommand::getCurrentArgType()
{
    return NULL == currentArg_ ? ARG_NONE : currentArg_->getArgType();
}

MatchType ParsedCommand::matchArg( FlexArray<Command*>& commands, const CommandArg* arg, int argIndex, const char *buffer, int len )
{
    CommandArgType argType( arg->getArgType() );
    MatchType match( MATCH_NONE );
    if( argType != ARG_QUOTED_STRING )
    {
        inQuotedString_ = false;
    }
    if( argType != ARG_STRING && argType != ARG_QUOTED_STRING && len > 0
            && ( buffer[ len - 1 ] == ' ' || buffer[ len - 1 ] == ';' ) )
    {
        return match;
    }
    switch( argType )
    {
    case ARG_COMMAND:
        match = matchCommand( commands, commandArg_, argIndex, buffer, len );
        break;
    case ARG_COMPONENT:
        match = matchComponent( argIndex, buffer, len );
        break;
    case ARG_CONFIG_DIR:
        match = matchConfigDir( argIndex, buffer, len );
        break;
    case ARG_CONFIG_VARIABLE:
        match = matchConfigVariable( argIndex, buffer, len );
        break;
    case ARG_LIST:
        match = matchList( argIndex, buffer, len );
        break;
    case ARG_DECIMATION_TYPE:
        match = matchDecimationType( argIndex, buffer, len );
        break;
    case ARG_FLOAT:
        match = matchFloat( argIndex, buffer, len );
        break;
    case ARG_INT:
        match = matchInteger( argIndex, buffer, len );
        break;
    case ARG_KEYWORD:
        match = matchKeyword( arg->getKeyword(), arg->getKeywordLen(), argIndex, buffer, len );
        break;
    case ARG_MISSION:
        match = matchMission( argIndex, buffer, len );
        break;
    case ARG_NONE:
        break;
    case ARG_QUOTED_STRING:
        match = matchQuotedString( argIndex, buffer, len );
        inQuotedString_ = match != MATCH_UNIQUE;
        break;
    case ARG_REGEX:
        match = matchRegex( argIndex, buffer, len );
        break;
    case ARG_SECONDS:
        match = matchSeconds( argIndex, buffer, len );
        break;
    case ARG_SERVICE_TYPE:
        match = matchServiceType( argIndex, buffer, len );
        break;
    case ARG_STRING:
        match = matchString( argIndex, buffer, len );
        break;
    case ARG_TIMESTAMP:
        match = matchTimestamp( argIndex, buffer, len );
        break;
    case ARG_TOKEN:
        match = matchToken( argIndex, buffer, len );
        break;
    case ARG_UNIT:
        match = matchUnit( argIndex, buffer, len );
        break;
    case ARG_UNIVERSAL:
        match = matchUniversal( argIndex, buffer, len );
        break;
    case ARG_VARIABLE:
        match = matchVariable( argIndex, buffer, len );
        break;
    }
    return match;
}

MatchType ParsedCommand::matchUnique( int synArgIndex, CommandArgType argType )
{
    argMatch_[argCount_ - 1] = MATCH_UNIQUE;
    argType_[argCount_ - 1] = argType;
    argIndex_[argCount_ - 1] = synArgIndex;
    return MATCH_UNIQUE;
}

MatchType ParsedCommand::matchCommand( FlexArray<Command*>& commands, Command*& command, int synArgIndex, const char *buffer, int len )
{
    MatchType match( MATCH_NONE );
    Command* uniqueCommand( NULL );

    /// Insert "^[number] " before a command.  Not used.
    /*if( buffer[0] == '^' )
    {
        bool haveSpace = false;
        ++buffer;
        --len;
        while( len > 0 && isdigit( buffer[0] ) )
        {
            ++buffer;
            --len;
        }
        if( len >  0 && buffer[0] == ' ' )
        {
            ++buffer;
            --len;
            haveSpace = true;
        }
        if( len == 0 )
        {
            return MATCH_MANY;
        }
        if( !haveSpace )
        {
            return MATCH_NONE;
        }
    }*/

    for( unsigned int i = 0; i < commands.size(); ++i )
    {
        Command* aCommand = commands.get( i );
        const char* keyword = aCommand->getKeyword();
        if( strncasecmp( keyword, buffer, len ) == 0 )
        {
            if( match == MATCH_NONE )
            {
                match = MATCH_UNIQUE;
                uniqueCommand = aCommand;
            }
            else
            {
                match = MATCH_MANY;
                break;
            }
        }
    }
    if( match == MATCH_UNIQUE )
    {
        command = uniqueCommand;
        argType_[argCount_ - 1] = ARG_COMMAND;
        argIndex_[argCount_ - 1] = synArgIndex;
    }
    else
    {
        command = NULL;
    }

    return match;
}

bool ParsedCommand::completeCommand( FlexArray<Command*>& commands, CommandLine* commandLine, const char *buffer, int len )
{
    if( NULL != command_ )
    {
        const char* keyword = command_->getKeyword();
        int keylen = strlen( keyword );
        for( int i = len; i < keylen; ++i )
        {
            char theChar = keyword[i];
            commandLine->setChar( theChar );
        }
        return true;
    }

    bool gotFirst = false;
    for( unsigned int i = 0; i < commands.size(); ++i )
    {
        Command* command = commands.get( i );
        const char* keyword = command->getKeyword();
        if( len <= command->getKeywordLen() && strncasecmp( keyword, buffer, len ) == 0 )
        {
            if( !gotFirst )
            {
                fwrite( keyword + len, 1, strlen( keyword ) - len, commandLine->getOutFile() );
                gotFirst = true;
            }
            else
            {
                fputc( '|', commandLine->getOutFile() );
                fwrite( keyword, 1, strlen( keyword ), commandLine->getOutFile() );
            }
        }
    }
    return false;
}

MatchType ParsedCommand::matchComponent( int synArgIndex, const char *buffer, int len )
{
    MatchType match( MATCH_NONE );
    const CodedStr* uniqueComponent( NULL );

    // loop through the slate to search for the search string
    for( unsigned short int code = 0; code < CodedStr::NO_CODE; ++code )
    {
        const CodedStr* name = Slate::GetName( code );
        if( NULL == name )
        {
            if( code > 0 )
            {
                break;
            }
            continue;
        }

        // see if the element matches the search string
        if( len <= ( int )name->length() && strncasecmp( name->cStr(), buffer, len ) == 0 )
        {
            if( len == ( int )name->length() )
            {
                match = MATCH_UNIQUE;
                uniqueComponent = name;
                argType_[argCount_ - 1] = ARG_COMPONENT;
                argIndex_[argCount_ - 1] = synArgIndex;
                break;
            }
            match = MATCH_MANY;
        }
    }
    componentArg_ = uniqueComponent;

    return match;

}

MatchType ParsedCommand::matchConfigDir( int synArgIndex, const char *buffer, int len )
{
    MatchType match( matchConfigSubDir( Str::EMPTY_STR, synArgIndex, buffer, len ) );
    Str hostnamePath;
    Str usernamePath;
    Str simPath;
    Str simUsernamePath;
    const size_t hnlen( 100 );
    char hostname[hnlen];
    *hostname = 0;
    if( 0 == gethostname( hostname, hnlen ) )
    {
        char* dotAt = strchr( hostname, '.' );
        if( NULL != dotAt && hostname != dotAt )
        {
            *dotAt = 0;
        }
        hostnamePath =  Str( hostname ) + "/";
    }
    if( hostnamePath != Str::EMPTY_STR )
    {
        match = orMatch( match, matchConfigSubDir( hostnamePath, synArgIndex, buffer, len ) );
        usernamePath = hostnamePath + getlogin() + "/";
        match = orMatch( match, matchConfigSubDir( usernamePath, synArgIndex, buffer, len ) );
    }
#ifndef __arm__
    simPath = "sim/";
#endif //#ifndef __arm__
    if( simPath != Str::EMPTY_STR )
    {
        match = orMatch( match, matchConfigSubDir( simPath, synArgIndex, buffer, len ) );
        simUsernamePath = simPath + getlogin() + "/";
        match = orMatch( match, matchConfigSubDir( simUsernamePath, synArgIndex, buffer, len ) );
    }
    return match;
}

MatchType ParsedCommand::matchConfigSubDir( const Str& subDir, int synArgIndex, const char *buffer, int len )
{
    MatchType match( MATCH_NONE );
    configDirArg_ = Str::EMPTY_STR;

    DIR* dir( opendir( ( "Config/" + subDir ).cStr() ) );
    if( NULL == dir )
    {
        return MATCH_NONE;
    }
    else
    {
        union // To use thread-safe readdir_r make sure dirent struct is big enough
        {
            struct dirent rent_;
            char b_[offsetof( struct dirent, d_name ) + NAME_MAX + 1];
        } di;
        struct dirent* entry;
        while( 0 == readdir_r( dir, &di.rent_, &entry ) && entry != NULL )
        {
            if( entry != NULL && entry->d_type == DT_DIR )
            {
                int nameLen = strlen( entry->d_name );
                if( len <= nameLen && strncasecmp( entry->d_name, buffer, len ) == 0 )
                {
                    if( len == nameLen )
                    {
                        match = MATCH_UNIQUE;
                        configDirArg_ = entry->d_name;
                        argType_[argCount_ - 1] = ARG_CONFIG_DIR;
                        argIndex_[argCount_ - 1] = synArgIndex;
                        break;
                    }
                    match = MATCH_MANY;
                }
            }
        }
        closedir( dir );
    }

    return match;

}

MatchType ParsedCommand::matchConfigVariable( int synArgIndex, const char *buffer, int len )
{
    MatchType match( MATCH_NONE );
    const ElementURI* uniqueVar( NULL );

    // loop through the slate to search for the search string
    for( unsigned short int index = 0; index < ConfigDataElement::GetConfigDataElementCount(); ++index )
    {
        ConfigDataElement* dataElement = ConfigDataElement::GetConfigDataElement( index );
        if( NULL == dataElement )
        {
            continue;
        }
        const ElementURI& var = dataElement->getUri();

        // see if the element matches the search string
        if( var.startsWith( buffer, len ) )
        {
            if( ( int )var.length() == len )
            {
                uniqueVar = &var;
                argType_[argCount_ - 1] = ARG_VARIABLE;
                argIndex_[argCount_ - 1] = synArgIndex;
                match = MATCH_UNIQUE;
                break;
            }
            match = MATCH_MANY;
        }
    }
    variableArg_ = uniqueVar;
    return match;
}

MatchType ParsedCommand::matchList( int synArgIndex, const char *buffer, int len )
{
    bool gotPeriod( false );
    bool gotNaN( false );
    bool hasBrackets = false;
    int bracketDepth( 0 );
    int ePos( -1 );
    int commaAt( -1 );
    if( len == 0 )
    {
        return MATCH_NONE;
    }
    for( int i = 0; i < len; ++i )
    {
        if( i > 0 && buffer[ i - 1 ] == ']' && buffer[i] != ']' && buffer[i] != ',' )
        {
            return MATCH_NONE;
        }
        if( tolower( buffer[commaAt + 1] ) == 'n'
                && ( i == commaAt + 1 || ( tolower( buffer[commaAt + 2] ) == 'a'
                                           && ( i == commaAt + 2 || ( tolower( buffer[commaAt + 3] ) == 'n'
                                                   && i == commaAt + 3 ) ) ) ) )
        {
            gotNaN = true;
            continue;
        }
        if( !gotNaN && buffer[i] == '.' && !gotPeriod && ePos == -1 )
        {
            gotPeriod = true;
            continue;
        }
        if( !gotNaN && tolower( buffer[i] ) == 'e' && ePos == -1 && i > 0 && buffer[i - 1] != '-' && buffer[i - 1] != ',' )
        {
            ePos = i;
            continue;
        }
        if( i > 0 && buffer[i] == ',' && buffer[i - 1] != '-' && buffer[i - 1] != 'e' && buffer[i - 1] != ',' && ( !hasBrackets || bracketDepth > 0 ) )
        {
            commaAt = i;
            gotNaN = false;
            ePos = -1;
            gotPeriod = false;
            continue;
        }
        if( buffer[i] == '[' && ( ( buffer[i - 1] == ',' && bracketDepth > 0 ) || buffer[i - 1] == '[' || i == 0 ) )
        {
            ++bracketDepth;
            hasBrackets = true;
            gotNaN = false;
            ePos = -1;
            gotPeriod = false;
            continue;
        }
        if( bracketDepth > 0 && buffer[i] == ']' && buffer[i - 1] != '-' && buffer[i - 1] != 'e' && buffer[i - 1] != ',' )
        {
            --bracketDepth;
            gotNaN = false;
            ePos = -1;
            gotPeriod = false;
            continue;
        }
        if( !gotNaN && buffer[i] == '-' && ( ePos + 1 == i || commaAt + 1 == i ) )
        {
            continue;
        }
        if( gotNaN || buffer[i] < '0' || buffer[i] > '9' )
        {
            return MATCH_NONE;
        }
    }
    if( commaAt > 0 && ( buffer[len - 1] == '-' || buffer[len - 1] == 'e' || buffer[len - 1] == ',' || bracketDepth > 0 ) )
    {
        return MATCH_MANY;
    }
    tokenArg_ = Str( buffer, len );
    return matchUnique( synArgIndex, ARG_LIST );
}


MatchType ParsedCommand::matchDecimationType( int synArgIndex, const char *buffer, int len )
{
    decimationTypeArg_ = DecimationLogWriter::Str2DecimationType( buffer, len );
    switch( decimationTypeArg_ )
    {
    case DecimationLogWriter::UNDEFINED_DECIMATION:
        return MATCH_NONE;
    case DecimationLogWriter::DECIMATION_MULTI:
        return MATCH_MANY;
    default:
        return matchUnique( synArgIndex, ARG_DECIMATION_TYPE );
    }
}

MatchType ParsedCommand::matchFloat( int synArgIndex, const char *buffer, int len )
{
    bool gotPeriod( false );
    bool gotNaN( false );
    int ePos( -1 );
    if( len == 0 )
    {
        return MATCH_NONE;
    }
    for( int i = 0; i < len; ++i )
    {
        if( tolower( buffer[0] ) == 'n'
                && ( i == 0 || ( tolower( buffer[1] ) == 'a'
                                 && ( i == 1 || ( tolower( buffer[2] ) == 'n'
                                         && i == 2 ) ) ) ) )
        {
            gotNaN = true;
            continue;
        }
        if( !gotNaN && buffer[i] == '.' && !gotPeriod && ePos == -1 )
        {
            gotPeriod = true;
            continue;
        }
        if( !gotNaN && tolower( buffer[i] ) == 'e' && ePos == -1 && i > 0 && buffer[i - 1] != '-' )
        {
            ePos = i;
            continue;
        }
        if( !gotNaN && buffer[i] == '-' && ePos + 1 == i )
        {
            continue;
        }
        if( gotNaN || buffer[i] < '0' || buffer[i] > '9' )
        {
            return MATCH_NONE;
        }
    }
    if( len > 0 && ( buffer[len - 1] == '-' || buffer[len - 1] == 'e' ) )
    {
        return MATCH_MANY;
    }
    numberArg_ = gotNaN ? nanf( "" ) : strtof( buffer, NULL );
    return matchUnique( synArgIndex, ARG_FLOAT );
}

MatchType ParsedCommand::matchInteger( int synArgIndex, const char *buffer, int len )
{
    if( len == 0 )
    {
        return MATCH_NONE;
    }
    for( int i = 0; i < len; ++i )
    {
        if( buffer[i] == '-' && 0 == i )
        {
            continue;
        }
        if( buffer[i] < '0' || buffer[i] > '9' )
        {
            return MATCH_NONE;
        }
    }
    integerArg_ = atoi( buffer );
    return matchUnique( synArgIndex, ARG_INT );
}

MatchType ParsedCommand::matchKeyword( const char* keyword, int keywordLen, int synArgIndex, const char *buffer, int len )
{
    //printf("\n*** matchKeyword **\n");
    MatchType matchType( MATCH_NONE );
    if( len <= keywordLen && strncasecmp( keyword, buffer, len ) == 0 )
    {
        matchType = matchUnique( synArgIndex, ARG_KEYWORD );
        //printf("\n*** for len=%d, keywordLen=%d, keyword=%s, buffer=%s\n",len,keywordLen,keyword,buffer);
    }
    return matchType;
}

MatchType ParsedCommand::matchMission( int synArgIndex, const char *buffer, int len )
{
    MatchType match( MATCH_NONE );
    missionArg_ = Str::EMPTY_STR;

    Str path = buffer;
    Str filename;
    size_t lastSlash = Str::FindLastOf( buffer, '/', len );

    if( Str::NO_POS == lastSlash )
    {
        path = "./Missions/";
        filename = Str( buffer, len );
    }
    else if( lastSlash == 0 )
    {
        path = "/";
        filename = Str( buffer + 1, len - 1 );
    }
    else if( buffer[0] == '/' )
    {
        path = Str( buffer, lastSlash + 1 );
        filename = Str( buffer + lastSlash + 1, len - lastSlash - 1 );
    }
    else
    {
        path = "./Missions/" + Str( buffer, lastSlash + 1 );
        filename = Str( buffer + lastSlash + 1, len - lastSlash - 1 );
    }

    DIR* dir( opendir( path.cStr() ) );
    if( NULL == dir )
    {
        return MATCH_NONE;
    }
    else
    {
        union // To use thread-safe readdir_r make sure dirent struct is big enough
        {
            struct dirent rent_;
            char b_[offsetof( struct dirent, d_name ) + NAME_MAX + 1];
        } di;
        struct dirent* entry;
        while( 0 == readdir_r( dir, &di.rent_, &entry ) && entry != NULL )
        {
            if( entry != NULL )
            {
                size_t nameLen = strlen( entry->d_name );
                if( filename.length() <= nameLen && strncasecmp( entry->d_name, filename.cStr(), filename.length() ) == 0 )
                {
                    if( DT_REG == entry->d_type )
                    {
                        if( filename.length() == nameLen ||
                                entry->d_name[filename.length()] == '.' )
                        {
                            missionArg_ = path + filename;
                            argType_[argCount_ - 1] = ARG_MISSION;
                            argIndex_[argCount_ - 1] = synArgIndex;
                            match = MATCH_UNIQUE;
                            break;
                        }
                    }
                    match = MATCH_MANY;
                }
            }
        }
        closedir( dir );
    }
    return match;

}

MatchType ParsedCommand::matchQuotedString( int synArgIndex, const char *buffer, int len )
{
    if( buffer[0] != '\"' )
    {
        stringArg_ = Str::EMPTY_STR;
        return MATCH_NONE;
    }
    if( len > 2 && buffer[ len - 2 ] == '\"' )
    {
        stringArg_ = Str::EMPTY_STR;
        return MATCH_NONE;
    }
    if( len > 1 && buffer[ len - 1 ] == '\"' )
    {
        stringArg_ = Str( buffer + 1, len - 2 );
        return matchUnique( synArgIndex, ARG_QUOTED_STRING );
    }
    stringArg_ = Str::EMPTY_STR;
    return MATCH_MANY;
}

MatchType ParsedCommand::matchRegex( int synArgIndex, const char *buffer, int len )
{
    regexArg_ = Str( buffer, len );
    return matchUnique( synArgIndex, ARG_REGEX );
}

MatchType ParsedCommand::matchSeconds( int synArgIndex, const char *buffer, int len )
{
    bool gotPeriod( false );
    if( len == 0 )
    {
        return MATCH_NONE;
    }
    for( int i = 0; i < len; ++i )
    {
        if( buffer[i] == '.' && !gotPeriod )
        {
            gotPeriod = true;
            continue;
        }
        if( buffer[i] < '0' || buffer[i] > '9' )
        {
            return MATCH_NONE;
        }
    }
    numberArg_ = strtof( buffer, NULL );
    return matchUnique( synArgIndex, ARG_SECONDS );
}

MatchType ParsedCommand::matchServiceType( int synArgIndex, const char *buffer, int len )
{
    serviceTypeArg_ = Service::Str2ServiceType( buffer, len );
    switch( serviceTypeArg_ )
    {
    case Service::SERVICE_UNSPECIFIED:
        return MATCH_NONE;
    case Service::SERVICE_MULTI:
        return MATCH_MANY;
    default:
        return matchUnique( synArgIndex, ARG_SERVICE_TYPE );
    }

}

MatchType ParsedCommand::matchString( int synArgIndex, const char *buffer, int len )
{
    if( len == 0 )
    {
        return MATCH_NONE;
    }
    stringArg_ = Str( buffer, len );
    return matchUnique( synArgIndex, ARG_STRING );
}

MatchType ParsedCommand::matchTimestamp( int synArgIndex, const char *buffer, int len )
{
    if( len == 0 )
    {
        return MATCH_NONE;
    }
    char fmt[] = "mm-dd-YYYYTHH:MM:SSZ";

    bool hasDash = strchr( buffer, '-' ) != NULL;
    bool hasSlash = strchr( buffer, '/' ) != NULL;
    bool yearFirst = len > 3 &&
                     ( ( hasDash && buffer[4] == '-' ) || ( hasSlash && buffer[4] == '/' ) );
    bool hasT = strchr( buffer, 'T' ) != NULL;
    bool hasColon = strchr( buffer, ':' ) != NULL ;
    bool hasZ = buffer[len - 1] == 'Z';

    const char* dateFmt = hasDash ? ( yearFirst ? "YYYY-mm-dd" : "mm-dd-YYYY" ) :
                          hasSlash ? ( yearFirst ? "YYYY/mm/dd" : "mm/dd/YYYY" ) : "YYYYmmdd";
    const char* betweenFmt = hasT ? "T" : "";
    const char* timeFmt = hasColon ? "HH:MM:SS" : "HHMMSS";
    const char* zFmt = hasZ ? "Z" : "";

    int fmtLen = snprintf( fmt, sizeof( fmt ), "%s%s%s%s", dateFmt, betweenFmt, timeFmt, zFmt );

    if( len > fmtLen )
    {
        timestampArg_ = Timestamp::NOT_SET_TIME;
        return MATCH_NONE;
    }

    bool gotHour = false;
    int year( 0 ), month( 0 ), day( 0 ), hour( 0 ), minute( 0 ), second( 0 );
    for( int i = 0; i < len && i < fmtLen; ++i )
    {
        switch( fmt[i] )
        {
        case 'Y':
        case 'm':
        case 'd':
        case 'H':
        case 'M':
        case 'S':
            if( !isdigit( buffer[i] ) )
            {
                timestampArg_ == Timestamp::NOT_SET_TIME;
                return MATCH_NONE;
            }
            break;
        default:
            if( buffer[i] != fmt[i] )
            {
                return MATCH_NONE;
            }
            break;
        }

        switch( fmt[i] )
        {
        case 'Y':
            year = year * 10 + buffer[i] - '0';
            break;
        case 'm':
            month = month * 10 + buffer[i] - '0';
            break;
        case 'd':
            day = day * 10 + buffer[i] - '0';
            break;
        case 'H':
            gotHour = true;
            hour = hour * 10 + buffer[i] - '0';
            break;
        case 'M':
            minute = minute * 10 + buffer[i] - '0';
            break;
        case 'S':
            second = second * 10 + buffer[i] - '0';
            break;
        default:
            break;
        }
    }
    if( len < fmtLen && !gotHour )
    {
        timestampArg_ = Timestamp::NOT_SET_TIME;
        return MATCH_MANY;
    }

    timestampArg_ = Timestamp( year, month, day, hour, minute, second );
    return matchUnique( synArgIndex, ARG_TIMESTAMP );
}

MatchType ParsedCommand::matchToken( int synArgIndex, const char *buffer, int len )
{
    if( len == 0 )
    {
        return MATCH_NONE;
    }
    tokenArg_ = Str( buffer, len );
    return matchUnique( synArgIndex, ARG_TOKEN );
}

MatchType ParsedCommand::matchUnit( int synArgIndex, const char *buffer, int len )
{
    MatchType match( MATCH_NONE );
    unitArg_ =  NULL;
    for( unsigned int i = 0; i < UnitRegistry::Size(); ++i )
    {
        const Str& key = UnitRegistry::GetIndexedKey( i );
        if( key.startsWith( buffer, len, false ) )
        {
            if( ( int )key.length() == len )
            {
                unitArg_ = UnitRegistry::GetIndexedUnit( i );
                argType_[argCount_ - 1] = ARG_UNIT;
                argIndex_[argCount_ - 1] = synArgIndex;
                match = MATCH_UNIQUE;
                break;
            }
            match = MATCH_MANY;
        }
    }

    return match;
}

MatchType ParsedCommand::matchUniversal( int synArgIndex, const char *buffer, int len )
{
    MatchType match( MATCH_NONE );
    universalArg_ = NULL;

    // loop through the slate to search for the search string
    for( unsigned short int code = 0; code < Slate::GetElementURICount(); ++code )
    {
        const ElementURI* var = Slate::GetElementURI( code );
        if( NULL == var )
        {
            continue;
        }

        // see if the element matches the search string
        if( var->isUniversal() && var->startsWith( buffer, len ) )
        {
            if( ( int )var->length() == len )
            {
                universalArg_ = ( UniversalURI* )var;
                argType_[argCount_ - 1] = ARG_UNIVERSAL;
                argIndex_[argCount_ - 1] = synArgIndex;
                match = MATCH_UNIQUE;
                break;
            }
            match = MATCH_MANY;
        }
    }
    return match;

}

MatchType ParsedCommand::matchVariable( int synArgIndex, const char *buffer, int len )
{
    MatchType match( MATCH_NONE );
    const ElementURI* uniqueVar( NULL );

    // loop through the slate to search for the search string
    for( unsigned short int code = 0; code < Slate::GetElementURICount(); ++code )
    {
        const ElementURI* var = Slate::GetElementURI( code );
        if( NULL == var )
        {
            continue;
        }

        // see if the element matches the search string
        if( var->startsWith( buffer, len ) )
        {
            if( ( int )var->length() == len )
            {
                uniqueVar = var;
                argType_[argCount_ - 1] = ARG_VARIABLE;
                argIndex_[argCount_ - 1] = synArgIndex;
                match = MATCH_UNIQUE;
                break;
            }
            match = MATCH_MANY;
        }
    }

    // check if this is a scratchpad variable
    if( match == MATCH_NONE
            && ( len > 0 && ( buffer[ 0 ] == '_' || buffer[ 0 ] == '#' ) )
            && ( len < 2 || buffer[ 1 ] == '.' )
            && ( len < 3
                 || ( isalpha( buffer[ 2 ] )
                      && ( buffer [ len - 1 ] == '_' || isalnum( buffer [ len - 1 ] ) ) ) ) )
    {
        match = MATCH_MANY;
        if( len > 2 )
        {
            BinaryDataType binaryType = buffer[ 0 ] == '#' ? MULTIVALUE : NO_TYPE;
            BlobType blobType = buffer[ 0 ] == '#' ? BLOB_FLOAT64LE : NOT_BLOB;
            ElementURI scratchEl( Str::EMPTY_STR, Str( buffer, len ), ElementURI::BASE_ELEMENT, ElementURI::UNSPECIFIED_UNIT, binaryType, blobType );
            scratchPadArg_ = scratchEl;
            uniqueVar = &scratchPadArg_;
            argType_[argCount_ - 1] = ARG_VARIABLE;
            argIndex_[argCount_ - 1] = synArgIndex;
            match = MATCH_UNIQUE;
        }
    }

    variableArg_ = uniqueVar;
    return match;
}

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

    // Are we looking for a command?
    if( argCount_ == 1 )
    {
        command_ = NULL;
        syntax_ = NULL;
        argMatch_[0] = lastArgMatch = matchCommand( commands, command_, -1, buffer, len );
    }

    // Are we figuring out the syntax?
    int argStart = 1;
    if( argCount_ == 2 && NULL != command_ )
    {
        syntax_ = NULL;
        int synCount = command_->getSyntaxCount();

        argType_[1] = ARG_NONE;
        argIndex_[1] = -1;

        for( int iSyn = 0; iSyn < synCount; ++iSyn )
        {
            CommandSyntax* syntax = command_->getSyntax( iSyn );
            if( synCount == 1 )
            {
                // There's only one syntax to choose from so stop looking.
                argStart = 0;
                syntax->setAltSyntax( NULL );
                syntax_ = syntax;
                break;
            }

            if( syntax->getArgCount() < 1 )
            {
                // No args associated with this syntax, should not have argCount_ = 2
                continue;
            }

            CommandArg* synArg = syntax->getArg( 0 );
            argMatch_[1] = matchArg( commands, synArg, 0, buffer, len );
            if( MATCH_NONE != argMatch_[1] )
            {
                if( MATCH_NONE == lastArgMatch )
                {
                    // This syntax is the first possible match
                    syntax->setAltSyntax( NULL );
                    syntax_ = syntax;
                    lastArgMatch = MATCH_UNIQUE;
                }
                else if( lastArgMatch != MATCH_NONE
                         && syntax->getArgCount() > 1
                         && NULL != syntax_
                         && syntax_->getArgCount() > 1
                         && synArg->equals( syntax_->getArg( 0 ) ) )
                {
                    // Another possible match is in the works, put it in a linked list
                    syntax->setAltSyntax( syntax_ );
                    syntax_ = syntax;
                    lastArgMatch = MATCH_MANY;
                }
                else
                {
                    // Multiple syntaxes are possible, but it's too soon to figure which
                    syntax_ = NULL;
                    argType_[argCount_ - 1] = ARG_NONE;
                    argIndex_[argCount_ - 1] = -1;
                    lastArgMatch = MATCH_MANY;
                }
            }
        }
    }

    if( argCount_ > 2 && command_ != NULL && syntax_ != NULL && syntax_->getAltSyntax() != NULL )
    {
        // We've gotten past the 2nd arg, and we still haven't picked a syntax
        CommandSyntax* uniqueSyntax( NULL );
        CommandSyntax* syntax = syntax_;
        lastArgMatch = MATCH_NONE;
        while( NULL != syntax )
        {
            CommandArg* synArg = syntax->getArg( argCount_ - 2 );
            argMatch_[argCount_ - 1] = matchArg( commands, synArg, argCount_ - 2, buffer, len );
            if( MATCH_NONE != argMatch_[argCount_ - 1] )
            {
                if( MATCH_NONE == lastArgMatch )
                {
                    lastArgMatch = MATCH_UNIQUE;
                    uniqueSyntax = syntax;
                }
                else
                {
                    argType_[argCount_ - 1] = ARG_NONE;
                    argIndex_[argCount_ - 1] = -1;
                    lastArgMatch = MATCH_MANY;
                }
            }
            syntax = syntax->getAltSyntax();
        }
        if( MATCH_UNIQUE == lastArgMatch )
        {
            syntax_ = uniqueSyntax;
            syntax_->setAltSyntax( NULL );
        }
    }

    // Now we run with the syntax
    if( command_ != NULL && syntax_ != NULL && syntax_->getAltSyntax() == NULL )
    {
        int synArgOffset = 1;  //TODO: Change to 0?
        int synArgCount = syntax_->getArgCount();
        if( argCount_ >= 2 )
        {
            argStart = argIndex_[argCount_ - 2];
            synArgOffset = argCount_ - 2 - argStart;
        }
        bool lastSkipped( false );
        for( int iSynArg = argStart; iSynArg < synArgCount; ++iSynArg )
        {
            int iArg = iSynArg + synArgOffset;
            if( iArg == argCount_ - 1 )
            {
                argType_[iArg] = ARG_NONE;
                argIndex_[iArg] = -1;

                currentArg_ = syntax_->getArg( iSynArg );
                if( currentArg_->getRequired() == REQUIRED_AFTER_PREVIOUS && lastSkipped )
                {
                    --synArgOffset;
                }
                else
                {
                    lastArgMatch = argMatch_[iArg] = matchArg( commands, currentArg_, iSynArg, buffer, len );
                    if( lastArgMatch == MATCH_NONE
                            && currentArg_->getRequired() == NOT_REQUIRED )
                    {
                        --synArgOffset;
                        lastSkipped = true;
                    }
                    else
                    {
                        lastSkipped = false;
                    }
                }
            }
        }
    }
    return lastArgMatch != MATCH_NONE;
}

/// If the last word in buffer is a valid arg, deal with it.
/// If we need to toss the last character, return false.
void ParsedCommand::implySyntax()
{
    int argCount = argCount_;
    if( argCount_ > 0 && argLen_[ argCount_ - 1 ] == 0 )
    {
        --argCount;
    }
    if( NULL == syntax_ && NULL != command_ && argType_[argCount - 1] != ARG_NONE )
    {
        for( int i = 0; i < command_->getSyntaxCount(); ++i )
        {
            CommandSyntax* syntax = command_->getSyntax( i );
            if( syntax->getRequiredArgSize( 0 ) == 0 )
            {
                syntax->setAltSyntax( NULL );
                syntax_ = syntax;
                break;
            }
        }
    }
}

bool ParsedCommand::syntaxOk()
{
    if( NULL != syntax_ )
    {
        int argCount = argCount_;
        if( argLen_[ argCount_ - 1 ] == 0 )
        {
            --argCount;
        }
        if( argType_[argCount - 1] != ARG_NONE )
        {
            int required = syntax_->getRequiredArgSize( argCount - 1 );
            if( required == argCount - 1
                    || required == argIndex_[argCount - 1] )
            {
                return true;
            }
        }
    }
    return false;
}
