#include "hwdata.h"
#include "esp_err.h"
#include "esp_log.h"
#include "bottom_half.h"
#include "char_lcd.h"
#include "../helper.h"
#include "nvs.h"

static const char* TAG = "hwdata";

HWData::HWData() 
    : compat_mode(true) 
{}

HWData::HWData(HWData1 data, bool compat_mode)
    : compat_mode(compat_mode),
      inner(data) 
{}

esp_err_t HWData::save(nvs_handle_t handle, bool force) {
    if (compat_mode && !force) {
        ESP_LOGW(TAG, "Not saving due to being in compatability mode.");
        return ESP_OK;
    }
    return inner.save(handle);
}

HWData HWData::load(nvs_handle_t handle) {
    esp_err_t err;

    uint16_t stored_version = 0;
    err = nvs_get_u16(handle, "version", &stored_version);
    if (err == ESP_ERR_NVS_NOT_FOUND) {
        ESP_LOGE(TAG, "No NVS data found! using defaults");
        return HWData();
    } else if (err != ESP_OK) {
        ESP_ERROR_CHECK_WITHOUT_ABORT(err);
        ESP_LOGE(TAG, "Other esp error! using defaults");
        return HWData();
    }

    HWData1 data;
    switch (stored_version) {
        case 0:
            ESP_LOGE(TAG, "HWData version was 0! using defaults");
            return HWData();
        case 1:
            data.load(handle);
            return HWData(data, false);
        default:
            ESP_LOGW(TAG, "Max currently supported version is %d, but saved version is %d. Loading version %d anyway!", CURRENT_HWDATA_VERSION, stored_version, CURRENT_HWDATA_VERSION);
            data.load(handle);
            return HWData(data, true);
    }
}

HWData1::HWData1() {}

esp_err_t HWData1::save(nvs_handle_t handle) const {
    ESP_ERROR_CHECK(nvs_set_u16(handle, "version", 1));

    // Serial number
    ESP_ERROR_CHECK(nvs_set_str(handle, "serial_num", serial_num.c_str()));

    // Revisions
    ESP_ERROR_CHECK(nvs_set_u8(handle, "rev_ctrl_maj", rev_ctrl_maj));
    ESP_ERROR_CHECK(nvs_set_u8(handle, "rev_ctrl_min", rev_ctrl_min));
    ESP_ERROR_CHECK(nvs_set_u8(handle, "rev_exp_maj", rev_exp_maj));
    ESP_ERROR_CHECK(nvs_set_u8(handle, "rev_exp_min", rev_exp_min));
    ESP_ERROR_CHECK(nvs_set_u8(handle, "rev_ft_maj", rev_ft_maj));
    ESP_ERROR_CHECK(nvs_set_u8(handle, "rev_ft_min", rev_ft_min));
    ESP_ERROR_CHECK(nvs_set_u8(handle, "rev_fb_maj", rev_fb_maj));
    ESP_ERROR_CHECK(nvs_set_u8(handle, "rev_fb_min", rev_fb_min));

    // Enums
    ESP_ERROR_CHECK(nvs_set_u8(handle, "sseg_color_t", static_cast<uint8_t>(sseg_color_t)));
    ESP_ERROR_CHECK(nvs_set_u8(handle, "sseg_color_b", static_cast<uint8_t>(sseg_color_b)));
    ESP_ERROR_CHECK(nvs_set_u8(handle, "lcd_color", static_cast<uint8_t>(lcd_color)));
    ESP_ERROR_CHECK(nvs_set_u8(handle, "button_type", static_cast<uint8_t>(button_type)));
    ESP_ERROR_CHECK(nvs_set_u8(handle, "tft_type", static_cast<uint8_t>(tft_type)));
    ESP_ERROR_CHECK(nvs_set_u8(handle, "bat_type", static_cast<uint8_t>(bat_type)));
    ESP_ERROR_CHECK(nvs_set_u8(handle, "shape1", static_cast<uint8_t>(shape1)));
    ESP_ERROR_CHECK(nvs_set_u8(handle, "shape2", static_cast<uint8_t>(shape2)));
    ESP_ERROR_CHECK(nvs_set_u8(handle, "shape3", static_cast<uint8_t>(shape3)));
    ESP_ERROR_CHECK(nvs_set_u8(handle, "shape4", static_cast<uint8_t>(shape4)));

    // Other fields
    ESP_ERROR_CHECK(nvs_set_u8(handle, "switch_pos", switch_pos));
    ESP_ERROR_CHECK(nvs_set_u8(handle, "has_speaker", has_speaker));
    ESP_ERROR_CHECK(nvs_set_u8(handle, "has_mic", has_mic));
    ESP_ERROR_CHECK(nvs_set_u8(handle, "has_ir", has_ir));
    ESP_ERROR_CHECK(nvs_set_u8(handle, "has_rfid", has_rfid));
    ESP_ERROR_CHECK(nvs_set_u8(handle, "has_fp", has_fp));
    ESP_ERROR_CHECK(nvs_set_u8(handle, "has_fp_hall", has_fp_hall));
    ESP_ERROR_CHECK(nvs_set_u8(handle, "has_close_hall", has_close_hall));

    // Battery capacity
    ESP_ERROR_CHECK(nvs_set_u16(handle, "bat_cap", bat_cap));

    return nvs_commit(handle);
}

