#include <cmdHandler.h>

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

#include <socket.h>

#include <logger.h>
#include <PontCoopScheduler.h>
#include <wizHelper.h>
#include <mbusComm.h>
#include <loopCtrl.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;


typedef bool (*cmdFunc_t)(uint8_t argc, char **args);

typedef struct {
    bool requiredConfigMode;
    char name[16];
    char help[512];
    cmdFunc_t cmdFunc;
} cmd_t;


// clear statistics
bool clearCmd(uint8_t argc, char **args) {
    t_mbusCommStats zeroedStats = { .requestCnt = 0, .errorCnt = 0 };
    mbusCommSetStats(zeroedStats);
    coloredMsg(LOG_YELLOW, true, "ch cc global statistics cleared");
    return true;
}

bool globalStatsCmd(uint8_t argc, char **args) {
    t_mbusCommStats *stats = mbusCommGetStats();
    char buf[256];
    sprintf(buf, \
      "Global statistics\n\r" \
      "  Requests: %d\n\r" \
      "  Errors:   %d\n\r",
      stats->requestCnt, stats->errorCnt
    );
    send(CMD_SOCK, buf, strlen(buf));
    return true;
}

bool mbusCommEnableCmd(uint8_t argc, char **args) {
    bool retCode = true;
    if (argc == 2) {
        if (0 == strcmp("false", args[1])) {
            mbusCommEnable(false);
            coloredMsg(LOG_YELLOW, true, "ch mcec Meterbus communication disabled");
        } else if (0 == strcmp("true", args[1])) {
            mbusCommEnable(true);
            coloredMsg(LOG_YELLOW, true, "ch mcec Meterbus communication enabled");
        } else {
            retCode = false;
        }
    } else {
        retCode = false;
    }
    return retCode;
}

bool loopEnableCmd(uint8_t argc, char **args) {
    bool retCode = true;
    if (argc == 2) {
        if (0 == strcmp("false", args[1])) {
            loopDisable();
            coloredMsg(LOG_YELLOW, true, "ch lec loop disabled");
        } else if (0 == strcmp("true", args[1])) {
            loopEnable();
            coloredMsg(LOG_YELLOW, true, "ch lec loop enabled");
        } else {
            retCode = false;
        }
    } else {
        retCode = false;
    }
    return retCode;
}

const static cmd_t COMMANDS[] = {
    { .requiredConfigMode = true, .name = "clear", .cmdFunc = clearCmd,
     .help = \
        "clear ................................ Clears the global Meterbus\n\r" \
        "                                       statistics\n\r" \
        "                                       Required configuration mode\n\r"
    },
    { .requiredConfigMode = true, .name = "mbusCommEnable", .cmdFunc = mbusCommEnableCmd,
     .help = \
        "mbusCommEnable true|false ............ Enables or disables the Meterbus\n\r" \
        "                                       communication\n\r"
    },
    { .requiredConfigMode = true, .name = "loopEnable", .cmdFunc = loopEnableCmd,
     .help = \
        "loopEnable true|false ................ Enables or disables the loop.\n\r" \
        "                                       Disable Meterbus communication\n\r" \
        "                                       first if you want to disable the\n\r" \
        "                                       for a longer time, otherwise the\n\r" \
        "                                       request will enable it again\n\r"
    },
    { .requiredConfigMode = false, .name = "globalStats", .cmdFunc = globalStatsCmd,
      .help = \
        "globalStats .......................... Show the global statistics\n\r" \
        "                                       counters requestCnt and errorCnt\n\r"
    },
    { .requiredConfigMode = false, .name = "END_OF_CMDS", .help = "",.cmdFunc = NULL }
};


