/**
 * @file vtparse.c
 * @brief VTParse
 * @details
 * An implementation of Paul Williams' DEC compatible state machine parser
 * This code is in the public domain.
 * @author Joshua Haberman <joshua@reverberate.org>
 *
 * Modified for Natural Tiny Shell (NT-Shell) : Shinichiro Nakamura
 */

#include "vtparse.h"
#include "vtparse_table.h"
#include "ntlibc.h"

void vtparse_init(vtparse_t *parser, vtparse_callback_t cb)
{
    parser->state                 = VTPARSE_STATE_GROUND;
    parser->intermediate_chars[0] = '\0';
    parser->num_params            = 0;
    parser->ignore_flagged        = 0;
    parser->cb                    = cb;
}

static void do_action(vtparse_t *parser, vtparse_action_t action, char ch)
{
    /* Some actions we handle internally (like parsing parameters), others
     * we hand to our client for processing */

    switch(action) {
        case VTPARSE_ACTION_PRINT:
        case VTPARSE_ACTION_EXECUTE:
        case VTPARSE_ACTION_HOOK:
        case VTPARSE_ACTION_PUT:
        case VTPARSE_ACTION_OSC_START:
        case VTPARSE_ACTION_OSC_PUT:
        case VTPARSE_ACTION_OSC_END:
        case VTPARSE_ACTION_UNHOOK:
        case VTPARSE_ACTION_CSI_DISPATCH:
        case VTPARSE_ACTION_ESC_DISPATCH:
            parser->cb(parser, action, ch);
            break;

        case VTPARSE_ACTION_IGNORE:
            /* do nothing */
            break;

        case VTPARSE_ACTION_COLLECT:
        {
            /* Append the character to the intermediate params */
            int num_intermediate_chars = ntlibc_strlen((char*)parser->intermediate_chars);

            if(num_intermediate_chars + 1 > MAX_INTERMEDIATE_CHARS)
                parser->ignore_flagged = 1;
            else
                parser->intermediate_chars[num_intermediate_chars++] = ch;

            break;
        }

        case VTPARSE_ACTION_PARAM:
        {
            /* process the param character */
            if(ch == ';')
            {
                parser->num_params += 1;
                parser->params[parser->num_params-1] = 0;
            }
            else
            {
                /* the character is a digit */
                int current_param;

                if(parser->num_params == 0)
                {
                    parser->num_params = 1;
                    parser->params[0]  = 0;
                }

                current_param = parser->num_params - 1;
                parser->params[current_param] *= 10;
                parser->params[current_param] += (ch - '0');
            }

            break;
        }

        case VTPARSE_ACTION_CLEAR:
            parser->intermediate_chars[0] = '\0';
            parser->num_params            = 0;
            parser->ignore_flagged        = 0;
            break;

        default:
            // Internal error: Unknown action.
            break;
    }
}

static void do_state_change(vtparse_t *parser, state_change_t change, char ch)
{
    /* A state change is an action and/or a new state to transition to. */

    vtparse_state_t  new_state = STATE(change);
    vtparse_action_t action    = ACTION(change);


    if(new_state)
    {
        /* Perform up to three actions:
         *   1. the exit action of the old state
         *   2. the action associated with the transition
         *   3. the entry actionk of the new action
         */

        vtparse_action_t exit_action = GET_EXIT_ACTIONS(parser->state);
        vtparse_action_t entry_action = GET_ENTRY_ACTIONS(new_state);

        if(exit_action)
            do_action(parser, exit_action, 0);

        if(action)
            do_action(parser, action, ch);

        if(entry_action)
            do_action(parser, entry_action, 0);

        parser->state = new_state;
    }
    else
    {
        do_action(parser, action, ch);
    }
}

void vtparse(vtparse_t *parser, unsigned char *data, int len)
{
    int i;
    for(i = 0; i < len; i++)
    {
        unsigned char ch = data[i];

        /* If a transition is defined from the "anywhere" state, always
         * use that.  Otherwise use the transition from the current state. */

        state_change_t change = GET_STATE_TABLE(VTPARSE_STATE_ANYWHERE, ch);

        if(!change)
            change = GET_STATE_TABLE(parser->state, ch);

        do_state_change(parser, change, data[i]);
    }
}

