#include "console.h"
#include <SDL3/SDL.h>
#include "jdraw.h"
#include "jinput.h"
#include "room.h"
#include "actor.h"
#include "jgame.h"
#include "debug.h"
#include "config.h"

namespace console
{
    bool enabled = false;
    bool initialized = false;
    char msg[80];
    char cmd[80];
    int cmd_size = 0;
    uint8_t cursor_blinker = 0;

    void execute_command();

    void init()
    {
        strcpy(msg, "PoolOS v0.1 ready.");
        cmd[0] = '\0';
        cmd_size = 0;
        initialized = true;
    }

    void toggle()
    {
        if (!initialized) return;
        enabled = !enabled;
        cmd[0] = '\0';
        cmd_size = 0;
    }

    const bool isEnabled()
    {
        return enabled;
    }

    const char scancode_to_ascii(uint8_t scancode)
    {
        if (scancode == SDL_SCANCODE_SPACE) return ' ';
        if (scancode == SDL_SCANCODE_0 || scancode == SDL_SCANCODE_KP_0) return '0';
        if (scancode >= SDL_SCANCODE_1 && scancode <= SDL_SCANCODE_9) return scancode+19;
        if (scancode >= SDL_SCANCODE_KP_1 && scancode <= SDL_SCANCODE_KP_9) return scancode-40;
        if (scancode >= SDL_SCANCODE_A && scancode <= SDL_SCANCODE_Z) return scancode+61;

        if (scancode == SDL_SCANCODE_MINUS) return '-';
        if (scancode == SDL_SCANCODE_EQUALS) return '=';
        if (scancode == SDL_SCANCODE_LEFTBRACKET) return '[';
        if (scancode == SDL_SCANCODE_RIGHTBRACKET) return ']';
        if (scancode == SDL_SCANCODE_BACKSLASH) return '\\';
        if (scancode == SDL_SCANCODE_SEMICOLON) return ';';
        if (scancode == SDL_SCANCODE_APOSTROPHE) return '<';
        if (scancode == SDL_SCANCODE_COMMA) return ',';
        if (scancode == SDL_SCANCODE_PERIOD) return '.';
        if (scancode == SDL_SCANCODE_SLASH) return '/';

        return '\0';
    }

    const bool update()
    {
        if (!initialized || !enabled) return false;

        const uint8_t scancode = input::getKeyPressed();
        if (scancode != SDL_SCANCODE_UNKNOWN) {
            cursor_blinker = 0;
            if (scancode == SDL_SCANCODE_BACKSPACE) {
                if (cmd_size>0) cmd[--cmd_size] = '\0';
            } else if (scancode == SDL_SCANCODE_UNKNOWN) {
                // do nothing
            } else if (scancode == SDL_SCANCODE_RETURN) {
                execute_command();
            } else {
                char ascii = scancode_to_ascii(scancode);
                if (ascii != '\0')
                {
                    cmd[cmd_size++] = ascii;
                    cmd[cmd_size] = '\0';
                }
            }
        }

        cursor_blinker=(cursor_blinker+1)&0x07;
        return true;
    }

    void draw()
    {
        if (!initialized || !enabled) return;

        draw::color(BLACK);
        draw::fillrect(1, 0, 318, 14);
        draw::color(GREEN);
        draw::rect(1, -1, 318, 15);

        draw::print(msg, 3, 1, WHITE, 0);
        draw::print(">", 3, 7, WHITE+LIGHT, 0);
        draw::print(cmd, 7, 7, WHITE+LIGHT, 0);

        if (cursor_blinker<4)
        {
            const int size = strlen(cmd);
            draw::print("_", 7+size*4, 7, WHITE+LIGHT, 0);
        }
    }

    char *tokens[4];
    void tokenize()
    {
        int token = 0;
        for (int i=0; i<4; ++i) tokens[i]=nullptr;
        char *p = cmd;
        char *fin = cmd;

        while (true)
        {
            while (*fin!='\0' && *fin!=' ') fin++;
            tokens[token++] = p;
            if (*fin=='\0') return;
            *fin = '\0';
            fin++; p = fin;
        }
    }

    #define CMD_UNKNOWN    -1
    #define CMD_GIVE        0
    #define CMD_DROP        1
    #define CMD_GOTO        2
    #define CMD_MOVE        3
    #define CMD_EXIT        4
    #define CMD_SHOW        5
    #define CMD_HIDE        6
    #define CMD_SET         7
    #define CMD_HELP        8
    #define NUM_CMDS        9

    const char *command_text[NUM_CMDS] = { "GIVE", "DROP", "GOTO", "MOVE", "EXIT", "SHOW", "HIDE", "SET", "HELP" };

    #define PARAM_UNKNOWN  -1
    #define PARAM_RUN       0
    #define PARAM_GOD       1
    #define PARAM_JUMP      2
    #define NUM_PARAMS      3

    const char *param_text[NUM_PARAMS] = { "RUN", "GOD", "JUMP" };

    const int getCommand()
    {
        for (int i=0; i<NUM_CMDS; ++i)
        {
            if (strcmp(tokens[0], command_text[i])==0) return i;
        }
        return -1;
    }

    const int getParam(int index)
    {
        for (int i=0; i<NUM_PARAMS; ++i)
        {
            if (strcmp(tokens[index], param_text[i])==0) return i;
        }
        return -1;
    }

    const int getNumber(int index)
    {
        return SDL_atoi(tokens[index]);
    }

