/*
 * Copyright (c) 2019 - 2021 Stefan Strobel <stefan.strobel@shimatta.net>
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at https://mozilla.org/MPL/2.0/.
 */

/**
 * @file    shellmatta.c
 * @brief   Main implementation of the Shellmatta terminal implementation
 * @author  Stefan Strobel <stefan.strobel@shimatta.net>
 */

/**
 * @addtogroup shellmatta_private
 * @{
 */

#include "shellmatta.h"
#include "shellmatta_autocomplete.h"
#include "shellmatta_history.h"
#include "shellmatta_utils.h"
#include "shellmatta_escape.h"
#include "shellmatta_opt.h"
#include "shellmatta_transport.h"
#include <stddef.h>
#include <string.h>
#include <stdarg.h>
#include <stdio.h>

/**
 * @}
 * @addtogroup shellmatta_api
 * @{
 */

/**
 * @brief           initialize the shellmatta terminal and provide all callbacks
 * @param[in,out]   inst                pointer to a shellmatta instance
 * @param[out]      handle              pointer to shellmatta handle -
 *                                      has to be used for all furterh api calls
 * @param[in]       buffer              pointer to the input buffer to use
 * @param[in]       bufferSize          size of the provided input buffer
 * @param[in]       historyBuffer       pointer to the history buffer to use
 *                                      NULL in case of no history buffer
 * @param[in]       historyBufferSize   size of the history buffer
 *                                      0 in case of no history buffer
 * @param[in]       prompt              pointer to prompt string - is printed
 *                                      after each command
 * @param[in]       cmdList             constant command list if no dynamic
 *                                      adding of commands is desired
 * @param[in]       writeFct            function pointer to output function
 */
shellmatta_retCode_t shellmatta_doInit(
        shellmatta_instance_t  *inst,
        shellmatta_handle_t    *handle,
        char                   *buffer,
        uint32_t                bufferSize,
        char                   *historyBuffer,
        uint32_t                historyBufferSize,
        const char             *prompt,
        const shellmatta_cmd_t *cmdList,
        shellmatta_write_t     writeFct)
{
    /** -# check parameters for plausibility  */
    if(     (NULL != inst)
        &&  (NULL != handle)
        &&  (NULL != buffer)
        &&  (0u   != bufferSize)
        &&  (NULL != prompt)
        &&  (NULL != writeFct)
        &&  ((NULL != historyBuffer) || (0u == historyBufferSize)))
    {
        /** -# copy all provided buffers into the shellmatta instance */
        inst->buffer                = buffer;
        inst->bufferSize            = bufferSize;
        inst->inputCount            = 0u;
        inst->byteCounter           = 0u;
        inst->lastNewlineIdx        = 0u;
        inst->cursor                = 0u;
        inst->stdinIdx              = 0u;
        inst->stdinLength           = 0u;
        inst->historyBuffer         = historyBuffer;
        inst->historyBufferSize     = historyBufferSize;
        inst->historyStart          = 0u;
        inst->historyEnd            = 0u;
        inst->historyRead           = 0u;
        inst->historyReadUp         = true;
        inst->write                 = writeFct;
        inst->prompt                = prompt;
        inst->echoEnabled           = true;
        inst->dirty                 = false;
        inst->tabCounter            = 0u;
        inst->escapeCounter         = 0u;
        inst->hereStartIdx          = 0u;
        inst->hereDelimiterIdx      = 0u;
        inst->hereLength            = 0u;
        inst->delimiter             = '\r';
        inst->mode                  = SHELLMATTA_MODE_INSERT;
        inst->cmdList               = &(inst->helpCmd);
        inst->continuousCmd         = NULL;
        inst->busyCmd               = NULL;
        inst->cmdListIsConst        = false;
        shellmatta_opt_init(inst, 0u);

        /** -# copy the help command structure to this instance */
        memcpy(&(inst->helpCmd), &helpCmd, sizeof(shellmatta_cmd_t));

        if(NULL != cmdList)
        {
            inst->helpCmd.next = (shellmatta_cmd_t *) cmdList;
            inst->cmdListIsConst = true;
        }

        inst->magic = SHELLMATTA_MAGIC;
        *handle     = (shellmatta_handle_t)inst;

        /** -# print the first prompt */
        utils_terminateInput(inst);

        /* init transport layer */
        shellmatta_init_transport_inst();
        transportLayerInst.originalWrite = inst->write;
    }

    return SHELLMATTA_OK;
}