// returns 0 to continue waiting for input
// returns -1 to close the connection
// returns 1 to toggle to config mode
// returns 2 to toggle back to default mode
int8_t cmdExecuteCommand(uint8_t *cmdLine, bool resetConfigMode) {
    const static uint8_t HELP_MSG[] = \
      "Usage\n\r" \
      "\n\r" \
      "help ................................. Show this help page\n\r" \
      "quit ................................. Terminate the console session\n\r" \
      "enable ............................... Enable configuration mode\n\r" \
      "disable .............................. Disable configuration mode\n\r" \
      ;
    const static uint8_t GOODBYE_MSG[] = "Good bye\n\r";
    const static uint8_t OK_MSG[] = "OK\n\r";
    const static uint8_t FAILED_MSG[] = "Failed\n\r";
    const static uint8_t REQUIRES_CONFIG_MODE_MGS[] = "Not executed, requires config mode\n\r";
    uint8_t *messageToSend = NULL;

    static bool configMode = false;

    if (resetConfigMode) {
        configMode = false;
    }

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

    #define MAX_NUM_OF_ARGS 8
    char *args[MAX_NUM_OF_TASKS];
    uint8_t argc = 0;
    char *inCmdLine = 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, false, "ch cec cmd is %s, number of arguments %d", cmd, argc);

    if (0 == strcmp(cmd, "quit")) {
        messageToSend = GOODBYE_MSG;
        retCode = -1;
    } else if (0 == strcmp(cmd, "help")) {
        send(CMD_SOCK, HELP_MSG, strlen(HELP_MSG));
        uint8_t cmdIdx = 0;
        while (true) {
            cmd_t command = COMMANDS[cmdIdx];
            if (0 == strcmp("END_OF_CMDS", command.name)) {
                break;
            }
            send(CMD_SOCK, command.help, strlen(command.help));
            cmdIdx++;
        }
        messageToSend = NULL;
    } else if (0 == strcmp(cmd, "enable")) {
        coloredMsg(LOG_YELLOW, true, "ch cec enable config mode");
        configMode = true;
        retCode = 1;
    } else if (0 == strcmp(cmd, "disable")) {
        coloredMsg(LOG_YELLOW, true, "ch cec disable config mode");
        configMode = false;
        retCode = 2;
    } else {
        uint8_t cmdIdx = 0;
        while (true) {
            cmd_t command = COMMANDS[cmdIdx];
            if (0 == strcmp("END_OF_CMDS", command.name)) {
                break;
            }
            if (0 == strcmp(cmd, command.name)) {
                if (command.requiredConfigMode && !configMode) {
                    messageToSend = REQUIRES_CONFIG_MODE_MGS;
                } else {
                    messageToSend = command.cmdFunc(argc, args) ? OK_MSG : FAILED_MSG;
                }
            }
            cmdIdx++;
        }
    }

    if (messageToSend) {
        send(CMD_SOCK, messageToSend, strlen(messageToSend));
    }

    return retCode;
}

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

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

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

    static uint8_t *prompt;
    static uint8_t defaultPrompt[] = "MBGW3 # ";
    static uint8_t elevatedPrompt[] = "MBGW3 (admin) > ";


    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, false, "ch che initializing socket");

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

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

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

            if (res == SOCK_OK) {
                coloredMsg(LOG_YELLOW, false, "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, false, "ch che socket state is 0x%02x", sockState);
            }

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

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

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

        case CH_RECEIVE:
            sockState = getSn_SR(CMD_SOCK);
            if (sockState != SOCK_ESTABLISHED) {
                coloredMsg(LOG_YELLOW, true, "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, false, "ch che recv returns 0x%02x", resultRecv);
                    if (resultRecv > 0) {
                        if ((receiveBuffer[strlen(receiveBuffer) - 1] == 0x0a) ||
                            (receiveBuffer[strlen(receiveBuffer) - 1] == 0x0d)) {
                            receiveBuffer[strlen(receiveBuffer) - 1] = 0;
                        }
                        if ((receiveBuffer[strlen(receiveBuffer) - 1] == 0x0a) ||
                            (receiveBuffer[strlen(receiveBuffer) - 1] == 0x0d)) {
                            receiveBuffer[strlen(receiveBuffer) - 1] = 0;
                        }
                        coloredMsg(LOG_YELLOW, false, "ch che received: %s", receiveBuffer);
                        int8_t resCEC = cmdExecuteCommand(receiveBuffer, resetConfigMode);
                        resetConfigMode = false;
                        switch (resCEC) {
                            case 0:
                                state = CH_PROMPT;
                                break;
                            case -1:
                                state = CH_DISCONNECT;
                                break;
                            case 1:
                                prompt = elevatedPrompt;
                                state = CH_PROMPT;
                                break;
                            case 2:
                                prompt = defaultPrompt;
                                state = CH_PROMPT;
                                break;
                        }
                    }
                }
            }
            break;

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

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

        case CH_ERROR:
            coloredMsg(LOG_YELLOW, true, "ch che error state, will stop here");
            schDel(cmdHandlerEngine, NULL);
            break;
        }
    }
}

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