void HWData1::load(nvs_handle_t handle) {
    char buf[128];
    size_t required_size = sizeof(buf);
    esp_err_t err = nvs_get_str(handle, "serial_num", buf, &required_size);
    serial_num = (err == ESP_OK) ? std::string(buf) : "";

    nvs_get_u8(handle, "rev_ctrl_maj", &rev_ctrl_maj);
    nvs_get_u8(handle, "rev_ctrl_min", &rev_ctrl_min);
    nvs_get_u8(handle, "rev_exp_maj", &rev_exp_maj);
    nvs_get_u8(handle, "rev_exp_min", &rev_exp_min);
    nvs_get_u8(handle, "rev_ft_maj", &rev_ft_maj);
    nvs_get_u8(handle, "rev_ft_min", &rev_ft_min);
    nvs_get_u8(handle, "rev_fb_maj", &rev_fb_maj);
    nvs_get_u8(handle, "rev_fb_min", &rev_fb_min);

    uint8_t tmp;
    if (nvs_get_u8(handle, "sseg_color_t", &tmp) == ESP_OK) sseg_color_t = static_cast<SSegColor>(tmp);
    if (nvs_get_u8(handle, "sseg_color_b", &tmp) == ESP_OK) sseg_color_b = static_cast<SSegColor>(tmp);
    if (nvs_get_u8(handle, "lcd_color", &tmp) == ESP_OK) lcd_color = static_cast<LCDColor>(tmp);
    if (nvs_get_u8(handle, "button_type", &tmp) == ESP_OK) button_type = static_cast<ButtonType>(tmp);
    if (nvs_get_u8(handle, "tft_type", &tmp) == ESP_OK) tft_type = static_cast<TFTType>(tmp);
    if (nvs_get_u8(handle, "bat_type", &tmp) == ESP_OK) bat_type = static_cast<BatType>(tmp);
    nvs_get_u16(handle, "bat_cap", &bat_cap);

    if (nvs_get_u8(handle, "shape1", &tmp) == ESP_OK) shape1 = static_cast<ShapeType>(tmp);
    if (nvs_get_u8(handle, "shape2", &tmp) == ESP_OK) shape2 = static_cast<ShapeType>(tmp);
    if (nvs_get_u8(handle, "shape3", &tmp) == ESP_OK) shape3 = static_cast<ShapeType>(tmp);
    if (nvs_get_u8(handle, "shape4", &tmp) == ESP_OK) shape4 = static_cast<ShapeType>(tmp);

    nvs_get_u8(handle, "switch_pos", &switch_pos);
    nvs_get_u8(handle, "has_speaker", &tmp); has_speaker = tmp;
    nvs_get_u8(handle, "has_mic", &tmp); has_mic = tmp;
    nvs_get_u8(handle, "has_ir", &tmp); has_ir = tmp;
    nvs_get_u8(handle, "has_rfid", &tmp); has_rfid = tmp;
    nvs_get_u8(handle, "has_fp", &tmp); has_fp = tmp;
    nvs_get_u8(handle, "has_fp_hall", &tmp); has_fp_hall = tmp;
    nvs_get_u8(handle, "has_close_hall", &tmp); has_close_hall = tmp;
}