/**
 * @brief       resets the whole shellmatta instance
 * @param[in]   handle      shellmatta instance handle
 * @param[in]   printPrompt print a new command prompt
 * 
 * This function can be used e.g. when working with connection based interfaces (e.g. sockets) to clear
 * the shell from old content when a new connection is opened.
 * It resets all internal states - the buffers are left as they are - they will be overwritten.
 * The history buffer is deleted as well.
 */
shellmatta_retCode_t shellmatta_resetShell( shellmatta_handle_t handle, bool printPrompt)
{
    shellmatta_instance_t *inst = (shellmatta_instance_t *)handle;
    shellmatta_retCode_t ret = SHELLMATTA_OK;

    /** -# check if the instance is plausible */
    if(     (NULL != handle)
        &&  (SHELLMATTA_MAGIC == inst->magic))
    {
        inst->inputCount            = 0u;
        inst->byteCounter           = 0u;
        inst->continuousCmd         = NULL;
        inst->busyCmd               = NULL;
        inst->lastNewlineIdx        = 0u;
        inst->cursor                = 0u;
        inst->stdinIdx              = 0u;
        inst->stdinLength           = 0u;
        inst->historyStart          = 0u;
        inst->historyEnd            = 0u;
        inst->historyRead           = 0u;
        inst->historyReadUp         = true;
        inst->dirty                 = false;
        inst->tabCounter            = 0u;
        inst->escapeCounter         = 0u;
        inst->hereStartIdx          = 0u;
        inst->hereDelimiterIdx      = 0u;
        inst->hereLength            = 0u;
        shellmatta_opt_init(inst, 0u);

        if(true == printPrompt)
        {
            /** -# print a prompt if requested */
            utils_terminateInput(inst);
        }
    }
    else
    {
        ret = SHELLMATTA_USE_FAULT;
    }
    
    return ret;
}

/**
 * @brief       adds a command to the command list alphabetically ordered
 * @param[in]   handle  shellmatta instance handle
 * @param[in]   cmd     pointer to the command to add type #shellmatta_cmd_t
 * @return      errorcode   #SHELLMATTA_OK
 *                          #SHELLMATTA_USE_FAULT (param err)
 *                          SHELLMATTA_DUPLICATE
 * 
 * The cmd name is mandatory, the rest of the command parameters (alias, helpText and usageText) are optional
 * and can be set to NULL if not used.
 */
