/* Hello World Example

   This example code is in the Public Domain (or CC0 licensed, at your option.)

   Unless required by applicable law or agreed to in writing, this
   software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
   CONDITIONS OF ANY KIND, either express or implied.
*/

/* ESP libraries */
#include "esp_log.h"
#include "esp_sleep.h"
#include "esp_system.h"

/* FreeRTOS */
#include "freertos/FreeRTOS.h"
#include "freertos/queue.h"
#include "freertos/task.h"

/* MISC */
#include "cbor.h"
#include "nvs_flash.h"

/* APP */
#include "app_bkp_ram.h"
#include "app_lvgl.h"
#include "app_mqtt.h"
#include "app_ui.h"
#include "app_wifi.h"

#define APP_WAKE_INTERVAL_MS (30 * 60 * 1000)

#define APP_LOG_TAG "MAIN"

RTC_NOINIT_ATTR app_bkp_ram_t g_app_bkp;

static CborError app_get_condition_string(CborValue *it, char *key, char **value, uint32_t *size);
static int       app_windlevel_to_font(char *input, char **output);
static int       app_condition_to_font(char *input, char **output);
static int       app_update_ui(void);

void app_main(void) {
    esp_err_t ret = nvs_flash_init();
    if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
        ESP_ERROR_CHECK(nvs_flash_erase());

        ret = nvs_flash_init();

        ESP_LOGW(APP_LOG_TAG, "NVS content corrupted or outdated, cleared.");
    }

    ESP_ERROR_CHECK(ret);

    if (app_wifi_init() != 0) {
        goto next_round;
    }

    if (app_mqtt_init() != 0) {
        goto next_round;
    }

    if (app_wifi_wait_ready(5000) != 0) {
        ESP_LOGW(APP_LOG_TAG, "Failed to connect to WiFi in time, bail out.");
        goto next_round;
    }

    if (app_lvgl_init() != 0) {
        goto next_round;
    }

    if (app_ui_init() != 0) {
        goto next_round;
    }

    if (app_update_ui() != 0) {
        goto next_round;
    }

    app_lvgl_start_flush();
    if(app_lvgl_wait_flush(5000) < 0) {
        ESP_LOGW(APP_LOG_TAG, "Did not get flush ready flag in time, bail out.");
        goto next_round;
    }

next_round:

    ESP_LOGI(APP_LOG_TAG, "Enter deep power down mode, interval: %d", APP_WAKE_INTERVAL_MS);
    app_lvgl_deinit();
    esp_deep_sleep(APP_WAKE_INTERVAL_MS * 1000);
}

