/*
 * 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_history.c
 * @brief   history buffer functions of shellmatta
 * @author  Stefan Strobel <stefan.strobel@shimatta.net>
 */

/**
 * @addtogroup shellmatta_history
 * @{
 */

#include "shellmatta_history.h"
#include "shellmatta.h"
#include "shellmatta_utils.h"

/**
 * @brief       appends a byte to the history ring stack buffer
 * @param[in]   inst    pointer to a shellmatta instance
 * @param[in]   byte    byte to append to the history buffer
 */
static void appendHistoryByte(shellmatta_instance_t *inst, char byte)
{
    /** -# calculate the new history buffer index */
    inst->historyEnd ++;
    if(inst->historyEnd >= inst->historyBufferSize)
    {
        inst->historyEnd = 0u;
    }

    /** -# append the byte */
    inst->historyBuffer[inst->historyEnd] = byte;

    /** -# check if we overwrite an existing stored command */
    if(inst->historyEnd == inst->historyStart)
    {
        /** -# move the start pointer to the next termination (0) */
        do
        {
            inst->historyStart ++;

            if(inst->historyStart >= inst->historyBufferSize)
            {
                inst->historyStart = 0u;
            }

        }while(0u != inst->historyBuffer[inst->historyStart]);
    }
}

/**
 * @brief       reads a byte from the history buffer and decreases the read index
 * @param[in]   inst    pointer to a shellmatta instance
 * @param[out]  byte    pointer to a char where the read out byte will be stored
 * @return      false: no new byte to read
 */
static bool getHistoryByte(shellmatta_instance_t *inst, char *byte)
{
    bool ret = false;

    /** -# check if we have reached the end of the buffer already */
    if(inst->historyRead != inst->historyStart)
    {
        /** -# read out one byte and decrease the read index */
        *byte = inst->historyBuffer[inst->historyRead];

        if(0u == inst->historyRead)
        {
            inst->historyRead = inst->historyBufferSize;
        }

        inst->historyRead --;

        ret = true;
    }

    return ret;
}

/**
 * @brief       compares the current buffer to the last command in the history buffer
 * @param[in]   inst    pointer to a shellmatta instance
 * @return      true:   current command is identical to the last one in the history buffer
 */
static bool compareLastCommand(shellmatta_instance_t *inst)
{
    bool        ret = false;
    uint32_t    i;
    uint32_t    cnt;

    /** -# check if there is anything in the buffer */
    if(inst->historyStart != inst->historyEnd)
    {
        i   = inst->historyEnd;
        cnt = 0u;

        ret = true;

        while((true == ret) && (cnt < inst->inputCount))
        {
            /** -# terminate compare on first mismatch */
            if((inst->historyBuffer[i] != inst->buffer[cnt]) || (0u == inst->historyBuffer[i]))
            {
                ret = false;
            }

            if(0u == i)
            {
                i = (inst->historyBufferSize - 1u);
            }
            else
            {
                i --;
            }

            cnt ++;
        }

        /*! -# check if we are at the end of the command in the buffer - there has to be a terminating 0 */
        if(0u != inst->historyBuffer[i])
        {
            ret = false;
        }
    }

    return ret;
}

/**
 * @brief           navigates in the history buffer by the given number of commands
 * @param[in, out]  inst    pointer to a shellmatta instance
 * @param[in]       cnt     direction and count to navigate
 * @return          false: end of buffer reached
 */
bool history_navigate(shellmatta_instance_t *inst, int32_t cnt)
{
    bool ret = true;
    uint32_t tempReadIdx = 0u;

    while((cnt > 0) && (true == ret))
    {
        if(inst->historyRead != inst->historyEnd)
        {
            inst->historyRead ++;
        }
        while(inst->historyRead != inst->historyEnd)
        {
            inst->historyRead ++;
            if(inst->historyRead >= inst->historyBufferSize)
            {
                inst->historyRead -= inst->historyBufferSize;
            }

            if(     (inst->historyRead != inst->historyEnd)
                &&  (0u == inst->historyBuffer[inst->historyRead]))
            {
                if(0u == inst->historyRead)
                {
                    inst->historyRead = inst->historyBufferSize;
                }
                inst->historyRead --;
                cnt -= 1;
                break;
            }
        }

        if(inst->historyRead == inst->historyEnd)
        {
            ret = false;
        }
    }

    while((cnt < 0) && (true == ret))
    {
        tempReadIdx = inst->historyRead;
        while(inst->historyRead != inst->historyStart)
        {
            if(0u == inst->historyRead)
            {
                inst->historyRead = inst->historyBufferSize;
            }
            inst->historyRead --;

            if(     (inst->historyRead != inst->historyStart)
                &&  (0u == inst->historyBuffer[inst->historyRead]))
            {
                if(0u == inst->historyRead)
                {
                    inst->historyRead = inst->historyBufferSize;
                }
                inst->historyRead --;
                cnt += 1;
                break;
            }
        }
        if(inst->historyRead == inst->historyStart)
        {
            inst->historyRead = tempReadIdx;
            inst->historyReadUp = false;
            ret = false;
        }
    }

    return ret;
}

/**
 * @brief       stores the current command from the instances buffer into the
 *              history buffer
 * @param[in]   inst        pointer to a shellmatta instance
 */
void history_storeCmd(shellmatta_instance_t *inst)
{
    uint32_t i;

    /** -# check if we have enough room for the command in the history buffer
     * and there is a new command to be stored                              */
    if(     (inst->historyBufferSize > inst->inputCount)
        &&  (0u     != inst->inputCount)
        &&  (true   == inst->dirty)
        &&  (true   != compareLastCommand(inst)))
    {
        /** -# append the command termination */
        appendHistoryByte(inst, 0u);

        /** -# append the command byte wise in reverse direction */
        for(i = inst->inputCount; i > 0u; i --)
        {
            appendHistoryByte(inst, inst->buffer[i - 1u]);
        }
    }

    inst->dirty = false;
}

/**
 * @brief       restores the command from the history buffer where the read
 *              index points on
 * @param[in]   inst    pointer to a shellmatta instance
 */
void history_restoreCmd(shellmatta_instance_t *inst)
{
    char byte;
    bool ret = true;
    bool anythingToRestore = false;

    ret = getHistoryByte(inst, &byte);

    /** -# delete the input if there is data in the history buffer */
    if(true == ret)
    {
        utils_clearInput(inst);
        anythingToRestore = true;
    }
    while((ret == true) && (byte != 0u))
    {
        inst->buffer[inst->inputCount] = byte;
        inst->inputCount    ++;
        inst->cursor        ++;
        ret = getHistoryByte(inst, &byte);
    }

    if(true == anythingToRestore)
    {
        utils_writeEcho(inst, inst->buffer, inst->inputCount);
        inst->dirty = false;
    }
    (void)history_navigate(inst, 1);
}

/**
 * @brief       resets the history buffer pointers to show to the most recent
 *              command again
 * @param[in]   inst    pointer to a shellmatta instance
 */
void history_reset(shellmatta_instance_t *inst)
{
    inst->historyRead = inst->historyEnd;
    inst->historyReadUp = true;
}

/**
 * @}
 */