shellmatta_retCode_t shellmatta_addCmd(shellmatta_handle_t handle, shellmatta_cmd_t *cmd)
{
    shellmatta_instance_t   *inst       = (shellmatta_instance_t*)handle;
    shellmatta_cmd_t       *tempCmd;
    shellmatta_cmd_t       **prevCmd;
    bool                    cmdPlaced   = false;
    shellmatta_retCode_t    ret         = SHELLMATTA_OK;
    int                     cmdDiff;
    int                     aliasDiff;

    /** -# check parameters for plausibility  */
    if(     (NULL               != inst)
        &&  (SHELLMATTA_MAGIC   == inst->magic)
        &&  (false              == inst->cmdListIsConst)
        &&  (NULL               != cmd)
        &&  (NULL               != cmd->cmd))
    {
        tempCmd = inst->cmdList;
        prevCmd = &inst->cmdList;
        /** -# register first command as list entry */
        if (NULL == tempCmd)
        {
            inst->cmdList = cmd;
            cmd->next = NULL;
        }
        /** -# append the new command sorted */
        else
        {
            while ((false == cmdPlaced) && (SHELLMATTA_OK == ret))
            {
                cmdDiff = strcmp(tempCmd->cmd, cmd->cmd);
                if(     (NULL != cmd->cmdAlias)
                    &&  (NULL != tempCmd->cmdAlias))
                {
                    aliasDiff = strcmp(tempCmd->cmdAlias, cmd->cmdAlias);
                }
                else
                {
                    aliasDiff = 1;
                }

                /** -# check for a duplicate command */
                if((0u == cmdDiff) || (0u == aliasDiff))
                {
                    ret = SHELLMATTA_DUPLICATE;
                }
                else if(cmdDiff > 0)
                {
                    cmd->next   = tempCmd;
                    *prevCmd    = cmd;
                    cmdPlaced   = true;
                }
                else if(NULL == tempCmd->next)
                {
                    tempCmd->next = cmd;
                    cmd->next = NULL;
                    cmdPlaced = true;
                }
                else
                {
                    /* nothing to do */
                }
                prevCmd = &(tempCmd->next);
                tempCmd = tempCmd->next;
            }
        }
    }
    else
    {
        ret = SHELLMATTA_USE_FAULT;
    }

    return ret;
}

/**
 * @brief       removes a command from the command list
 * @param[in]   handle  shellmatta instance handle
 * @param[in]   cmd     pointer to the command to remove type #shellmatta_cmd_t
 * @return      errorcode   #SHELLMATTA_OK
 *                          #SHELLMATTA_USE_FAULT (param err)
 */
shellmatta_retCode_t shellmatta_removeCmd(shellmatta_handle_t handle, shellmatta_cmd_t *cmd)
{
    shellmatta_instance_t   *inst       = (shellmatta_instance_t*)handle;
    shellmatta_cmd_t       *prevCmd;
    shellmatta_cmd_t       *tempCmd;
    shellmatta_retCode_t    ret         = SHELLMATTA_OK;

    /** -# check parameters for plausibility  */
    if(     (NULL               != inst)
        &&  (SHELLMATTA_MAGIC   == inst->magic)
        &&  (false              == inst->cmdListIsConst)
        &&  (NULL               != cmd)
        &&  (NULL               != cmd->cmd))
    {
        tempCmd     = inst->cmdList;
        prevCmd     = NULL;

        /** -# loop through command list */
        while(NULL != tempCmd)
        {
            /** -# compare command strings to find the command to delete */
            if     (0                    == strcmp(tempCmd->cmd, cmd->cmd)
                && (strlen(tempCmd->cmd) == strlen(cmd->cmd)))
            {
                /** -# first command removed */
                if(NULL == prevCmd)
                {
                    inst->cmdList = tempCmd->next;
                }
                /** -# last command removed */
                else if(NULL == tempCmd->next)
                {
                    prevCmd->next = NULL;
                }
                /** -# command removed from the middle of the list */
                else
                {
                    prevCmd->next = tempCmd->next;
                }
                
                break;
            }

            prevCmd = tempCmd;
            tempCmd = tempCmd->next;
        }
    }
    else
    {
        ret = SHELLMATTA_USE_FAULT;
    }

    return ret;
}

/**
 * @brief       changes configuration of a shellmatta instance
 * @param[in]   handle          shellmatta instance handle
 * @param[in]   mode            insert mode of the shellmatta type #shellmatta_mode_t
 * @param[in]   echoEnabled     true: echo received chars to the output
 * @param[in]   delimiter       delimiter used to detect the end of a cmd (default "\r")
 * @return      errorcode   #SHELLMATTA_OK
 *                          #SHELLMATTA_USE_FAULT (param err)
 */
