#define SCPI_TLST_EX_C
#include "app/scpi/scpi_tlst_ex.h"
#include "app/scpi/scpi_parser.h"
#include <ctype.h> // toupper, islower

// Author: Sychov A., Jan 2021
// Extension module to adopt the Tree-List algorithms to the SCPI module.

//----------------------------------------------------------------
// Refer to:
// [1] IEEE 488.2 Standard, revision IEEE Std 488.2-1987 (1992)
//     "IEEE Standard Codes, Formats, Protocols, and Common Commands for Use With IEEE Std 488.1-1987, IEEE
//      Standard Digital Interface for Programmable Instrumentation"
// [2] SCPI Specification, revision 1999.0
//     "Standard Commands for Programmable Instruments (SCPI), VERSION 1999.0, May 1999"
//----------------------------------------------------------------

#if !TREE_LIST_DEEP_ENABLE || !TREE_LIST_DEEP_PARENT_SEARCH_ENABLE
#error Tree List deep search is required or parent search feature must be enabled
#else
// @scpi_commands_tlds - functional set for @tree_list_search_deep
const sTreeListSearchDeep_FSet_t scpi_commands_tlds = {
   .fnext = treelist_getnext_key,
   .fcmp = treelist_header_compare,
   .fisdef = treelist_is_default_node,
   #if TREE_LIST_RECURSIVE_LIMIT_ENABLE
   .freclim = treelist_recursive_limiter,
   #endif
   .fagree = treelist_result_agree,
};
#endif

// ========================================================================================================
// @scpi_is_default_node
// Checks if the passed string is the default (optional) SCPI command mnemonic
// Is used to determine the default node in the tree-list.
// References:
//  "5.1 Interpreting Command Tables", [2]
//  "7.6.1 <COMMAND PROGPRAM HEADER>", [1]
//  "7.6.1.5 Header Compounding Rules", [1]
// @commandString - SCPI command simple mnemonic;
// Note: compound mnemonics are not supported;
// Note: the passed mnemonic must be valid, the mnemonic will not be validated.
bool scpi_is_default_node( const char * commandString )
{
   my_assert( commandString );

   if( NULL == commandString ) return false;

   bool optional_open = false;
   bool optional_close = false;

   while( '\0' != *commandString )
   {
      if( scpi_isopt_open( *commandString ) )
      {
          if( !optional_open )
          {
             optional_open = true;
             ++commandString;
             continue; // skip character
          }

          return false; // invalid mnemonic: extra optional sign '['
      }

      if( scpi_isopt_close( *commandString ) )
      {
          if( optional_open && !optional_close )
          {
             optional_close = true;
             ++commandString;
             continue; // skip character
          }

          return false; // invalid mnemonic: invalid syntax (optional sign ']')
      }

      ++commandString;
   }

   my_assert( !(optional_open ^ optional_close) ); // must be 'open===close'
  
   // if both @optional_open and @optional_close signs are present, this mnemonic is considered as default
   return ( optional_open && optional_close );
}