static void handle_uint8(KeypadKey key, uint8_t& val) {
    char key_c = char_of_keypad_key(key);
    bool is_digit = std::isdigit(static_cast<unsigned char>(key_c));
    uint8_t digit = is_digit ? static_cast<uint8_t>(key_c - '0') : 0;

    if (key == KeypadKey::star) {
        val = 0;
    } else if (is_digit) {
        uint16_t new_digit = ((uint16_t) val) * 10 + (uint16_t) digit;
        if (new_digit < 255) val = new_digit;
    }
}
static void handle_uint16(KeypadKey key, uint16_t& val) {
    char key_c = char_of_keypad_key(key);
    bool is_digit = std::isdigit(static_cast<unsigned char>(key_c));
    uint8_t digit = is_digit ? static_cast<uint8_t>(key_c - '0') : 0;

    if (key == KeypadKey::star) {
        val = 0;
    } else if (is_digit) {
        uint32_t new_digit = ((uint32_t) val) * 10 + (uint32_t) digit;
        if (new_digit < 65535) val = new_digit;
    }
}

static void handle_enum(ButtonKey key, uint8_t& val, uint8_t n_items) {
    if (key == ButtonKey::b1) {
        // dec
        val = (val + n_items - 1) % n_items;
    } else if (key == ButtonKey::b2) {
        // inc
        val = (val + 1) % n_items;
    }
}

