#include "config.h"
#include "ch32v30x_flash.h"
#include "ch32v30x_rng.h"
#include "lib/cifra/sha2.h"
#include "lib/monocypher/monocypher-ed25519.h"
#include "sx1262.h"
#include "util/hexdump.h"
#include "util/log.h"

PersistentData_t persistent;

LoRaSettings currentLoRaSettings;

#define TAG "Config"

NodeEntry *getNextNode() {
    uint32_t oldest_timestamp = UINT32_MAX;
    NodeEntry *selectedNode = &(persistent.contacts[0]);
    for (int i = 0; i < CONTACT_COUNT; i++) {
        NodeEntry *curNode = &(persistent.contacts[i]);
        if (curNode->flags & NODE_ENTRY_FAV_FLAG) {
            continue;
        }
        if (curNode->last_seen_lt == 0) {
            selectedNode = curNode;
            break;
        }
        if (curNode->last_seen_lt < oldest_timestamp) {
            oldest_timestamp = curNode->last_seen_lt;
            selectedNode = curNode;
        }
    }
    return selectedNode;
}

NodeEntry *getNode (uint8_t hash) {
    NodeEntry *selectedNode = NULL;
    for (int i = 0; i < CONTACT_COUNT; i++) {
        NodeEntry *curNode = &(persistent.contacts[i]);
        if (curNode->pubKey[0] == hash) {
            selectedNode = curNode;
            break;
        }
    }
    return selectedNode;
}

NodeEntry *getNodePrefix (const uint8_t *hash) {
    NodeEntry *selectedNode = NULL;
    for (int i = 0; i < CONTACT_COUNT; i++) {
        NodeEntry *curNode = &(persistent.contacts[i]);
        if (memcmp (curNode->pubKey, hash, 4)) {
            selectedNode = curNode;
            break;
        }
    }
    return selectedNode;
}

Channel *getChannel (uint8_t hash, uint8_t ignoreCount) {
    Channel *selectedChannel = NULL;
    Channel *finalChannel = NULL;
    uint8_t matchCount = 0;
    for (int i = 0; i < ChannelCount; i++) {
        Channel *curChannel = &(persistent.channels[i]);
        if (curChannel->hash == hash) {
            selectedChannel = curChannel;
            if (++matchCount > ignoreCount) {
                finalChannel = selectedChannel;
                break;
            }
        }
    }
    return finalChannel;
}

void addChannel (char *name, const uint8_t *key) {

    uint32_t oldest_timestamp = UINT32_MAX;
    Channel *selectedChannel = &(persistent.channels[0]);
    for (int i = 0; i < ChannelCount; i++) {
        Channel *curChan = &(persistent.channels[i]);

        if (curChan->timestamp == 0) {
            selectedChannel = curChan;
            MESH_LOGD (TAG, "Deciding on channel index %d because of timestamp 0, name is %s", i, name);
            break;
        }
        if (strlen (curChan->name) == 0) {
            selectedChannel = curChan;
            MESH_LOGD (TAG, "Deciding on channel index %d because of name len 0, name is %s", i, name);
            break;
        }

        if (curChan->timestamp < oldest_timestamp) {
            oldest_timestamp = curChan->timestamp;
            selectedChannel = curChan;
        }
    }


    memset (selectedChannel->name, 0, sizeof (selectedChannel->name));
    strncpy (selectedChannel->name, name, sizeof (selectedChannel->name));
    memcpy (selectedChannel->key, key, sizeof (selectedChannel->key));
    // Buffer for the digest
    uint8_t hash[CF_SHA256_HASHSZ];

    // Context
    cf_sha256_context ctx;

    // 1. Initialize
    cf_sha256_init (&ctx);

    // 2. Feed in your data
    cf_sha256_update (&ctx, selectedChannel->key, sizeof (selectedChannel->key));

    // 3. Compute digest
    cf_sha256_digest (&ctx, hash);
    selectedChannel->hash = hash[0];
    selectedChannel->timestamp = RTC_GetCounter();
}

void printNodeDB() {
    puts ("Node database:");
    for (int i = 0; i < CONTACT_COUNT; i++) {
        const NodeEntry *node = &(persistent.contacts[i]);
        if (node->last_seen_lt == 0)
            continue;  // skip inactive nodes

        iprintf ("Node %d:\n", i);
        iprintf ("  Name: %s\n", node->name);
        hexdump ("Pubkey", node->pubKey, sizeof (node->pubKey));
        hexdump ("Secret", node->secret, sizeof (node->secret));
        iprintf ("\n");

        iprintf ("  GPS: lat=%d, lon=%d\n", node->gps_latitude, node->gps_longitude);
        iprintf ("  Path: ... (not expanded, add if needed)\n");
        iprintf ("  Flags: 0x%02X\n", node->flags);
        iprintf ("  Type: 0x%02X\n", node->type);
        iprintf ("  Authenticated: %s\n", node->authenticated ? "Yes" : "No");
        iprintf ("  Last Seen (remote ts): %d\n", node->last_seen_rt);
        iprintf ("  Last Seen (local ts): %d\n", node->last_seen_lt);
        iprintf ("  Sync timestamp: %d\n", node->sync_timestamp);
        iprintf ("--------------------------------------\n");
    }
}