shellmatta_retCode_t shellmatta_configure(  shellmatta_handle_t handle,
                                            shellmatta_mode_t   mode,
                                            bool                echoEnabled,
                                            char                delimiter)
{
    shellmatta_instance_t   *inst   = (shellmatta_instance_t*)handle;
    shellmatta_retCode_t    ret     = SHELLMATTA_OK;

    /** -# check parameters for plausibility  */
    if(     (NULL               != inst)
        &&  (SHELLMATTA_MAGIC   == inst->magic)
        &&  ((SHELLMATTA_MODE_INSERT == mode) || (SHELLMATTA_MODE_OVERWRITE == mode)))
    {
        inst->mode          = mode;
        inst->echoEnabled   = echoEnabled;
        inst->delimiter     = delimiter;
    }
    else
    {
        ret = SHELLMATTA_USE_FAULT;
    }

    return ret;
}

/**
 * @brief       processes the passed amount of data
 * @param[in]   handle  shellmatta instance handle
 * @param[in]   data    pointer to input data to process
 * @param[in]   size    length of input data to process
 * @return      errorcode   #SHELLMATTA_OK
 *                          #SHELLMATTA_USE_FAULT
 */
shellmatta_retCode_t shellmatta_processData(shellmatta_handle_t     handle,
                                            char                    *data,
                                            uint32_t                 size)
{
    shellmatta_cmd_t        *cmd;
    uint8_t                 cmdExecuted = 0u;
    uint32_t                cmdLen;
    char                    *tempString;

    shellmatta_retCode_t    ret = SHELLMATTA_OK;
    shellmatta_retCode_t    cmdRet;
    shellmatta_instance_t   *inst = (shellmatta_instance_t*)handle;

    /** -# check parameters for plausibility  */
    if(     (NULL               != inst)
        &&  (SHELLMATTA_MAGIC   == inst->magic))
    {
        if (    (transportLayerInst.state != STATE_PROCESS_PAYLOAD)
            &&  (transportLayerInst.state != STATE_MANUAL_INPUT))
        {
            // TODO: Move this into shellmatta_transport.c
            /* use headerCounter to watch how many header fields have been worked on */
            uint8_t headerCounter = 0u;
            while ( (size > headerCounter) 
                ||  (   (true == transportLayerInst.mandatory)
                    &&  (size > headerCounter))
                ||  (true == transportLayerInst.continueStep))
            {
                switch (transportLayerInst.state)
                {
                case STATE_GET_SOH:
                    /* wait for SOH or go to payload */
                    break;

                case STATE_GET_PROTOCOL_VERSION:
                    protocolVersion = data[headerCounter];
                    break;

                case STATE_GET_PACKET_TYPE:
                    packetType = data[headerCounter];
                    break;

                case STATE_GET_PAYLOAD_LENGTH:
                    payloadLength = (uint8_t)data[headerCounter];
                    break;

                case STATE_GET_SOURCE:
                    source = data[headerCounter];
                    break;

                case STATE_GET_DESTINATION:
                    destination = data[headerCounter];
                    break;
                
                case STATE_GET_H2S_SEQUENCE_CNT:
                    packetSequenceCounter_h2s = data[headerCounter];
                    break;
                
                case STATE_GET_S2H_SEQUENCE_CNT:
                    packetSequenceCounter_s2h = data[headerCounter];
                    break;

                case STATE_GET_PAYLOAD:
                    if (0u == payloadLength)
                    {
                        transportLayerInst.continueStep = true;
                    }
                    payloadBuffer[payloadCounter++] = data[headerCounter];
                    break;

                case STATE_GET_CRC:
                    transportLayerInst.continueStep = false;
                    crc32 |= (uint8_t)data[headerCounter] << (SHELLMATTA_LENGTH_CRC - 1 - crcCounter++) * 8u;
                    break;
                
                default:
                    break;
                }
                /* handling of transport layer fsm */
                ret = shellmatta_handle_transport_fsm(data);
                
                /* crc error handling */
                if (SHELLMATTA_ERROR == ret)
                {
                    shellmatta_reset_transport();
                    utils_writeEcho(handle, "crc error\r\n", 11);
                    utils_terminateInput(inst);
                    return SHELLMATTA_OK;
                }

                headerCounter++;

                /* processes payload by forwarding it recursively without transport layer to processData() */
                if (transportLayerInst.state == STATE_PROCESS_PAYLOAD)
                {
                    /* replace inst->write function pointer with transport layer write function */
                    transportLayerInst.originalWrite = inst->write;
                    inst->write = shellmatta_write_transport;
                    
                    /* recursive call with complete payload */
                    shellmatta_processData(handle, payloadBuffer, payloadLength);
                    
                    /* set back inst->write function pointer to original */
                    inst->write = transportLayerInst.originalWrite;
                    shellmatta_handle_transport_fsm(data);
                    return SHELLMATTA_OK;
                }
            }
        }

        if (    (transportLayerInst.active)
            &&  (transportLayerInst.state != STATE_PROCESS_PAYLOAD)
            &&  (transportLayerInst.state != STATE_MANUAL_INPUT))
        {
            return SHELLMATTA_OK;
        }

        /** -# in busy mode - keep calling this command */
        if(NULL != inst->busyCmd)
        {
            /** -# just call the function until it is not busy anymore */
            (void)shellmatta_opt_reInit(inst);
            ret = inst->busyCmd->cmdFct(handle, inst->buffer, inst->inputCount);

            if(SHELLMATTA_BUSY == ret)
            {
                /** -# do nothing - still busy */
            }
            else if(SHELLMATTA_CONTINUE == ret)
            {
                inst->continuousCmd = inst->busyCmd;
                inst->busyCmd       = NULL;
            }
            else
            {
                utils_terminateInput(inst);
            }
        }
        /** -# call continuous function even if there is no data */
        else if((0u == size) && (NULL != inst->continuousCmd))
        {
            /** -# just call the function without any new data */
            inst->stdinLength               = 0u;
            inst->buffer[inst->stdinIdx]    = '\0';
            (void)shellmatta_opt_reInit(inst);
            ret = inst->continuousCmd->cmdFct(handle, inst->buffer, inst->inputCount);

            if(SHELLMATTA_CONTINUE == ret)
            {
                /** -# do nothing just continue */
            }
            else if(SHELLMATTA_BUSY == ret)
            {
                inst->busyCmd       = inst->continuousCmd;
                inst->continuousCmd = NULL;
            }
            else
            {
                utils_terminateInput(inst);
            }
        }
        else
        {
            /* nothing to do here - continue parsing the command */
        }

        /** -# process byte wise */
        for (; (inst->byteCounter < size) && (NULL == inst->busyCmd); inst->byteCounter++)
        {
            /** -# in continuous mode - pass data directly to the command */
            if(NULL != inst->continuousCmd)
            {
                /** -# copy data and call command function */
                inst->buffer[inst->stdinIdx]        = data[inst->byteCounter];
                inst->buffer[inst->stdinIdx + 1u]   = '\0';
                inst->stdinLength                   = 1u;
                (void)shellmatta_opt_reInit(inst);
                ret = inst->continuousCmd->cmdFct(handle, inst->buffer, inst->inputCount);
                
                /** -# check if continuous mode is canceled or interrupted by busy mode */
                if(SHELLMATTA_BUSY == ret)
                {
                    inst->busyCmd 	= inst->continuousCmd;
                    inst->continuousCmd = NULL;
                }
                else if(('\x03' == data[inst->byteCounter]))
                {
                    /** -# cancel continue session */
                    utils_terminateInput(inst);
                    ret = SHELLMATTA_OK;
                }
                else if(SHELLMATTA_CONTINUE == ret)
                {
                    /** -# do nothing - continue */
                }
                else
                {
                    utils_terminateInput(inst);
                }
            }
            /** -# handle escape sequences */
            else if(inst->escapeCounter != 0u)
            {
                escape_handleSequence(inst, data[inst->byteCounter]);
            }
            /** -# handle delimiter as start of processing the command */
            else if (inst->delimiter == data[inst->byteCounter])
            {
                if(0u == inst->hereLength)
                {
                    /**
                      *  @dot
                      *  digraph heredocParser {
                      *      start -> wait [ label="<< in first line - store delimiter" ];
                      *      wait -> wait [ label="delimiter not detected" ];
                      *      wait -> end [ label="delimiter found - remove all heredoc stuff and send the input to the command" ];
                      *  }
                      *  @enddot */

                    /** -# check for heredoc - add string delimiter to stop strstr from searching too far */
                    inst->buffer[inst->inputCount] = '\0';
                    tempString = strstr(inst->buffer, "<<");
                    if(NULL != tempString)
                    {
                        /*' -# check if length of heredoc delimiter is valid */
                        if(inst->inputCount > ((uint32_t)(tempString - inst->buffer) + 2u))
                        {
                            inst->hereStartIdx      = (uint32_t)(tempString - inst->buffer);
                            inst->hereDelimiterIdx  = inst->hereStartIdx + 2u;
                            while((inst->hereDelimiterIdx < inst->inputCount)
                                    && (    ('\0' == inst->buffer[inst->hereDelimiterIdx])
                                        ||  (' '  == inst->buffer[inst->hereDelimiterIdx])))
                            {
                                inst->hereDelimiterIdx ++;
                            }

                            inst->hereLength = inst->inputCount - inst->hereDelimiterIdx;

                            inst->dirty = true;
                            utils_insertChars(inst, &data[inst->byteCounter], 1u);
                            inst->lastNewlineIdx = inst->inputCount;
                        }
                        else
                        {
                            inst->hereLength    = 0u;

                            /** -# store the current command and reset the history buffer */
                            inst->dirty = true;
                            history_storeCmd(inst);
                            history_reset(inst);
                        }
                    }
                    else
                    {
                        /** -# store the current command and reset the history buffer */
                        inst->dirty = true;
                        history_storeCmd(inst);
                        history_reset(inst);
                    }
                }
                else
                {
                    tempString  = &inst->buffer[inst->lastNewlineIdx];
                    cmdLen      = inst->inputCount - inst->lastNewlineIdx;

                    /** -# skip newline characters before comparison */
                    while(('\n' == *tempString) || ('\r' == *tempString))
                    {
                        tempString ++;
                        cmdLen --;
                    }

                    if(     (inst->hereLength == cmdLen)
                        &&  (0  == strncmp( &inst->buffer[inst->hereDelimiterIdx],
                                            tempString,
                                            inst->hereLength)))
                    {
                        /** -# store the current command and reset the history buffer */
                        inst->dirty = true;
                        history_storeCmd(inst);
                        history_reset(inst);

                        /** -# process heredoc as stdin like input  */
                        /** -# find start of heredoc data           */
                        inst->stdinIdx = inst->hereDelimiterIdx + inst->hereLength;
                        while(      ('\n' == inst->buffer[inst->stdinIdx])
                                ||  ('\r' == inst->buffer[inst->stdinIdx]))
                        {
                            inst->stdinIdx ++;
                        }
                        /** -# calculate length and terminate stdin string */
                        if(inst->stdinIdx < inst->lastNewlineIdx)
                        {
                            inst->stdinLength = inst->lastNewlineIdx - inst->stdinIdx;
                            inst->buffer[inst->lastNewlineIdx] = '\0';
                        }
                        else
                        {
                            inst->stdinLength = 0u;
                        }

                        /** -# calculate length and terminate argument string */
                        inst->inputCount = inst->hereStartIdx;
                        inst->buffer[inst->hereStartIdx] = '\0';

                        /** -# terminate heredoc session */
                        inst->hereLength = 0u;
                    }
                    else
                    {
                        /** -# the party goes on - just print the delimiter and store the position */
                        inst->lastNewlineIdx = inst->inputCount;
                        utils_insertChars(inst, &data[inst->byteCounter], 1u);
                    }
                }

                if(0u == inst->hereLength)
                {
                    cmd = inst->cmdList;

                    /** -# determine the cmd len (chars until first space or \0 is found */
                    cmdLen = 0u;
                    while(      (cmdLen <   inst->inputCount)
                            &&  (' '    !=  inst->buffer[cmdLen])
                            &&  ('\r'   !=  inst->buffer[cmdLen])
                            &&  ('\n'   !=  inst->buffer[cmdLen])
                            &&  ('\0'   !=  inst->buffer[cmdLen]))
                    {
                        cmdLen ++;
                    }

                    /** -# search for a matching command */
                    while (NULL != cmd)
                    {
                        /** -# compare command and alias string and length */
                        if (    ((cmdLen    == strlen(cmd->cmd))
                                && (0       == strncmp(inst->buffer, cmd->cmd, cmdLen)))
                            || ((NULL != cmd->cmdAlias)
                                && (cmdLen  == strlen(cmd->cmdAlias))
                                && (0       == strncmp(inst->buffer, cmd->cmdAlias, cmdLen))))
                        {
                            utils_writeEcho(inst, "\r\n", 2u);
                            shellmatta_opt_init(inst, cmdLen + 1u);
                            cmdExecuted = 1u;
                            cmdRet = cmd->cmdFct(handle, inst->buffer, inst->inputCount);

                            switch(cmdRet)
                            {
                                case SHELLMATTA_CONTINUE:
                                    /** -# initialize stdin buffer and continuous cmd */
                                    inst->stdinIdx      = inst->inputCount + 1u;
                                    inst->stdinLength   = 0u;
                                    inst->continuousCmd = cmd;
                                    ret                 = cmdRet;
                                    break;
                                
                                case SHELLMATTA_BUSY:
                                    inst->busyCmd   = cmd;
                                    ret             = cmdRet;
                                    break;

                                default:
                                    /* nothing to do - everything ok */
                                    break;
                            }
                            cmd = NULL;
                        }
                        else
                        {
                            cmd = cmd->next;
                        }
                    }

                    if ((0u == cmdExecuted) && (inst->inputCount > 0))
                    {
                        inst->write("\r\nCommand: ", 11u);
                        inst->write(inst->buffer, inst->inputCount);
                        inst->write(" not found", 10u);
                    }

                    /** -# terminate this session if no continuous mode is requested */
                    if(     (NULL == inst->continuousCmd)
                        &&  (NULL == inst->busyCmd))
                    {
                        utils_terminateInput(inst);
                    }
                }
            }
            /** -# ignore newline as first character (to be compatible to
             * terminals sending newline after return) */
            else if((0u == inst->inputCount) && ('\n' == data[inst->byteCounter]))
            {
                /* do nothing */
            }
            /** -# check for tabulator key - auto complete */
            else if('\t' == data[inst->byteCounter])
            {
                inst->dirty = true;
                autocomplete_run(inst);
            }
            /** -# check for cancel -
             *      terminate current input and print prompt again */
            else if('\x03' == data[inst->byteCounter])
            {
                inst->dirty = false;
                history_reset(inst);
                utils_terminateInput(inst);
            }
            /** -# check for backspace */
            else if(    ('\b'   == data[inst->byteCounter])
                    ||  ('\x7f' == data[inst->byteCounter]))
            {
                inst->dirty = true;
                utils_removeChars(inst, 1u, true);
            }
            /** -# check for start of escape sequence */
            else if('\x1b' == data[inst->byteCounter])
            {
                inst->escapeCounter = 1u;
            }
            else
            {
                inst->dirty = true;
                utils_insertChars(inst, &data[inst->byteCounter], 1u);
            }

            /** -# reset tab counter on not a tab */
            if ('\t' != data[inst->byteCounter])
            {
                inst->tabCounter = 0u;
            }
        }

        /** -# initialize the byte buffer if processing of the input is finished */
        if(ret != SHELLMATTA_BUSY)
        {
            inst->byteCounter = 0u;
        }
    }
    else
    {
        ret = SHELLMATTA_USE_FAULT;
    }

    /* if manual input happened, reset transport layer fsm back to initial state */
    if (transportLayerInst.state == STATE_MANUAL_INPUT)
    {
        transportLayerInst.state = STATE_GET_SOH;
    }

    return ret;
}