void hardware_config() {
    clean_bomb();
    uint8_t current_item = 0;
    const uint8_t n_items = 28;

    HWData1& hwdata = get_hw_data().inner;

    ButtonKey btn;
    KeypadKey key;
    bool dirty = true;
    while (true) {
        if (dirty) {
            // display
            char name[21];
            char value[21];

            switch (current_item) {
                case 0: // serial_num
                    snprintf(name, sizeof(name), "%-20s", "serial_num");
                    snprintf(value, sizeof(value), "%s", hwdata.serial_num.c_str());
                    break;

                case 1: // rev_ctrl_maj
                    snprintf(name, sizeof(name), "%-20s", "rev_ctrl_maj");
                    snprintf(value, sizeof(value), "%d", hwdata.rev_ctrl_maj);
                    break;

                case 2: // rev_ctrl_min
                    snprintf(name, sizeof(name), "%-20s", "rev_ctrl_min");
                    snprintf(value, sizeof(value), "%d", hwdata.rev_ctrl_min);
                    break;

                case 3: // rev_exp_maj
                    snprintf(name, sizeof(name), "%-20s", "rev_exp_maj");
                    snprintf(value, sizeof(value), "%d", hwdata.rev_exp_maj);
                    break;

                case 4: // rev_exp_min
                    snprintf(name, sizeof(name), "%-20s", "rev_exp_min");
                    snprintf(value, sizeof(value), "%d", hwdata.rev_exp_min);
                    break;

                case 5: // rev_ft_maj
                    snprintf(name, sizeof(name), "%-20s", "rev_ft_maj");
                    snprintf(value, sizeof(value), "%d", hwdata.rev_ft_maj);
                    break;

                case 6: // rev_ft_min
                    snprintf(name, sizeof(name), "%-20s", "rev_ft_min");
                    snprintf(value, sizeof(value), "%d", hwdata.rev_ft_min);
                    break;

                case 7: // rev_fb_maj
                    snprintf(name, sizeof(name), "%-20s", "rev_fb_maj");
                    snprintf(value, sizeof(value), "%d", hwdata.rev_fb_maj);
                    break;

                case 8: // rev_fb_min
                    snprintf(name, sizeof(name), "%-20s", "rev_fb_min");
                    snprintf(value, sizeof(value), "%d", hwdata.rev_fb_min);
                    break;

                case 9: // sseg_color_t
                    snprintf(name, sizeof(name), "%-20s", "sseg_color_t");
                    snprintf(value, sizeof(value), "%s", SSEG_COLOR_NAMES[static_cast<uint8_t>(hwdata.sseg_color_t)]);
                    break;

                case 10: // sseg_color_b
                    snprintf(name, sizeof(name), "%-20s", "sseg_color_b");
                    snprintf(value, sizeof(value), "%s", SSEG_COLOR_NAMES[static_cast<uint8_t>(hwdata.sseg_color_b)]);
                    break;

                case 11: // lcd_color
                    snprintf(name, sizeof(name), "%-20s", "lcd_color");
                    snprintf(value, sizeof(value), "%s", LCD_COLOR_NAMES[static_cast<uint8_t>(hwdata.lcd_color)]);
                    break;

                case 12: // switch_pos
                    snprintf(name, sizeof(name), "%-20s", "switch_pos");
                    snprintf(value, sizeof(value), "%d", hwdata.switch_pos);
                    break;

                case 13: // button_type
                    snprintf(name, sizeof(name), "%-20s", "button_type");
                    snprintf(value, sizeof(value), "%s", BUTTON_TYPE_NAMES[static_cast<uint8_t>(hwdata.button_type)]);
                    break;

                case 14: // tft_type
                    snprintf(name, sizeof(name), "%-20s", "tft_type");
                    snprintf(value, sizeof(value), "%s", TFT_TYPE_NAMES[static_cast<uint8_t>(hwdata.tft_type)]);
                    break;

                case 15: // bat_type
                    snprintf(name, sizeof(name), "%-20s", "bat_type");
                    snprintf(value, sizeof(value), "%s", BAT_TYPE_NAMES[static_cast<uint8_t>(hwdata.bat_type)]);
                    break;

                case 16: // bat_cap
                    snprintf(name, sizeof(name), "%-20s", "bat_cap");
                    snprintf(value, sizeof(value), "%d", hwdata.bat_cap);
                    break;

                case 17: // shape1
                    snprintf(name, sizeof(name), "%-20s", "shape1");
                    snprintf(value, sizeof(value), "%s", SHAPE_TYPE_NAMES[static_cast<uint8_t>(hwdata.shape1)]);
                    break;

                case 18: // shape2
                    snprintf(name, sizeof(name), "%-20s", "shape2");
                    snprintf(value, sizeof(value), "%s", SHAPE_TYPE_NAMES[static_cast<uint8_t>(hwdata.shape2)]);
                    break;

                case 19: // shape3
                    snprintf(name, sizeof(name), "%-20s", "shape3");
                    snprintf(value, sizeof(value), "%s", SHAPE_TYPE_NAMES[static_cast<uint8_t>(hwdata.shape3)]);
                    break;

                case 20: // shape4
                    snprintf(name, sizeof(name), "%-20s", "shape4");
                    snprintf(value, sizeof(value), "%s", SHAPE_TYPE_NAMES[static_cast<uint8_t>(hwdata.shape4)]);
                    break;

                case 21: // has_speaker
                    snprintf(name, sizeof(name), "%-20s", "has_speaker");
                    snprintf(value, sizeof(value), "%s", hwdata.has_speaker ? "true" : "false");
                    break;

                case 22: // has_mic
                    snprintf(name, sizeof(name), "%-20s", "has_mic");
                    snprintf(value, sizeof(value), "%s", hwdata.has_mic ? "true" : "false");
                    break;

                case 23: // has_ir
                    snprintf(name, sizeof(name), "%-20s", "has_ir");
                    snprintf(value, sizeof(value), "%s", hwdata.has_ir ? "true" : "false");
                    break;

                case 24: // has_rfid
                    snprintf(name, sizeof(name), "%-20s", "has_rfid");
                    snprintf(value, sizeof(value), "%s", hwdata.has_rfid ? "true" : "false");
                    break;

                case 25: // has_fp
                    snprintf(name, sizeof(name), "%-20s", "has_fp");
                    snprintf(value, sizeof(value), "%s", hwdata.has_fp ? "true" : "false");
                    break;

                case 26: // has_fp_hall
                    snprintf(name, sizeof(name), "%-20s", "has_fp_hall");
                    snprintf(value, sizeof(value), "%s", hwdata.has_fp_hall ? "true" : "false");
                    break;

                case 27: // has_close_hall
                    snprintf(name, sizeof(name), "%-20s", "has_close_hall");
                    snprintf(value, sizeof(value), "%s", hwdata.has_close_hall ? "true" : "false");
                    break;

                default:
                    break;
            }

            lcd_print(1, 0, name);
            lcd_print(2, 0, value);

            dirty = false;
        }

        if (get_button_pressed(&btn)) {
            dirty = true;
            switch (btn) {
                case ButtonKey::b3: // dec
                    current_item = (current_item + n_items - 1) % n_items;
                    break;
                case ButtonKey::b4: // inc
                    current_item = (current_item + 1) % n_items;
                    break;
                default:
                    switch (current_item) {   
                        case 9: // sseg_color_t
                            handle_enum(btn, reinterpret_cast<uint8_t&>(hwdata.sseg_color_t), SSEG_COLOR_COUNT);
                            break;

                        case 10: // sseg_color_b
                            handle_enum(btn, reinterpret_cast<uint8_t&>(hwdata.sseg_color_b), SSEG_COLOR_COUNT);
                            break;

                        case 11: // lcd_color
                            handle_enum(btn, reinterpret_cast<uint8_t&>(hwdata.lcd_color), LCD_COLOR_COUNT);
                            break;

                        case 13: // button_type
                            handle_enum(btn, reinterpret_cast<uint8_t&>(hwdata.button_type), BUTTON_TYPE_COUNT);
                            break;

                        case 14: // tft_type
                            handle_enum(btn, reinterpret_cast<uint8_t&>(hwdata.tft_type), TFT_TYPE_COUNT);
                            break;

                        case 15: // bat_type
                            handle_enum(btn, reinterpret_cast<uint8_t&>(hwdata.bat_type), BAT_TYPE_COUNT);
                            break;
                
                        case 17: // shape1
                            handle_enum(btn, reinterpret_cast<uint8_t&>(hwdata.shape1), SHAPE_TYPE_COUNT);
                            break;                
                        case 18: // shape2
                            handle_enum(btn, reinterpret_cast<uint8_t&>(hwdata.shape2), SHAPE_TYPE_COUNT);
                            break;
                        case 19: // shape3
                            handle_enum(btn, reinterpret_cast<uint8_t&>(hwdata.shape3), SHAPE_TYPE_COUNT);
                            break;
                        case 20: // shape4                
                            handle_enum(btn, reinterpret_cast<uint8_t&>(hwdata.shape4), SHAPE_TYPE_COUNT);
                            break;
                
                        case 21: // has_speaker
                            hwdata.has_speaker = btn == ButtonKey::button_green;
                            break;
                
                        case 22: // has_mic
                            hwdata.has_mic = btn == ButtonKey::button_green;
                            break;
                        
                        case 23: // has_ir
                            hwdata.has_ir = btn == ButtonKey::button_green;
                            break;
                        
                        case 24: // has_rfid
                            hwdata.has_rfid = btn == ButtonKey::button_green;
                            break;
                        
                        case 25: // has_fp
                            hwdata.has_fp = btn == ButtonKey::button_green;
                            break;
                        
                        case 26: // has_fp_hall                        
                            hwdata.has_fp_hall = btn == ButtonKey::button_green;
                            break;
                        
                        case 27: // has_close_hall
                            hwdata.has_close_hall = btn == ButtonKey::button_green;
                            break;

                        default:
                            break;
                    }
                    break;
            }
        }
        if (get_keypad_pressed(&key)) {
            dirty = true;
            if (key == KeypadKey::pound) {
                // TODO: ask the user to save
                return; // done
            }

            char key_c = char_of_keypad_key(key);
            bool is_digit = std::isdigit(static_cast<unsigned char>(key_c));
            uint8_t digit = is_digit ? static_cast<uint8_t>(key_c - '0') : 0;

            // update the current value
            switch (current_item) {
                case 0: // serial_num
                    if (key == KeypadKey::star) {
                        hwdata.serial_num.clear();
                    } else {
                        hwdata.serial_num.push_back(char_of_keypad_key(key));
                    }
                    break;
                
                case 1: // rev_ctrl_maj
                    handle_uint8(key, hwdata.rev_ctrl_maj);
                    break;
                
                case 2: // rev_ctrl_min
                    handle_uint8(key, hwdata.rev_ctrl_min);
                    break;
                
                case 3: // rev_exp_maj
                    handle_uint8(key, hwdata.rev_exp_maj);
                    break;
                
                case 4: // rev_exp_min
                    handle_uint8(key, hwdata.rev_exp_min);
                    break;
                
                case 5: // rev_ft_maj
                    handle_uint8(key, hwdata.rev_ft_maj);
                    break;
                
                case 6: // rev_ft_min
                    handle_uint8(key, hwdata.rev_ft_min);
                    break;
                
                case 7: // rev_fb_maj
                    handle_uint8(key, hwdata.rev_fb_maj);
                    break;
                
                case 8: // rev_fb_min
                    handle_uint8(key, hwdata.rev_fb_min);
                    break;
                
                case 12: // switch_pos
                    if (digit == 2 || digit == 3) hwdata.switch_pos = digit;
                    break;
                
                case 16: // bat_cap
                    handle_uint16(key, hwdata.bat_cap);
                    break;
                                   
                default:
                    break;
            }
        }

        vTaskDelay(pdMS_TO_TICKS(10));
    }
}