void loadConfig() {
    memcpy (&persistent, FLASH_USER_PAGE_ADDR, sizeof (persistent));
    memcpy(&currentLoRaSettings, &(persistent.loraSettings), sizeof(currentLoRaSettings));
    uint32_t crcSum = *((uint32_t *)(((uint8_t *)&persistent) + (sizeof (persistent) - 2)));
    memset ((((uint8_t *)&persistent) + (sizeof (persistent) - sizeof (crcSum))), 0, 4);
    CRC_ResetDR();
    uint32_t currentSum = CRC_CalcBlockCRC ((uint32_t *)&persistent, sizeof (persistent) - 2);

    if (currentSum != crcSum) {
        memset (&persistent, 0, sizeof (persistent));
    }
}

void saveConfig() {
    CRC_ResetDR();
    uint32_t currentSum = CRC_CalcBlockCRC ((uint32_t *)&persistent, sizeof (persistent) - 2);
    memcpy ((((uint8_t *)&persistent) + (sizeof (persistent) - sizeof (currentSum))), (uint8_t *)currentSum, 4);
    FLASH_Unlock();
    FLASH_ErasePage_Fast (1919);
    FLASH_ProgramPage_Fast (1919, (uint32_t *)&persistent);
    FLASH_Lock();
}

void genSeed (uint8_t *seedOut) {
    RCC_AHBPeriphClockCmd (RCC_AHBPeriph_RNG, ENABLE);
    RNG_Cmd (ENABLE);
    uint32_t random;
    for (uint8_t i = 0; i < 8; i++) {
        while (RNG_GetFlagStatus (RNG_FLAG_DRDY) == RESET) {
            Delay_Ms(10);
        }
        random = RNG_GetRandomNumber();

        memcpy (&(seedOut[i * 4]), &random, sizeof (random));
    }
    RCC_AHBPeriphClockCmd (RCC_AHBPeriph_RNG, DISABLE);
    RNG_Cmd (DISABLE);
}

const uint8_t publicChannelPSK[16] = {0x8b, 0x33, 0x87, 0xe9, 0xc5, 0xcd, 0xea, 0x6a, 0xc9, 0xe5, 0xed, 0xba, 0xa1, 0x15, 0xcd, 0x72};
const uint8_t BRNTestChannelPSK[16] = {0x44, 0x81, 0xda, 0x0e, 0x4e, 0x03, 0xc4, 0x9e, 0x84, 0x77, 0x25, 0xd8, 0x3a, 0x93, 0xbf, 0x80};

const char *getStringRole (uint8_t role) {
    switch (role) {
    case NODE_TYPE_CHAT_NODE:
        return "Chat node";

    case NODE_TYPE_REPEATER:
        return "Repeater";

    case NODE_TYPE_ROOM_SERVER:
        return "Room server";

    case NODE_TYPE_SENSOR:
        return "Sensor";

    default:
        return "Unknown";
    }
}

void populateDefaults() {

    uint8_t seed[32];
    //memcpy(seed, "vFt0FRugSOeqnkshImMCVfgHM5vBxz4", 32); //chat node identity
    //memcpy (seed, "vFt0FRugSOeqnkshImMCVfgHM5vBxz3", 32);  // repeater identity
    memcpy (seed, "vFt0FRugSOeqnkshImMCVfgHM5vBxy1", 32);  // repeater identity
    // genSeed(seed); //random identity

    crypto_ed25519_key_pair(persistent.privkey, persistent.pubkey, seed);


    persistent.nodeType = NODE_TYPE_CHAT_NODE;
    //persistent.nodeType = NODE_TYPE_REPEATER;
    memset (persistent.password, 0, sizeof (persistent.password));
    strcpy (persistent.password, "hesielko");
    //strcpy (persistent.nodeName, "BRN WCHNode RISCV");
    strcpy (persistent.nodeName, "BRN WCH Mini");

    persistent.adcMultiplier = 0;

    persistent.loraSettings.txPowerInDbm = 20;
    persistent.loraSettings.frequencyInHz = 869554000;
    persistent.loraSettings.spreadingFactor = 8;
    persistent.loraSettings.bandwidth = SX126X_LORA_BW_62_5;
    persistent.loraSettings.codingRate = SX126X_LORA_CR_4_8;
    persistent.loraSettings.preambleLength = 16;
    persistent.loraSettings.tcxoVoltage = 2200;  // ebyte
    // persistent.tcxoVoltage = 1800;  // heltec

    memcpy(&currentLoRaSettings, &(persistent.loraSettings), sizeof(currentLoRaSettings));

    addChannel ("Public", publicChannelPSK);
    addChannel ("BRNTest", BRNTestChannelPSK);

    persistent.doRepeat = 1;
    persistent.allowReadOnly = 1;


    persistent.latitude = 48190900;
    persistent.longitude = 17030300;
    persistent.altitude = 23400;
}

void LoraApply() {

    MESH_LOGW (TAG, "LoraInit");
    LoRaInit();


    char useRegulatorLDO = 0;

    LoRaDebugPrint (0);
    uint16_t loraBeginStat = LoRaBegin (currentLoRaSettings.frequencyInHz, currentLoRaSettings.txPowerInDbm, currentLoRaSettings.tcxoVoltage, useRegulatorLDO);
    if (loraBeginStat != 0) {
        MESH_LOGE (TAG, "Does not recognize the module");
        while (1) {
            Delay_Ms(1000);
            MESH_LOGE (TAG, "CRITICAL: LoRa not found, halted");
        }
    }

    char crcOn = 1;
    char invertIrq = 0;

    LoRaConfig (currentLoRaSettings.spreadingFactor, currentLoRaSettings.bandwidth, currentLoRaSettings.codingRate, currentLoRaSettings.preambleLength, 0, crcOn, invertIrq);
}