#include <cmdHelper.h>
#include <logger.h>

#include <eeprom.h>
#include <config.h>

#include <string.h>
#include <stdlib.h>



static bool showConfigCmd(uint8_t argc, char **args) {
    bool retCode = true;

    t_configBlock configBlock;
    eepromReadConfigBlock(&configBlock);
    sendString("This is the saved configuration.\n\r");
    sendString("It is not necessarily the active configuration.\n\r");
    sendFormatString("configMagic:         %lx\n\r", configBlock.configMagic);
    sendFormatString("deviceName:          %s\n\r", configBlock.deviceName);
    sendFormatString("MAC address:         %02x:%02x:%02x:%02x:%02x:%02x\n\r", configBlock.macAddress[0], 
                                                                           configBlock.macAddress[1], 
                                                                           configBlock.macAddress[2], 
                                                                           configBlock.macAddress[3], 
                                                                           configBlock.macAddress[4], 
                                                                           configBlock.macAddress[5]);
    sendFormatString("frontend threshold:  %ld\n\r", configBlock.frontendThreshold);
    sendFormatString("broker:              %s\n\r", configBlock.brokerName);
    sendFormatString("watchdogTopic:       %s\n\r", configBlock.watchdogTopic);
    sendFormatString("startupTopic:        %s\n\r", configBlock.startupTopic);
    sendFormatString("statusTopic:         %s\n\r", configBlock.statusTopic);
    sendFormatString("mbusDataTopic:       %s\n\r", configBlock.mbusDataTopic);
    sendFormatString("syslog server:       %s\n\r", configBlock.syslogServerName);
    sendFormatString("device block cnt:    %d\n\r", configBlock.numOfDeviceBlocks);

    for (uint8_t i = 0; i < configBlock.numOfDeviceBlocks; i++) {
        t_deviceBlock tmpDeviceBlock;
        eepromReadDeviceBlock(i, &tmpDeviceBlock);
        if (tmpDeviceBlock.deviceMagic == DEVICE_MAGIC) {
            sendFormatString("device %d: \n\r", i);
            sendFormatString("  Name: %s, Address: %d, Period: %d\n\r", 
                       tmpDeviceBlock.deviceName, tmpDeviceBlock.address, tmpDeviceBlock.period);
            sendFormatString("  Considered Fields: %d %d %d %d\n\r",
                       tmpDeviceBlock.consideredField[0],
                       tmpDeviceBlock.consideredField[1],
                       tmpDeviceBlock.consideredField[2],
                       tmpDeviceBlock.consideredField[3]);
            if (tmpDeviceBlock.deviceMagic != DEVICE_MAGIC) {
                sendString("  DEVICE MAGIC DOES NOT MATCH\n\r");
            }
        }
    }

    return retCode;
}




static bool setStringParameterCmd(uint8_t argc, char **args, size_t offset, size_t length) {
    bool retCode = true;

    t_configBlock configBlock;
    char *parameterName = args[1];
    char *newParameterValue = args[2];
    if (strlen(newParameterValue) >= length) {
        sendString("given new value for is too long\n\r");
        retCode = false;
    } else {
        sendFormatString("set %s to %s\n\r", parameterName, newParameterValue);

        eepromReadConfigBlock(&configBlock);
        strcpy((((char*)&configBlock) + offset), newParameterValue);
        eepromWriteConfigBlock(&configBlock);
    }

    return retCode;
}

static bool setInt32ParameterCmd(uint8_t argc, char **args, size_t offset, int32_t minV, int32_t maxV) {
    bool retCode = true;

    t_configBlock configBlock;
    char *parameterName = args[1];
    char *newParameterValue = args[2];
    long int value = strtol(newParameterValue, NULL, 10);
    if (value < minV) {
        sendString("value is too small\n\r");
        retCode = false;
    } else if (value > maxV) {
        sendString("value is too large\n\r");
    } else {
        int32_t v = (int32_t) value;
        sendFormatString("set %s to %ld\n\r", parameterName, v);

        eepromReadConfigBlock(&configBlock);
        *((int32_t*)(((uint8_t*)&configBlock) + offset)) = v;
        eepromWriteConfigBlock(&configBlock);
    }

    return retCode;
}


static bool setDeviceNameCmd(uint8_t argc, char **args) {
    return setStringParameterCmd(argc, args, 
                                 offsetof(t_configBlock, deviceName), 
                                 sizeof(((t_configBlock*)0)->deviceName));
}

