#include <cmdHandler.h>
#include <cmdHelper.h>

#include <stdint.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <stdarg.h>

#include <socket.h>

#include <logger.h>
#include <PontCoopScheduler.h>
#include <wizHelper.h>
#include <config.h>


extern const uint8_t CMD_SOCK;

const uint16_t cmdPort = 23;

typedef enum {
    CH_INIT,
    CH_LISTEN,
    CH_WAITING,
    CH_BANNER,
    CH_PROMPT,
    CH_RECEIVE,
    CH_DISCONNECT,
    CH_DISCONNECT_WAIT,
    CH_ERROR
} chState_t;

void sendString(const char *buf) {
        send(CMD_SOCK, (uint8_t*)buf, strlen(buf));
}

bool sendFormatString(const char *format, ...) {
    bool retCode = true;
    va_list vl;
    va_start(vl, format);
    char buf[4096];
    int vcnt = vsnprintf(buf, sizeof(buf), format, vl);
    retCode = (vcnt < sizeof(buf));
    va_end(vl);
    sendString(buf);
    return retCode;
}

// returns 0 to continue waiting for input
// returns -1 to close the connection
// returns 1 to toggle to admin mode
// returns 2 to toggle to config mode
// returns 3 to toggle back to default mode
static int8_t cmdExecuteCommand(uint8_t *cmdLine, bool resetSpecialModes) {
    const static char HELP_MSG[] = \
      "Usage\n\r" \
      "\n\r" \
      "help ................................. Show this help page\n\r" \
      "quit ................................. Terminate the console session\n\r" \
      "enable ............................... Enable admin mode\n\r" \
      "config ............................... Enter configuration mode\n\r" \
      "disable .............................. Disable admin/config mode\n\r" \
      ;
    const static char CONFIG_INTRO_MSG[] = \
      "In configuration mode each command changing the configuration\n\r" \
      "will save changes directly to the EEPROM.\n\r" \
      "However, the system will only consider these changes after a\n\r" \
      "restart since only in this sitution the EEPROM is read.\n\r" \
      "\n\r" \
      ;
    const static char GOODBYE_MSG[] = "Good bye\n\r";
    const static char OK_MSG[] = "OK\n\r";
    const static char FAILED_MSG[] = "Failed\n\r";
    const static char UNKNOWN_COMMAND[] = "Unknown command\n\r";
    uint8_t *messageToSend = NULL;

    static bool adminMode = false;
    static bool configMode = false;

    if (resetSpecialModes) {
        adminMode = false;
        configMode = false;
    }

    cmd_t const * commands = getRegularCommands();
    if (adminMode) {
        commands = getAdminCommands();
    } else if (configMode) {
        commands = getConfigCommands();
    }

    coloredMsg(LOG_YELLOW, "ch cec cmdLine is %s", cmdLine);;

    #define MAX_NUM_OF_ARGS 8
    char *args[MAX_NUM_OF_TASKS];
    uint8_t argc = 0;
    char *inCmdLine = (char*)cmdLine;
    do {
        args[argc++] = strtok(inCmdLine, " ");
        inCmdLine = NULL;
    } while (args[argc - 1] != NULL);
    if (argc > 0) {
        argc--;
    }
    char *cmd = args[0];

    int8_t retCode = 0;
    coloredMsg(LOG_YELLOW, "ch cec cmd is %s, number of arguments %d", cmd, argc);

    if (0 == strcmp(cmd, "quit")) {
        messageToSend = (uint8_t*)GOODBYE_MSG;
        retCode = -1;
    } else if (0 == strcmp(cmd, "help")) {
        if (configMode) {
            sendString(CONFIG_INTRO_MSG);
        }
        sendString(HELP_MSG);
        uint8_t cmdIdx = 0;
        while (true) {
            cmd_t command = commands[cmdIdx];
            if (0 == strcmp("END_OF_CMDS", command.name)) {
                break;
            }
            sendString(command.help);
            cmdIdx++;
        }
        messageToSend = NULL;
    } else if (0 == strcmp(cmd, "enable")) {
        coloredMsg(LOG_YELLOW, "ch cec enable admin mode");
        adminMode = true;
        retCode = 1;
    } else if (0 == strcmp(cmd, "disable")) {
        coloredMsg(LOG_YELLOW, "ch cec disable admin mode");
        adminMode = false;
        configMode = false;
        retCode = 3;
    } else if (0 == strcmp(cmd, "config")) {
        coloredMsg(LOG_YELLOW, "ch cec enable config mode");
        configMode = true;
        retCode = 2;
    } else {
        uint8_t cmdIdx = 0;
        while (true) {
            cmd_t command = commands[cmdIdx];
            if (0 == strcmp("END_OF_CMDS", command.name)) {
                messageToSend = (uint8_t*) UNKNOWN_COMMAND;
                break;
            }
            if (0 == strcmp(cmd, command.name)) {
                messageToSend = command.cmdFunc(argc, args) ? (uint8_t*)OK_MSG : (uint8_t*)FAILED_MSG;
                break;
            }
            cmdIdx++;
        }
    }

    if (messageToSend) {
        coloredMsg(LOG_YELLOW, "ch cec command finally returns %s", messageToSend);
        send(CMD_SOCK, messageToSend, strlen((char*)messageToSend));
    }

    return retCode;
}