/**
 * @brief       simple write function to write a datastream to the output of an instance
 * @param[in]   handle  shellmatta instance handle
 * @param[in]   data    data to be written
 * @param[in]   length  amount of data to be written
 * @return
 */
shellmatta_retCode_t shellmatta_write(  shellmatta_handle_t handle,
                                        char                *data,
                                        uint32_t            length)
{
    shellmatta_retCode_t    ret = SHELLMATTA_USE_FAULT;
    shellmatta_instance_t   *inst = (shellmatta_instance_t*)handle;

    /** -# check parameters for plausibility  */
    if(     (NULL               != inst)
        &&  (SHELLMATTA_MAGIC   == inst->magic))
    {
        /** -# pass the data to the write function of the instance  */
        ret = inst->write(data, length);
    }

    return ret;
}

/**
 * @brief       reads the stdin like buffer
 * @param[in]   handle  shellmatta instance handle
 * @param[out]  data    stdin data or NULL
 * @param[out]  length  size of the stdin data
 * @return
 */
shellmatta_retCode_t shellmatta_read(   shellmatta_handle_t handle,
                                        char                **data, 
                                        uint32_t            *length)
{
    shellmatta_retCode_t    ret = SHELLMATTA_USE_FAULT;
    shellmatta_instance_t   *inst = (shellmatta_instance_t*)handle;

    /** -# check parameters for plausibility  */
    if(     (NULL               != inst)
        &&  (SHELLMATTA_MAGIC   == inst->magic)
        &&  (NULL               != data)
        &&  (NULL               != length))
    {
        /** -# return a pointer to the data or NULL */
        if(0u == inst->stdinLength)
        {
            *data = NULL;
        }
        else
        {
            *data = &(inst->buffer[inst->stdinIdx]);
        }
        *length = inst->stdinLength;
    }

    return ret;
}