static bool setBrokerNameCmd(uint8_t argc, char **args) {
    return setStringParameterCmd(argc, args, 
                                 offsetof(t_configBlock, brokerName), 
                                 sizeof(((t_configBlock*)0)->brokerName));
}

static bool setSyslogServerCmd(uint8_t argc, char **args) {
    return setStringParameterCmd(argc, args, 
                                 offsetof(t_configBlock, syslogServerName), 
                                 sizeof(((t_configBlock*)0)->syslogServerName));
}

static bool setWatchdogTopicCmd(uint8_t argc, char **args) {
    return setStringParameterCmd(argc, args, 
                                 offsetof(t_configBlock, watchdogTopic), 
                                 sizeof(((t_configBlock*)0)->watchdogTopic));
}

static bool setStartupTopicCmd(uint8_t argc, char **args) {
    return setStringParameterCmd(argc, args, 
                                 offsetof(t_configBlock, startupTopic), 
                                 sizeof(((t_configBlock*)0)->startupTopic));
}

static bool setStatusTopicCmd(uint8_t argc, char **args) {
    return setStringParameterCmd(argc, args, 
                                 offsetof(t_configBlock, statusTopic), 
                                 sizeof(((t_configBlock*)0)->statusTopic));
}

static bool setMbusDataTopicCmd(uint8_t argc, char **args) {
    return setStringParameterCmd(argc, args, 
                                 offsetof(t_configBlock, mbusDataTopic), 
                                 sizeof(((t_configBlock*)0)->mbusDataTopic));
}

static bool setFrontendThresholdCmd(uint8_t argc, char **args) {
    return setInt32ParameterCmd(argc, args,
                                offsetof(t_configBlock, frontendThreshold),
                                0, 1023);
}

static bool makeDevice(uint8_t argOffset, uint8_t argc, char **args, t_deviceBlock *deviceBlock) {
    if (strcmp(args[1], "help") == 0) {
        sendString("deviceName address period field1 field2 field3 field4\n\r");
        sendString("deviceName: max. length = 16\n\r");
        sendString("address: between 1 and 254\n\r");
        sendString("period: in seconds, between 0 (disabled) and 86400 (1 day)\n\r");
        sendString("fields: between -1 (not considered) and 254\n\r");
        return false;
    }


    if ((argc - argOffset) != 8) {
        sendString("wrong number of arguments\n\r");
        return false;
    }

    char *deviceName = args[1 + argOffset];
    if (strcmp(deviceName, "*") != 0) {
        if (strlen(deviceName) >= sizeof(deviceBlock->deviceName)) {
            sendString("devicename too long\n\r");
            return false;
        }
        strcpy(deviceBlock->deviceName, deviceName);
    }

    char *rawAddressStr = args[2 + argOffset];
    if (strcmp(rawAddressStr, "*") != 0) {
        long int rawAddress = strtol(rawAddressStr, NULL, 10);
        if (rawAddress < 1 || rawAddress > 254) {
            sendString("illegal address\n\r");
            return false;
        }
        deviceBlock->address = (uint8_t)rawAddress;
    }

    char *rawPeriodStr = args[3 + argOffset];
    if (strcmp(rawPeriodStr, "*") != 0) {
        long int rawPeriod = strtol(rawPeriodStr, NULL, 10);
        if (rawPeriod < 0 || rawPeriod > 86400) {
            sendString("illegal period\n\r");
            return false;
        }
        deviceBlock->period = (int32_t) rawPeriod;
    }

    for (uint8_t i = 0; i < MBUSDEVICE_NUM_OF_CONSIDEREDFIELDS; i++) {
        char *rawFieldNumStr = args[4 + i + argOffset];
        if (strcmp(rawFieldNumStr, "*") != 0) {
            long int rawFieldNum = strtol(rawFieldNumStr, NULL, 10);
            if (rawFieldNum < -1 || rawFieldNum > 127) {
                sendString("illegal considered field index\n\r");
                return false;
            }
            deviceBlock->consideredField[i] = (int8_t) rawFieldNum;
        }
    }

    return true;
}

