/*
 * This file is part of the encoders project.
 * Copyright 2025 Edward V. Emelianov <edward.emelianoff@gmail.com>.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <string.h>

#include "flash.h"
#include "proto.h"
#include "spi.h"
#include "strfunc.h"
#include "usart.h"
#include "usb_dev.h"
#include "version.inc"

// user runtime parameters
parameters_t user_pars = {0};

// error codes
typedef enum {
    ERR_OK,         // no errors
    ERR_FAIL,       // failed to run command
    ERR_BADCMD,     // bad command
    ERR_BADPAR,     // bad parameter (absent or wrong value)
    ERR_SILENCE,    // getter or other - don't need to send "OK"
    ERR_AMOUNT
} errcode_e;

static const char* const errors[ERR_AMOUNT] = {
    [ERR_OK] = "OK",
    [ERR_FAIL] = "FAIL",
    [ERR_BADCMD] = "BADCMD",
    [ERR_BADPAR] = "BADPAR",
    [ERR_SILENCE] = "",
};

// commands indexes
typedef enum{
    C_dummy,
    C_help,
    C_sendX,
    C_sendY,
    C_setiface1,
    C_setiface2,
    C_setiface3,
    C_dumpconf,
    C_erasestorage,
    C_storeconf,
    C_reset,
    C_fin,
    C_encstart,
    C_encX,
    C_encY,
    C_spistat,
    C_spiinit,
    C_spideinit,
    C_cpol,
    C_cpha,
    C_br,
    C_encbits,
    C_testX,
    C_testY,
    C_encbufsz,
    C_minzeros,
    C_maxzeros,
    C_autom,
    C_amperiod,
    C_usart,
    C_ssii,
    C_debug,
    C_AMOUNT
} cmd_e;

// command handler (idx - index of command in list, par - all after equal sign in "cmd = par")
typedef errcode_e (*handler_t)(cmd_e idx, char *par);

typedef struct{
    const char* cmd;    // command
    handler_t handler;  // handler function
} funcdescr_t;

static const funcdescr_t commands[C_AMOUNT];

// getter for integer parameter
#define IGETTER(idx, ival)  do{CMDWR(commands[idx].cmd); USB_putbyte(I_CMD, '='); \
                            CMDWR(i2str(ival)); CMDn(); return ERR_SILENCE; }while(0)

static errcode_e help(cmd_e idx, char* par);

static errcode_e dummy(cmd_e idx, char *par){
    static int32_t val = -111;
    if(par && *par){
        int32_t i;
        char *nxt = getint(par, &i);
        DBGs(par);
        DBGs(nxt);
        if(par == nxt) return ERR_BADPAR;
        val = i;
    }
    IGETTER(idx, val);
}

static errcode_e sendenc(cmd_e idx, char *par){
    if(!par || !*par) return ERR_BADPAR;
    switch(idx){
        case C_sendX:
            if(!CDCready[I_X]) return ERR_FAIL;
            if(!USB_sendstr(I_X, par)) return ERR_FAIL;
            newline(I_X);
        break;
        case C_sendY:
            if(!CDCready[I_Y]) return ERR_FAIL;
            if(!USB_sendstr(I_Y, par)) return ERR_FAIL;
            newline(I_Y);
        break;
        default:
            return ERR_BADCMD;
    }
    return ERR_OK;
}

static errcode_e setiface(cmd_e idx, char *par){
    if(idx < C_setiface1 || idx >= C_setiface1 + InterfacesAmount) return ERR_BADCMD;
    idx -= C_setiface1; // now it is an index of iIlengths
    if(par && *par){
        int l = strlen(par);
        DBGs(i2str(l));
        if(l > MAX_IINTERFACE_SZ) return ERR_BADPAR; // too long
        the_conf.iIlengths[idx] = (uint8_t) l * 2;
        char *ptr = (char*)the_conf.iInterface[idx];
        for(int i = 0; i < l; ++i){ // make fucking hryunicode
            *ptr++ = *par++;
            *ptr++ = 0;
        }
    }
    // getter
    int l = the_conf.iIlengths[idx] / 2;
    char *ptr = (char*)the_conf.iInterface[idx];
    CMDWR(commands[idx + C_setiface1].cmd);
    CMDWR("=");
    for(int i = 0; i < l; ++i){
        USB_putbyte(I_CMD, *ptr);
        ptr += 2;
    }
    CMDn();
    return ERR_SILENCE;
}

static errcode_e erasestor(cmd_e _U_ idx, char *par){
    int32_t npage = -1;
    if(par){
        if(par == getint(par, &npage)) return ERR_BADPAR;
    }
    if(erase_storage(npage)) return ERR_FAIL;
    return ERR_OK;
}

static errcode_e storeconf(_U_ cmd_e idx, _U_ char *par){
    if(store_userconf()) return ERR_FAIL;
    return ERR_OK;
}

static errcode_e reset(_U_ cmd_e idx, _U_ char *par){
    NVIC_SystemReset();
    return ERR_OK; // never reached
}

static errcode_e fini(_U_ cmd_e idx, _U_ char *par){
    flashstorage_init();
    return ERR_OK; // never reached
}

static errcode_e encstart(cmd_e idx, _U_ char *par){
    if(idx == C_encX || idx == C_encstart)
        if(!spi_start_enc(0)) return ERR_FAIL;
    if(idx == C_encY || idx == C_encstart)
        if(!spi_start_enc(1)) return ERR_FAIL;
    return ERR_OK;
}

static errcode_e spistat(_U_ cmd_e idx, _U_ char *par){
    for(int i = 1; i < 3; ++i){
        USB_sendstr(I_CMD, "SPI");
        USB_putbyte(I_CMD, '0' + i);
        USB_sendstr(I_CMD, ": ");
        switch(spi_status[i]){
            case SPI_NOTREADY:
                USB_sendstr(I_CMD, "not ready");
            break;
            case SPI_BUSY:
                USB_sendstr(I_CMD, "busy");
            break;
            case SPI_READY:
                USB_sendstr(I_CMD, "ready");
            break;
        }
            newline(I_CMD);
    }
    return ERR_SILENCE;
}

static errcode_e spiinit(_U_ cmd_e idx, _U_ char *par){
    for(int i = 1; i < 3; ++i){
        USB_sendstr(I_CMD, "Init SPI");
        USB_putbyte(I_CMD, '0' + i);
        newline(I_CMD);
        spi_setup(i);
    }
    return ERR_SILENCE;
}

static errcode_e spideinit(_U_ cmd_e idx, _U_ char *par){
    for(int i = 1; i < 3; ++i){
        USB_sendstr(I_CMD, "DEinit SPI");
        USB_putbyte(I_CMD, '0' + i);
        newline(I_CMD);
        spi_deinit(i);
    }
    return ERR_SILENCE;
}

static errcode_e setuintpar(cmd_e idx, char *par){
    uint32_t val;
    if(par){
        if(par == getnum(par, &val)) return ERR_BADPAR;
        switch(idx){
            case C_br:
                if(val == 0 || val > 7) return ERR_BADPAR;
                the_conf.flags.BR = val;
                break;
            case C_encbits:
                if(val < ENCRESOL_MIN || val > ENCRESOL_MAX) return ERR_BADPAR; // don't support less than 8 of more than 32
                the_conf.encbits = val;
                break;
            case C_encbufsz:
                if(val < 8 || val > ENCODER_BUFSZ_MAX) return ERR_BADPAR;
                the_conf.encbufsz = val;
                break;
            case C_minzeros:
                if(val < MINZEROS_MIN || val > MINZEROS_MAX) return ERR_BADPAR;
                the_conf.minzeros = val;
                break;
            case C_maxzeros:
                if(val < MAXZEROS_MIN || val > MAXZEROS_MAX) return ERR_BADPAR;
                the_conf.maxzeros = val;
                break;
            case C_amperiod:
                if(val > 255 || val == 0) return ERR_BADPAR;
                the_conf.monittime = val;
                break;
            case C_ssii:
                if(val > UINT16_MAX) return ERR_BADPAR;
                the_conf.send232_interval = val;
                break;
            default:
                return ERR_BADCMD;
        }
    }
    switch(idx){
        case C_br:
            val = the_conf.flags.BR;
            break;
        case C_encbits:
            val = the_conf.encbits;
            break;
        case C_encbufsz:
            val = the_conf.encbufsz;
            break;
        case C_minzeros:
            val = the_conf.minzeros;
            break;
        case C_maxzeros:
            val = the_conf.maxzeros;
            break;
        case C_amperiod:
            val = the_conf.monittime;
            break;
        case C_ssii:
            val = the_conf.send232_interval;
            break;
        default:
            return ERR_BADCMD;
    }
    CMDWR(commands[idx].cmd);
    USB_putbyte(I_CMD, '=');
    CMDWR(u2str(val));
    CMDn();
    return ERR_SILENCE;
}

static errcode_e setboolpar(cmd_e idx, char *par){
    uint8_t val;
    if(par){
        if(*par != '0' && *par != '1') return ERR_BADPAR;
        val = *par - '0';
        switch(idx){
            case C_cpha:
                the_conf.flags.CPHA = val;
                break;
            case C_cpol:
                the_conf.flags.CPOL = val;
                break;
            case C_testX:
                user_pars.testx = val;
                if(val) spi_start_enc(0);
                break;
            case C_testY:
                user_pars.testy = val;
                if(val) spi_start_enc(1);
                break;
            case C_autom:
                the_conf.flags.monit = val;
                break;
            case C_debug:
                the_conf.flags.debug = val;
                break;
            default:
                return ERR_BADCMD;
        }
    }
    switch(idx){
        case C_cpha:
            val = the_conf.flags.CPHA;
            break;
        case C_cpol:
            val = the_conf.flags.CPOL;
            break;
        case C_testX:
            val = user_pars.testx;
            break;
        case C_testY:
            val = user_pars.testy;
            break;
        case C_autom:
            val = the_conf.flags.monit;
            break;
        case C_debug:
            val = the_conf.flags.debug;
            break;
        default:
            return ERR_BADCMD;
    }
    CMDWR(commands[idx].cmd);
    USB_putbyte(I_CMD, '=');
    USB_putbyte(I_CMD, '0' + val);
    CMDn();
    return ERR_SILENCE;
}

static errcode_e dumpconf(cmd_e _U_ idx, char _U_ *par){
    CMDWR("userconf_sz="); CMDWR(u2str(the_conf.userconf_sz));
    CMDWR("\ncurrentconfidx="); CMDWR(i2str(currentconfidx));
    CMDn();
    for(int i = 0; i < InterfacesAmount; ++i)
        setiface(C_setiface1 + i, NULL);
    setboolpar(C_autom, NULL);
    setuintpar(C_amperiod, NULL);
    setuintpar(C_br, NULL);
    setboolpar(C_cpha, NULL);
    setboolpar(C_cpol, NULL);
    setboolpar(C_debug, NULL);
    setuintpar(C_encbits, NULL);
    setuintpar(C_encbufsz, NULL);
    setuintpar(C_maxzeros, NULL);
    setuintpar(C_minzeros, NULL);
    setuintpar(C_ssii, NULL);
    return ERR_SILENCE;
}

static errcode_e usart(cmd_e _U_ idx, char _U_ *par){
    usart_send_enc(0xdeadbeef, 0xbeefdead);
    return ERR_OK;
}

// text commands
static const funcdescr_t commands[C_AMOUNT] = {
    [C_dummy] = {"dummy", dummy},
    [C_help] = {"help", help},
    [C_sendX] = {"sendx", sendenc},
    [C_sendY] = {"sendy", sendenc},
    [C_setiface1] = {"setiface1", setiface},
    [C_setiface2] = {"setiface2", setiface},
    [C_setiface3] = {"setiface3", setiface},
    [C_dumpconf] = {"dumpconf", dumpconf},
    [C_erasestorage] = {"erasestorage", erasestor},
    [C_storeconf] = {"storeconf", storeconf},
    [C_reset] = {"reset", reset},
    [C_fin] = {"fin", fini},
    [C_encstart] = {"readenc", encstart},
    [C_encX] = {"readX", encstart},
    [C_encY] = {"readY", encstart},
    [C_spistat] = {"spistat", spistat},
    [C_spiinit] = {"spiinit", spiinit},
    [C_spideinit] = {"spideinit", spideinit},
    [C_cpol] = {"CPOL", setboolpar},
    [C_cpha] = {"CPHA", setboolpar},
    [C_br] = {"BR", setuintpar},
    [C_encbits] = {"encbits", setuintpar},
    [C_testX] = {"testx", setboolpar},
    [C_testY] = {"testy", setboolpar},
    [C_encbufsz] = {"encbufsz", setuintpar},
    [C_minzeros] = {"minzeros", setuintpar},
    [C_maxzeros] = {"maxzeros", setuintpar},
    [C_autom] = {"autom", setboolpar},
    [C_amperiod] = {"amperiod", setuintpar},
    [C_usart] = {"usart", usart},
    [C_ssii] = {"ssii", setuintpar},
    [C_debug] = {"debug", setboolpar},
};

typedef struct{
    int idx;            // command index (if < 0 - just display `help` as grouping header)
    const char *help;   // help message
} help_t;

// SHOUL be sorted and grouped
static const help_t helpmessages[] = {
    {-1, "Base commands"},
    {C_encstart, "read both encoders once"},
    {C_encX, "read X encoder once"},
    {C_encY, "read Y encoder once"},
    {C_help, "show this help"},
    {C_reset, "reset MCU"},
    {C_spideinit, "deinit SPI"},
    {C_spiinit, "init SPI"},
    {C_spistat, "get status of both SPI interfaces"},
    {-1, "Configuration"},
    {C_autom, "turn on or off automonitoring"},
    {C_amperiod, "period (ms) of monitoring, 1..255"},
    {C_br, "change SPI BR register (1 - 18MHz ... 7 - 281kHz)"},
    {C_cpha, "change CPHA value (0/1)"},
    {C_cpol, "change CPOL value (0/1)"},
    {C_debug, "turn on debugging output of read encoders' values"},
    {C_dumpconf, "dump current configuration"},
    {C_encbits, "set encoder data bits amount (26/32)"},
    {C_encbufsz, "change encoder auxiliary buffer size (8..32 bytes)"},
    {C_erasestorage, "erase full storage or current page (=n)"},
    {C_maxzeros, "maximal amount of zeros in preamble"},
    {C_minzeros, "minimal amount of zeros in preamble"},
    {C_setiface1, "set name of first (command) interface"},
    {C_setiface2, "set name of second (axis X) interface"},
    {C_setiface3, "set name of third (axis Y) interface"},
    {C_ssii, "change interval (ms) of sending enc data to SSII (0 - don't send)"},
    {C_storeconf, "store configuration in flash memory"},
    {-1, "Debug commands"},
    {C_dummy, "dummy integer setter/getter"},
    {C_fin, "reinit flash"},
    {C_sendX, "send text string to X encoder's terminal"},
    {C_sendY, "send text string to Y encoder's terminal"},
    {C_testX, "test X-axis throughput"},
    {C_testY, "test Y-axis throughput"},
    {C_usart, "send test encoders data over usart"},
    {-1, NULL},
};

static errcode_e help(_U_ cmd_e idx, _U_ char*  par){
    CMDWRn("https://github.com/eddyem/stm32samples/tree/master/F1:F103/BISS_C_encoders "
#ifdef EBUG
            "debug "
#else
            "release "
#endif
           "build #" BUILD_NUMBER " @ " BUILD_DATE);
    CMDWRn("\ncommands format: 'command[=setter]\\n'");
    const help_t *c = helpmessages;
    while(c->help){
        if(c->idx < 0){ // header
            CMDWR("\n    ");
            CMDWR(c->help);
            USB_putbyte(I_CMD, ':');
        }else{
            CMDWR(commands[c->idx].cmd);
            CMDWR(" - ");
            CMDWR(c->help);
        }
        CMDn();
        ++c;
    }
    return ERR_SILENCE;
}

// *cmd is "command" for commands/getters or "parameter=value" for setters
void parse_cmd(char *cmd){
    errcode_e ecode = ERR_BADCMD;
    // command and its parameter
//CMDWRn(cmd);
    char *cmdstart = omit_spaces(cmd), *parstart = NULL;
    if(!cmdstart) goto retn;
    char *ptr = cmdstart;
    while(*ptr > ' ' && *ptr != '=') ++ptr;
    if(*ptr && *ptr <= ' '){ // there's spaces after command
        *ptr++ = 0;
        ptr = omit_spaces(ptr);
    }
    if(*ptr){ // check rest of string
        if(*ptr != '=') goto retn; // strange symbols after command name
        *ptr++ = 0;
        parstart = omit_spaces(ptr); // get parameter
        if(*parstart <= ' ') goto retn; // no parameter after equal sign
    }
    int idx = 0;
    for(; idx < C_AMOUNT; ++idx) // search in command lists
        if(0 == strcmp(commands[idx].cmd, cmdstart)) break;
    if(idx >= C_AMOUNT) goto retn; // not found
    ecode = commands[idx].handler(idx, parstart);
retn:
    if(ecode == ERR_BADCMD){
        CMDWR(cmd);
        USB_putbyte(I_CMD, '=');
        CMDWRn(errors[ERR_BADCMD]);
    } else if(ecode != ERR_SILENCE) CMDWRn(errors[ecode]);
}