static int app_update_ui(void) {
    app_mqtt_subscribe("iot/weather/ESP32_Weather/response");

    CborEncoder root_encoder;
    uint8_t    *tx_buf = malloc(100);
    if (tx_buf == NULL) {
        ESP_LOGE(APP_LOG_TAG, "Failed to allocate tx buffer");

        return -1;
    }

    cbor_encoder_init(&root_encoder, tx_buf, 100, 0);

    CborEncoder map_encoder;
    cbor_encoder_create_map(&root_encoder, &map_encoder, 2);

    cbor_encode_text_stringz(&map_encoder, "type");
    cbor_encode_text_stringz(&map_encoder, "condition");

    cbor_encode_text_stringz(&map_encoder, "city_id");
    cbor_encode_uint(&map_encoder, 10);

    cbor_encoder_close_container(&root_encoder, &map_encoder);

    uint32_t data_size = cbor_encoder_get_buffer_size(&root_encoder, tx_buf);

    ESP_LOGI(APP_LOG_TAG, "Encoded CBOR message %d bytes.", data_size);

    app_mqtt_publish("iot/weather/ESP32_Weather/request", tx_buf, data_size);

    free(tx_buf);

    char    *topic;
    uint8_t *payload;
    uint32_t payload_len;

    if (app_mqtt_poll(&topic, &payload, &payload_len, 10000) != 0) {
        ESP_LOGE(APP_LOG_TAG, "failed to receive payload in time, bail out");

        return -2;
    }

    ESP_LOGI(APP_LOG_TAG, "Received %d bytes from topic %s", payload_len, topic);

    CborParser root_parser;
    CborValue  it;
    CborError  cbor_ret;

    cbor_ret = cbor_parser_init(payload, payload_len, 0, &root_parser, &it);
    if (cbor_ret != CborNoError) {
        ESP_LOGE(APP_LOG_TAG, "CBOR Parse 1");
        return -3;
    }

    char    *str;
    uint32_t size;
    if (app_get_condition_string(&it, "sunRise", &str, &size) == CborNoError) {
        app_ui_update(APP_UI_CMD_SUNRISE, str);
        free(str);
    }

    if (app_get_condition_string(&it, "sunSet", &str, &size) == CborNoError) {
        app_ui_update(APP_UI_CMD_SUNSET, str);
        free(str);
    }

    if (app_get_condition_string(&it, "uvi", &str, &size) == CborNoError) {
        app_ui_update(APP_UI_CMD_UVI, str);
        free(str);
    }

    if (app_get_condition_string(&it, "updatetime", &str, &size) == CborNoError) {
        app_ui_update(APP_UI_CMD_UPDATED, str);
        free(str);
    }

    if (app_get_condition_string(&it, "temp", &str, &size) == CborNoError) {
        app_ui_update(APP_UI_CMD_TEMP, str);
        free(str);
    }

    if (app_get_condition_string(&it, "humidity", &str, &size) == CborNoError) {
        app_ui_update(APP_UI_CMD_HUMID, str);
        free(str);
    }

    if (app_get_condition_string(&it, "realFeel", &str, &size) == CborNoError) {
        app_ui_update(APP_UI_CMD_TEMP_REAL, str);
        free(str);
    }

    if (app_get_condition_string(&it, "windSpeed", &str, &size) == CborNoError) {
        app_ui_update(APP_UI_CMD_WIND_SPEED, str);
        free(str);
    }

    if (app_get_condition_string(&it, "windLevel", &str, &size) == CborNoError) {
        app_windlevel_to_font(str, &str);
        app_ui_update(APP_UI_CMD_WIND_LEVEL, str);
        free(str);
    }

    if (app_get_condition_string(&it, "conditionId", &str, &size) == CborNoError) {
        app_condition_to_font(str, &str);
        app_ui_update(APP_UI_CMD_COND, str);
        free(str);
    }

    free(topic);
    free(payload);

    return 0;
}