static bool addDeviceCmd(uint8_t argc, char **args) {
    t_deviceBlock deviceBlock = { .deviceName = "", .address = 0, .period = 0, .consideredField = { -1, -1, -1, -1}};
    bool retCode = makeDevice(0, argc, args, &deviceBlock);
    if (retCode) {
        sendString("New device would be:\n\r");
        sendFormatString("  Name: %s, Address: %d, Period: %d\n\r", 
                         deviceBlock.deviceName, deviceBlock.address, deviceBlock.period);
        for (uint8_t i = 0; i < MBUSDEVICE_NUM_OF_CONSIDEREDFIELDS; i++) {
            sendFormatString("  Considered field: %d\n\r", deviceBlock.consideredField[i]);
        }
    }
    deviceBlock.deviceMagic = DEVICE_MAGIC;

    uint8_t index = getConfig()->numOfDeviceBlocks;
    for (uint8_t i = 0; i < getConfig()->numOfDeviceBlocks; i++) {
        t_deviceBlock tmpDeviceBlock;
        eepromReadDeviceBlock(i, &tmpDeviceBlock);
        if (tmpDeviceBlock.period == -1) {
            index = i;
            break;
        }
    }
    eepromWriteDeviceBlock(index, &deviceBlock);
    if (index == getConfig()->numOfDeviceBlocks) {
        t_configBlock configBlock;
        eepromReadConfigBlock(&configBlock);
        configBlock.numOfDeviceBlocks += 1;
        eepromWriteConfigBlock(&configBlock);
    }

    return retCode;
}

static bool deleteDeviceCmd(uint8_t argc, char **args) {
    long int rawIndex = strtol(args[1], NULL, 10);
    if (rawIndex < 0 || rawIndex > getConfig()->numOfDeviceBlocks) {
        sendFormatString("illegal index, must be greater 0 and less %d\n\r", getConfig()->numOfDeviceBlocks);
        return false;
    }
    uint8_t index = (uint8_t) rawIndex;

    t_deviceBlock deviceBlock;
    eepromReadDeviceBlock(index, &deviceBlock);
    deviceBlock.period = -1;
    eepromWriteDeviceBlock(index, &deviceBlock);

    return true;
}

static bool changeDeviceCmd(uint8_t argc, char **args) {
    if (strcmp(args[1], "help") == 0) {
        sendString("First argument: index of device in list\n\r");
        sendFormatString("Between 0 and %d\n\r", getConfig()->numOfDeviceBlocks);
        sendString("For further arguments use a * to keep the value\n\r");
        return makeDevice(0, argc, args, NULL);
    }

    long int rawIndex = strtol(args[1], NULL, 10);
    if (rawIndex < 0 || rawIndex > getConfig()->numOfDeviceBlocks) {
        sendFormatString("illegal index, must be greater 0 and less %d\n\r", getConfig()->numOfDeviceBlocks);
        return false;
    }
    uint8_t index = (uint8_t) rawIndex;

    t_deviceBlock deviceBlock;
    eepromReadDeviceBlock(index, &deviceBlock);

    bool retCode = makeDevice(1, argc, args, &deviceBlock);
    if (retCode) {
        sendString("Changed device will be:\n\r");
        sendFormatString("  Index: %d\n\r", index);
        sendFormatString("  Name: %s, Address: %d, Period: %d\n\r", 
                         deviceBlock.deviceName, deviceBlock.address, deviceBlock.period);
        for (uint8_t i = 0; i < MBUSDEVICE_NUM_OF_CONSIDEREDFIELDS; i++) {
            sendFormatString("  Considered field: %d\n\r", deviceBlock.consideredField[i]);
        }
        if (deviceBlock.period == 0) {
            sendString("  Device is marked as inactive\n\r");
        }
        eepromWriteDeviceBlock(index, &deviceBlock);
    }

    return retCode;
}

static bool listDevicesCmd(uint8_t argc, char **args) {
    for (uint8_t i = 0; i < getConfig()->numOfDeviceBlocks; i++) {
        t_deviceBlock deviceBlock;
        eepromReadDeviceBlock(i, &deviceBlock);
        sendFormatString("Index: %d\n\r", i);
        sendFormatString("  Name: %s, Address: %d, Period: %d\n\r", 
                         deviceBlock.deviceName, deviceBlock.address, deviceBlock.period);
        for (uint8_t i = 0; i < MBUSDEVICE_NUM_OF_CONSIDEREDFIELDS; i++) {
            sendFormatString("  Considered field: %d\n\r", deviceBlock.consideredField[i]);
        }
        if (deviceBlock.period == 0) {
            sendString("  Device is marked as inactive\n\r");
        } else if (deviceBlock.period == -1) {
            sendString("  Device is marked as deleted\n\r");
        }
    }
    return true;
}