// ========================================================================================================
// @scpi_command_header_compare
// Compares the program mnemonic of registered command (@command) with
// ... the program mnemonic of received command header (@src_key).
// Function ignores the tailing question mark of the command header mnemonic.
// References:
//  "7.6 Program Header Functional Elements", [1]
//  "5.1 Interpreting Command Tables", [2]
// @commandString - the registered command header part, null-term string,
// ... attached to the node using @nodekey_ parameter of DECLARE_TREE_LIST_LEAF.
// @comparingToken - the searching command header part, a pair of (@shead, @stail)
// ... pointers grouped into the @sStrToken_t.
// Note: the @sStrToken_t structure referred by @src_key shall be writable.
// Note: @command can points to the part of the original command string.
// Returns: The function returns true in case the part specified by @src_key 
// ... matchs to the part of the command specified by @command and nearest  
// ... mnemonic separator (colon).
bool scpi_command_header_compare( const char * commandString,
                                  const sStrToken_t * comparingToken )
{
     bool result = true;  // forward set, true by default.
     bool longform = false;
    
     const char * shead = comparingToken->shead;
     const char * stail = comparingToken->stail;

     my_assert( shead );
     my_assert( stail );     
     my_assert( commandString );

     if( scpi_isnl( *(stail-1) )) --stail;
     if( scpi_queryind( *(stail-1) )) --stail;
     
     // skip white characters before comparing
     while( scpi_iswhite( *shead ) )
          shead++;

     if( shead >= stail )
     {
         return false; // invalid mnemonic
     }

     // Optional mnemonic indicators (e.g., "[:SYSTem]:ERRor"):
     bool optional_open = false;
     bool optional_close = false;

     // Until the end of the searching mnemonic is reached:
     for( ;shead != stail; ++commandString )
     {
         if( scpi_isopt_open( *commandString ) )
         {
             if( !optional_open )
             {
                optional_open = true;
                continue; // skip character
             }

             return false; // error: extra optional sign '['
         }

         if( scpi_isopt_close( *commandString ) )
         {
             if( optional_open && !optional_close )
             {
                optional_close = true;
                continue; // skip character
             }

             return false; // error: invalid syntax (optional sign ']')
         }

         if( '\0' == *commandString ) 
         {
             return false;  // end of source mnemonic when @shead != @stail
         }

         if( toupper(*shead) != toupper(*commandString) )
         {
             return false;   // case insensetive characters mismatch             
         } 

         if( islower(*commandString) )
         {
             longform = true; // long form found while @shead != @stail             
         }

         ++shead;
     }

     // check if long form of mnemonic is found:
     if( longform )
     {
         // the searching mnemonic shall have the same length as
         // ... long form of source mnemonic.

         // Check if the end of long form reached: NULL-char or ']' is in the end of @commandString 
         result = ('\0' == *commandString || scpi_isopt_close( *commandString ) );
     }
     else
     {
         // the searching mnemonic shall have the same length as
         // ... short form of source mnemonic.
         
         // Note: @commandString has been incremented at least once.

         // Check if the end of short form reached, check the character's
         // ... case at @commandString position and @commandString-1 position, the
         // ... register of characters must differ:
         // (the same character's register -> error)
         result = ( islower(*(commandString-1)) != islower(*commandString) || '\0' == *commandString );
     }

     // if short form of command is received, it is required
     // to validate the passed registered command til the end.
     // Fix: if long form is received, it is requried to process the @commandString til the end
     if( optional_open && !optional_close )
     {
         while( '\0' != *commandString )
         {
            if( scpi_isopt_close( *commandString ) )
            {
                if( optional_close )
                {
                   return false; // error: invalid syntax (optional sign ']')
                }

                optional_close = true;
            }
            ++commandString;
         }
     }

     my_assert( !(optional_open ^ optional_close) ); // must be 'open===close'

     return result;
}

#if TREE_LIST_RECURSIVE_LIMIT_ENABLE
// @scpi_search_recursive_limiter   
// Limits the depth of recursive calls of @tree_list_search_deep
// Is used to prevent stack overflow.
// @dir - direction: true - enter recursive call, false - leave call;
// @ctx - mandatory user dependent context.
// Returns, if @dir is 'true':
// - true: recursive call is possible;
// - false: recursive call is impossible;
// Returns, if @dir is 'false': does not matter;
bool scpi_search_recursive_limiter( bool dir, void * _ctx )
{
    my_assert( _ctx );

    sTreeListSearchCtx_t * ctx = (sTreeListSearchCtx_t*)_ctx;
    
    if( dir )
      if( ctx->call_depth >= SCPI_MAX_CMD_SEARCH_DEPTH )
      {
         my_assert( ctx->call_depth < SCPI_MAX_CMD_SEARCH_DEPTH );

         return false; // limit is reached
      }

    return true;
}
#endif