static int app_condition_to_font(char *input, char **output) {
    *output = malloc(4);
    if (*output == NULL) return -1;

    char *unicode = "\U0000F00D";

    int wx_id = strtol(input, NULL, 10);

    if (wx_id <= 5) {
        /* Do nothing */
    } else if (wx_id <= 7) {
        unicode = "\U0000F072";
    } else if (wx_id <= 11) {
        unicode = "\U0000F002";
    } else if (wx_id <= 12) {
        unicode = "\U0000F072";
    } else if (wx_id <= 14) {
        unicode = "\U0000F013";
    } else if (wx_id <= 23) {
        unicode = "\U0000F009";
    } else if (wx_id <= 25) {
        unicode = "\U0000F00A";
    } else if (wx_id <= 28) {
        unicode = "\U0000F003";
    } else if (wx_id <= 33) {
        unicode = "\U0000F082";
    } else if (wx_id <= 35) {
        unicode = "\U0000F0B6";
    } else if (wx_id <= 36) {
        unicode = "\U0000F041";
    } else if (wx_id <= 41) {
        unicode = "\U0000F00E";
    } else if (wx_id <= 43) {
        unicode = "\U0000F005";
    } else if (wx_id <= 45) {
        unicode = "\U0000F068";
    } else if (wx_id <= 48) {
        unicode = "\U0000F004";
    } else if (wx_id <= 50) {
        unicode = "\U0000F006";
    } else if (wx_id <= 57) {
        unicode = "\U0000F008";
    } else if (wx_id <= 63) {
        unicode = "\U0000F00A";
    } else if (wx_id <= 65) {
        unicode = "\U0000F006";
    } else if (wx_id <= 70) {
        unicode = "\U0000F008";
    } else if (wx_id <= 77) {
        unicode = "\U0000F00A";
    } else if (wx_id <= 78) {
        unicode = "\U0000F008";
    } else if (wx_id <= 79) {
        unicode = "\U0000F0B6";
    } else if (wx_id <= 82) {
        unicode = "\U0000F002";
    } else if (wx_id <= 84) {
        unicode = "\U0000F003";
    } else if (wx_id <= 85) {
        unicode = "\U0000F002";
    } else if (wx_id <= 86) {
        unicode = "\U0000F009";
    } else if (wx_id <= 90) {
        unicode = "\U0000F00E";
    } else if (wx_id <= 93) {
        unicode = "\U0000F008";
    } else if (wx_id <= 94) {
        unicode = "\U0000F00A";
    }

    snprintf(*output, 4, "%s", unicode);
    free(input);

    return 0;
}

static int app_windlevel_to_font(char *input, char **output) {
    *output = malloc(4);
    if (*output == NULL) return -1;

    char *unicode = "\U0000F0B7";

    if (input[1] != '\0') {
        switch (input[0]) {
            case '0':
                unicode = "\U0000F0C1";
                break;
            case '1':
                unicode = "\U0000F0C2";
                break;
            case '2':
                unicode = "\U0000F0C3";
                break;
            default:
                break;
        }
    } else {
        switch (input[0]) {
            case '0':
                unicode = "\U0000F0B7";
                break;
            case '1':
                unicode = "\U0000F0B8";
                break;
            case '2':
                unicode = "\U0000F0B9";
                break;
            case '3':
                unicode = "\U0000F0BA";
                break;
            case '4':
                unicode = "\U0000F0BB";
                break;
            case '5':
                unicode = "\U0000F0BC";
                break;
            case '6':
                unicode = "\U0000F0BD";
                break;
            case '7':
                unicode = "\U0000F0BE";
                break;
            case '8':
                unicode = "\U0000F0BF";
                break;
            case '9':
                unicode = "\U0000F0C0";
                break;
        }
    }

    snprintf(*output, 4, "%s", unicode);
    free(input);
    return 0;
}

static CborError app_get_condition_string(CborValue *it, char *key, char **value, uint32_t *size) {
    CborError ret;

    CborValue data;
    ret = cbor_value_map_find_value(it, "data", &data);
    if (ret != CborNoError) {
        ESP_LOGE(APP_LOG_TAG, "CBOR Parse 2");
        return ret;
    }

    if (cbor_value_get_type(&data) != CborMapType) {
        ESP_LOGE(APP_LOG_TAG, "CBOR Parse 3");
        return ret;
    }

    CborValue condition;
    ret = cbor_value_map_find_value(&data, "condition", &condition);
    if (ret != CborNoError) {
        ESP_LOGE(APP_LOG_TAG, "CBOR Parse 4");
        return ret;
    }

    if (cbor_value_get_type(&condition) != CborMapType) {
        ESP_LOGE(APP_LOG_TAG, "CBOR Parse 5");
        return ret;
    }

    CborValue result;
    ret = cbor_value_map_find_value(&condition, key, &result);
    if (ret != CborNoError) {
        ESP_LOGE(APP_LOG_TAG, "CBOR Parse 6");
        return ret;
    }

    if (cbor_value_is_text_string(&result)) {
        cbor_value_dup_text_string(&result, value, (size_t *)&size, &result);
    }

    return ret;
}