    const int getIndexFromString(char *str, std::vector<const char*> list)
    {
        //static const char *skillset_name[4] = {"SHOES", "GLOVES", "PANTS", "BAG"};
        for (int i=0; i<(int)list.size(); ++i)
        {
            if (strcmp(str, list[i])==0)
            {
                return i;
            }
        }
        return -1;
    }

    void execute_command()
    {
        tokenize();
        int command = getCommand();
        switch (command)
        {
            case CMD_UNKNOWN:
                strcpy(msg, "ERROR: Command not found.");
                break;
            case CMD_GIVE:
                if (!tokens[1]) {
                    strcpy(msg, "ERROR: Nothing to give.");
                } else {
                    strcpy(msg, "Ok.");
                    if (!actor::hero::giveBooster(tokens[1]))
                        if (!actor::hero::giveSkill(tokens[1]))
                            if (!actor::hero::pickPart(tokens[1]))
                                strcpy(msg, "ERROR: Cannot give that.");
                }
                break;
            case CMD_DROP:
                 if (!tokens[1]) {
                    strcpy(msg, "ERROR: Nothing to drop.");
                } else {
                    strcpy(msg, "Ok.");
                    if (!actor::hero::giveSkill(tokens[1]))
                        if (!actor::hero::pickPart(tokens[1]))
                            strcpy(msg, "ERROR: Cannot drop that.");
                }
                break;
            case CMD_GOTO:
                if (!tokens[1]) {
                    strcpy(msg, "ERROR: Nowhere to go.");
                } else {
                    room::load(getNumber(1));
                    strcpy(msg, "Ok.");
                }
                break;
            case CMD_MOVE:
                if (!tokens[1]) {
                    strcpy(msg, "ERROR: Nowhere to move.");
                } else {
                    switch (tokens[1][0])
                    {
                        case 'X':
                            if (!tokens[2]) {
                                strcpy(msg, "ERROR: Nowhere to move.");
                            } else {
                                int x = SDL_atoi(tokens[2]);
                                actor::hero::move(&x, nullptr, nullptr);
                            }
                            break;
                        case 'Y':
                            if (!tokens[2]) {
                                strcpy(msg, "ERROR: Nowhere to move.");
                            } else {
                                int y = SDL_atoi(tokens[2]);
                                actor::hero::move(nullptr, &y, nullptr);
                            }
                            break;
                        case 'Z':
                            if (!tokens[2]) {
                                strcpy(msg, "ERROR: Nowhere to move.");
                            } else {
                                int z = SDL_atoi(tokens[2]);
                                actor::hero::move(nullptr, nullptr, &z);
                            }
                            break;
                        default:
                            strcpy(msg, "ERROR: Unknown axis.");
                    }
                }
                break;
            case CMD_EXIT:
                game::exit();
                break;
            case CMD_SHOW:
                 if (!tokens[1]) {
                    strcpy(msg, "ERROR: Nothing to show.");
                } else {
                    strcpy(msg, "Ok.");
                    const int value = getIndexFromString(tokens[1], {"NOTHING", "POS", "FLAGS"});
                    if (value==-1)
                        strcpy(msg, "ERROR: Cannot show that.");
                    else
                        debug::enable(value);
                }
                break;
            case CMD_HIDE:
                 if (!tokens[1]) {
                    strcpy(msg, "ERROR: Nothing to hide.");
                } else {
                    strcpy(msg, "Ok.");
                    const int value = getIndexFromString(tokens[1], {"NOTHING", "POS", "FLAGS"});
                    if (value==-1)
                        strcpy(msg, "ERROR: Cannot hide that.");
                    else
                        debug::disable(value);
                }
                break;
            case CMD_SET:
                if (!tokens[1]) {
                    strcpy(msg, "ERROR: Nothing to set.");
                } else {
                    strcpy(msg, "Ok.");
                    const int value = getIndexFromString(tokens[1], {"KIOSK", "INFINITELIVES"});
                    if (value==-1)
                        strcpy(msg, "ERROR: Cannot set that.");
                    else
                        switch (value) {
                            case 0:
                                config::setKioskMode(!config::getKioskMode());
                                break;
                            case 1:
                                config::setInifiniteLives(!config::getInfiniteLives());
                                break;
                        }

                }
            case CMD_HELP:
                if (!tokens[1]) {
                    strcpy(msg, "GIVE DROP GOTO MOVE EXIT SHOW HIDE SET HELP");
                } else {
                    int command = getCommand();
                    switch (command)
                    {
                        case CMD_UNKNOWN:
                            strcpy(msg, "Unknown command, cannot help.");
                            break;
                        case CMD_GIVE:
                            strcpy(msg, "RUN GOD JUMP LIVE SHOES GLOVES PANTS BAG FILTER PUMP TIMER SALT PIPE ELBOW");
                            break;
                        case CMD_DROP:
                            strcpy(msg, "RUN GOD JUMP LIVE SHOES GLOVES PANTS BAG FILTER PUMP TIMER SALT PIPE ELBOW");
                            break;
                        case CMD_GOTO:
                            strcpy(msg, "PARAM: Number of room to go.");
                            break;
                        case CMD_MOVE:
                            strcpy(msg, "PARAMS: Axis Number. Exemple: MOVE X 16, MOVE Z 4...");
                            break;
                        case CMD_EXIT:
                            strcpy(msg, "Exits the game.");
                            break;
                        case CMD_SHOW:
                            strcpy(msg, "NOTHING POS FLAGS");
                            break;
                        case CMD_HIDE:
                            strcpy(msg, "NOTHING POS FLAGS");
                            break;
                    }

                }
        }
        cmd_size=0;
        cmd[cmd_size] = '\0';
    }
}