// @scpi_command_getnext_prepare
// Prepare context for @scpi_command_getnext_key before calling @tree_list_search_deep
// @ctx - search key context, is used to iterate upon the command mnemonic chain (compound command)
// @cmd_shead - compound SCPI command mnemonic head
// @cmd_stail - compound SCPI command mnemonic tail
void scpi_command_getnext_prepare( sScpiCmdGetNextKey_t * ctx, const char * cmd_shead, const char * cmd_stail )
{
   my_assert( ctx );
   my_assert( cmd_shead );
   my_assert( cmd_stail );
   my_assert( cmd_shead <= cmd_stail );

   ctx->full_token.shead = cmd_shead;
   ctx->full_token.stail = cmd_stail;

   ctx->found_token.shead = NULL;
   ctx->found_token.stail = NULL;
}

// @scpi_command_search_prepare
// Prepare the SCPI Command search context before calling @tree_list_search_deep
// @ctx - search context, is used in call of @tree_list_search_deep
// @cmd_shead - compound SCPI command mnemonic head
// @cmd_stail - compound SCPI command mnemonic tail
void scpi_command_search_prepare( sTreeListSearchCtx_t * ctx, const char * cmd_shead, const char * cmd_stail )
{
   my_assert( ctx );

   scpi_command_getnext_prepare( &ctx->cmdKey, cmd_shead, cmd_stail );

   #if TREE_LIST_RECURSIVE_LIMIT_ENABLE
   ctx->call_depth = 0;
   #endif
}

// @scpi_command_getnext_key
// Get next command mnemonic token in the chain specified in @ctx
// @ctx - rotine context;
// Returns:
//  NULL - no key found / end of the command line;
//  NON NULL: string token containing command mnemonic (key)
const sStrToken_t* scpi_command_getnext_key( sScpiCmdGetNextKey_t * ctx )
{
   my_assert( ctx->full_token.shead );
   my_assert( ctx->full_token.stail );
   my_assert( ctx->full_token.shead  <= ctx->full_token.stail );
   my_assert( NULL == ctx->found_token.shead || ctx->found_token.shead >= ctx->full_token.shead );
   my_assert( NULL == ctx->found_token.shead || ctx->found_token.shead <= ctx->full_token.stail );
   my_assert( NULL == ctx->found_token.stail || ctx->found_token.stail >= ctx->full_token.shead );
   my_assert( NULL == ctx->found_token.stail || ctx->found_token.stail <= ctx->full_token.stail );

   if( NULL == ctx->found_token.shead )
   {
       ctx->found_token.shead = ctx->full_token.shead;
       ctx->found_token.stail = scpi_cmdsep( ctx->full_token.shead );

       my_assert( ctx->found_token.stail ); // @tail never will be NULL

       return &ctx->found_token;
   }

   if(  ctx->found_token.shead >= ctx->full_token.stail
     && ctx->found_token.stail >= ctx->full_token.stail )
   {
       return NULL; // end-of-command line
   }

   // all the command mnemonics are separated with SCPI_CHARACTER_COMPOUNDCOMMAND_SEPARATOR
   if( scpi_iscmdsep( *ctx->found_token.stail ) )
   {
       ctx->found_token.shead = (ctx->found_token.stail + 1); // skip separator
       ctx->found_token.stail = scpi_cmdsep( ctx->found_token.shead );

       my_assert( ctx->found_token.stail ); // @tail never will be NULL

       return &ctx->found_token;
   }

   return NULL; // invalid command chain
}

// @scpi_command_command_found
// SCPI search command result notifee.
void scpi_command_command_found( sTreeListSearchCtx_t * ctx, const sTreeList_t * found, const sTreeList_t * parent )
{
   ctx->parent = parent;  // pass the parent node pointer to the caller (@scpiSearchCommandHandler)
}