#ifndef SHELLMATTA_STRIP_PRINTF

/**
 * @brief       printf like function to print output to the instances output
 * @param[in]   handle  shellmatta instance handle
 * @param[in]   fmt     format string and parameters
 * @return      errorcode   #SHELLMATTA_OK
 *                          #SHELLMATTA_USE_FAULT   (no valid instance)
 *                          #SHELLMATTA_ERROR       (buffer overflow or format err)
 */
shellmatta_retCode_t shellmatta_printf( shellmatta_handle_t handle,
                                        const char          *fmt,
                                        ...)
{
    char                    outputBuffer[SHELLMATTA_OUTPUT_BUFFER_SIZE];
    va_list                 arg;
    int                     length;
    shellmatta_retCode_t    ret = SHELLMATTA_OK;
    shellmatta_instance_t   *inst = (shellmatta_instance_t*)handle;

    /** -# check parameters for plausibility  */
    if(     (NULL               != inst)
        &&  (SHELLMATTA_MAGIC   == inst->magic))
    {
        /** -# assemble output string and write it  */
        va_start(arg, fmt);
        length = vsnprintf(outputBuffer, SHELLMATTA_OUTPUT_BUFFER_SIZE, fmt, arg);
        va_end(arg);

        if(length < 0)
        {
            ret = SHELLMATTA_ERROR;
        }
        else
        {
            inst->write(outputBuffer, length);
        }
    }
    else
    {
        ret = SHELLMATTA_USE_FAULT;
    }
    return ret;
}
#endif

/** @} */