static void cmdHandlerEngine(void *handle) {
    static uint8_t receiveBuffer[256];

    static chState_t state = CH_INIT;
    static bool resetSpecialModes = false;

    static char banner[] = \
      "MBGW3\n\r" \
      "Type help for usage help\n\r" \
      "or quit to close the connection.\n\r";

    static char *prompt;
    static char defaultPrompt[] = " > ";
    static char adminPrompt[] = " (admin) # ";
    static char configPrompt[] = " (config) $ ";


    int8_t res = 0;
    uint8_t sockState;
    int32_t resultSend;
    int16_t receivedOctets;
    int32_t resultRecv;
    uint8_t resultDisconnect;


    if (isNetworkAvailable()) {
        switch (state) {
        case CH_INIT:
            coloredMsg(LOG_YELLOW, "ch che initializing socket");

            res = socket(CMD_SOCK, Sn_MR_TCP, cmdPort, SF_IO_NONBLOCK);
            coloredMsg(LOG_YELLOW, "ch che socket returns %d", res);

            if (res == CMD_SOCK) {
                coloredMsg(LOG_YELLOW, "ch che socket is initialized");
                state = CH_LISTEN;
            } else {
                state = CH_ERROR;
            }
            break;

        case CH_LISTEN:
            coloredMsg(LOG_YELLOW, "ch che listening");
            
            res = listen(CMD_SOCK);
            coloredMsg(LOG_YELLOW, "ch che listen returns %d", res);

            if (res == SOCK_OK) {
                coloredMsg(LOG_YELLOW, "ch che ok, waiting for established");
                state = CH_WAITING;
            } else {
                state = CH_ERROR;
            }
            break;

        case CH_WAITING:
            sockState = getSn_SR(CMD_SOCK);
            if (sockState != SOCK_LISTEN) {
                coloredMsg(LOG_YELLOW, "ch che socket state is 0x%02x", sockState);
                state = CH_DISCONNECT;
            }

            if (sockState == SOCK_ESTABLISHED) {
                coloredMsg(LOG_YELLOW, "ch che connection is established");
                state = CH_BANNER;
            }
            break;

        case CH_BANNER:
            coloredMsg(LOG_YELLOW, "ch che send banner");
            sockState = getSn_SR(CMD_SOCK);
            if (sockState != SOCK_ESTABLISHED) {
                coloredMsg(LOG_YELLOW, "ch che sockState is 0x%02x when trying to send banner", sockState);
                state = CH_DISCONNECT;
            } else {
                resultSend = send(CMD_SOCK, (uint8_t*)banner, strlen(banner));
                coloredMsg(LOG_YELLOW, "ch che sent banner, send returns 0x%02x", resultSend);
                prompt = defaultPrompt;
                resetSpecialModes = true;
                state = CH_PROMPT;
            }
            break;

        case CH_PROMPT:
            coloredMsg(LOG_YELLOW, "ch che send prompt");
            sockState = getSn_SR(CMD_SOCK);
            if (sockState != SOCK_ESTABLISHED) {
                coloredMsg(LOG_YELLOW, "ch che sockState is 0x%02x when trying to send promt", sockState);
                state = CH_DISCONNECT;
            } else {
                sendFormatString("%s %s", getConfig()->deviceName, prompt);
                coloredMsg(LOG_YELLOW, "ch che sent prompt %s %s", getConfig()->deviceName, prompt);
                state = CH_RECEIVE;
            }
            break;

        case CH_RECEIVE:
            sockState = getSn_SR(CMD_SOCK);
            if (sockState != SOCK_ESTABLISHED) {
                coloredMsg(LOG_YELLOW, "ch che sockState is 0x%02x when trying to receive something", sockState);
                state = CH_DISCONNECT;
            } else {
                receivedOctets = getSn_RX_RSR(CMD_SOCK);

                if (receivedOctets > 0) {
                    memset(receiveBuffer, 0, sizeof(receiveBuffer));
                    resultRecv = recv(CMD_SOCK, receiveBuffer, sizeof(receiveBuffer));
                    coloredMsg(LOG_YELLOW, "ch che recv returns 0x%02x", resultRecv);
                    if (resultRecv > 0) {
                        if ((receiveBuffer[strlen((char*)receiveBuffer) - 1] == 0x0a) ||
                            (receiveBuffer[strlen((char*)receiveBuffer) - 1] == 0x0d)) {
                            receiveBuffer[strlen((char*)receiveBuffer) - 1] = 0;
                        }
                        if ((receiveBuffer[strlen((char*)receiveBuffer) - 1] == 0x0a) ||
                            (receiveBuffer[strlen((char*)receiveBuffer) - 1] == 0x0d)) {
                            receiveBuffer[strlen((char*)receiveBuffer) - 1] = 0;
                        }
                        coloredMsg(LOG_YELLOW, "ch che received: %s", receiveBuffer);
                        int8_t resCEC = cmdExecuteCommand(receiveBuffer, resetSpecialModes);
                        resetSpecialModes = false;
                        switch (resCEC) {
                            case 0:
                                state = CH_PROMPT;
                                break;
                            case -1:
                                state = CH_DISCONNECT;
                                break;
                            case 1:
                                prompt = adminPrompt;
                                state = CH_PROMPT;
                                break;
                            case 2:
                                prompt = configPrompt;
                                state = CH_PROMPT;
                                break;
                            case 3:
                                prompt = defaultPrompt;
                                state = CH_PROMPT;
                                break;
                        }
                    }
                }
            }
            break;

        case CH_DISCONNECT:
            coloredMsg(LOG_YELLOW, "ch che close our end");
            resultDisconnect = disconnect(CMD_SOCK);
            coloredMsg(LOG_YELLOW, "ch che disconnect returns 0x%02x", resultDisconnect);
            state = CH_DISCONNECT_WAIT;
            break;

        case CH_DISCONNECT_WAIT:
            //coloredMsg(LOG_YELLOW, "ch che waiting after disconnect");
            sockState = getSn_SR(CMD_SOCK);
            //coloredMsg(LOG_YELLOW, "ch che sockState is 0x%02x", sockState);
            if (sockState == SOCK_CLOSED) {
                coloredMsg(LOG_YELLOW, "ch che socket is closed now");
                state = CH_INIT;
            }
            break;
        

        case CH_ERROR:
            coloredMsg(LOG_YELLOW, "ch che error state, will stop here");
            schDel(cmdHandlerEngine, NULL);
            state = CH_INIT;
            schAdd(cmdHandlerEngine, NULL, 5000, 100);
            coloredMsg(LOG_YELLOW, "ch che restart command handler engine in 5s");
            break;
        }
    }
}

void cmdHandlerInit() {
    schAdd(cmdHandlerEngine, NULL, 0, 100);
}