const static cmd_t SET_COMMANDS[] = {
    { .name = "devicename", .cmdFunc = setDeviceNameCmd,
      .help = \
        "devicename ........................... Name of this device\n\r"
    },
    { .name = "brokername", .cmdFunc = setBrokerNameCmd,
      .help = \
        "brokername ........................... Hostname of the MQTT broker\n\r"
    },
    { .name = "syslogserver", .cmdFunc = setSyslogServerCmd,
      .help = \
        "syslogserver ......................... Hostname of the Syslog server\n\r"
    },
    { .name = "watchdogtopic", .cmdFunc = setWatchdogTopicCmd,
      .help = \
        "watchdogtopic ........................ Watchdog Topic\n\r"
    },
    { .name = "startuptopic", .cmdFunc = setStartupTopicCmd,
      .help = \
        "startuptopic ......................... Startup Topic\n\r"
    },
    { .name = "statustopic", .cmdFunc = setStatusTopicCmd,
      .help = \
        "statustopic .......................... Status Topic\n\r"
    },
    { .name = "mbusdatatopic", .cmdFunc = setMbusDataTopicCmd,
      .help = \
        "mbusdatatopic ........................ MBus Data Topic\n\r"
    },
    { .name = "frontendthreshold", .cmdFunc = setFrontendThresholdCmd,
      .help = \
        "frontendthreshold .................... Frontend Threshold (default: 240)\n\r"
    },
    { .name = "END_OF_CMDS", .help = "",.cmdFunc = NULL }
};

const static char UNKNOWN_PARAMETER[] = "unknown parameter\n\r";
const static char OK_MSG[] = "OK\n\r";
const static char FAILED_MSG[] = "Failed\n\r";

static bool setCmd(uint8_t argc, char **args) {
    bool retCode = false;
    uint8_t *messageToSend = NULL;

    char *cmd = args[1];
    if (argc >= 2) {
        if (0 == strcmp("help", cmd)) {
            sendString("You can set the following parameters:\n\r");
            uint8_t cmdIdx = 0;
            while (true) {
                cmd_t command = SET_COMMANDS[cmdIdx];
                if (0 == strcmp("END_OF_CMDS", command.name)) {
                    break;
                }
                sendString(command.help);
                cmdIdx++;
            }
            retCode = true;
        } else {
            uint8_t cmdIdx = 0;
            while (true) {
                cmd_t command = SET_COMMANDS[cmdIdx];
                if (0 == strcmp("END_OF_CMDS", command.name)) {
                    messageToSend = (uint8_t*) UNKNOWN_PARAMETER;
                    break;
                }
                if (0 == strcmp(cmd, command.name)) {
                    retCode = command.cmdFunc(argc, args);
                    messageToSend =  retCode ? (uint8_t*)OK_MSG : (uint8_t*)FAILED_MSG;
                    sendString("Remember you need to restart to active this change.\n\r");
                    break;
                }
                cmdIdx++;
            }
            sendString((const char*)messageToSend);
        }
    }

    return retCode;
}

static bool restartCmd(uint8_t argc, char **args) {
    HAL_NVIC_SystemReset();
    // you won't come here ...
    return true;
}



const cmd_t CONFIG_COMMANDS[] = {
    { .name = "show", .cmdFunc = showConfigCmd,
      .help = \
        "show ................................. Show the configuration\n\r"
    },
    { .name = "set", .cmdFunc = setCmd,
      .help = \
        "set .................................. Set configuration parameters\n\r" \
        "                                       Argument help gives a list of \n\r" \
        "                                       parameters\n\r"
    },
    { .name = "addDevice", .cmdFunc = addDeviceCmd,
      .help = \
        "addDevice ............................ Add a new device to the end of the list\n\r"
    },
    { .name = "changeDevice", .cmdFunc = changeDeviceCmd,
      .help = \
        "changeDevice ......................... Change a new device by index\n\r"
    },
    { .name = "listDevices", .cmdFunc = listDevicesCmd,
      .help = \
        "listDevices .......................... List the configured devices\n\r"
    },
    { .name = "deleteDevice", .cmdFunc = deleteDeviceCmd,
      .help = \
        "deleteDevice ......................... Delete a device\n\r" \
        "                                       Argument: index\n\r"
    },
    { .name = "restart", .cmdFunc = restartCmd,
      .help = \
        "restart .............................. Restart the system,\n\r" \
        "                                       Required to reload config\n\r"
    },
    { .name = "END_OF_CMDS", .help = "",.cmdFunc = NULL }
};



const cmd_t *getConfigCommands() {
    return CONFIG_COMMANDS;
}