#include "step1.h"

__attribute__((unused))
static const char *TAG = "step1";

static const char* COLOR_NAMES[] = {
    "green",
    "red",
    "yellow",
    "blue"
};

static const char* NUM_NAMES[] = {
    "one",
    "two",
    "three",
    "four"
};

static uint32_t NEOPIXEL_COLORS[4] = {
    LEDColor::LED_COLOR_GREEN,
    LEDColor::LED_COLOR_RED,
    LEDColor::LED_COLOR_YELLOW,
    LEDColor::LED_COLOR_BLUE,
};

static std::random_device my_rd;
static std::mt19937 my_gen(my_rd());
static std::uniform_int_distribution<> zero_to_one(0, 1);
static std::uniform_int_distribution<> zero_to_two(0, 2);
static std::uniform_int_distribution<> zero_to_three(0, 3);

static lv_obj_t *old_scr;
static lv_obj_t *scr;
static lv_obj_t *text = NULL;

static lv_style_t style_screen;
static lv_style_t green_text;
static lv_style_t red_text;
static lv_style_t yellow_text;
static lv_style_t blue_text;

static lv_style_t* last_style = nullptr;

static int switch_leds[] = {0, 0, 0, 0};

static lv_style_t* color_styles[] = {
    &green_text,
    &red_text,
    &yellow_text,
    &blue_text
};

/// 0 => part a
/// 1 => part b
/// 2 => part c
static int part = 0;


static void init_step(void) {
    if (xSemaphoreTake(xGuiSemaphore, portMAX_DELAY) == pdTRUE) {
        scr = lv_obj_create(NULL);

        // Set the background color of the display to black.
        lv_style_init(&style_screen);
        lv_style_set_bg_color(&style_screen, lv_color_black());
        lv_style_set_text_font(&style_screen, &lv_font_montserrat_32);
        lv_obj_add_style(scr, &style_screen, LV_STATE_DEFAULT);

        // rgb565
        lv_color_t green = { .full = 0x0560 };
        lv_color_t red = { .full = 0xf800 };
        lv_color_t yellow = { .full = 0xfce0 };
        lv_color_t blue = { .full = 0x045f };

        lv_style_init(&green_text);
        lv_style_set_text_color(&green_text, green);

        lv_style_init(&red_text);
        lv_style_set_text_color(&red_text, red);

        lv_style_init(&yellow_text);
        lv_style_set_text_color(&yellow_text, yellow);

        lv_style_init(&blue_text);
        lv_style_set_text_color(&blue_text, blue);

        text = lv_label_create(scr);

        old_scr = lv_scr_act();
        lv_scr_load(scr);
        xSemaphoreGive(xGuiSemaphore);
    }
}

static void clean_up_step(void) {
    while (xSemaphoreTake(xGuiSemaphore, portMAX_DELAY) == pdFALSE) vTaskDelay(pdMS_TO_TICKS(10));
    lv_scr_load(old_scr);
    lv_obj_del(scr);
    xSemaphoreGive(xGuiSemaphore);
}

static void generate_switch_leds(void) {
    int colors[4] = {0, 1, 2, 3};

    int idx = zero_to_three(my_gen);
    switch_leds[0] = colors[idx];
    colors[idx] = colors[3];

    idx = zero_to_two(my_gen);
    switch_leds[1] = colors[idx];
    colors[idx] = colors[2];

    idx = zero_to_one(my_gen);
    switch_leds[2] = colors[idx];
    colors[idx] = colors[1];

    switch_leds[3] = colors[0];

    for (int i = 0; i < 4; i++) {
        led_set(IndicatorLED::LED_S1 - i, NEOPIXEL_COLORS[switch_leds[i]]);
    }
    leds_flush();
}

static void set_text_and_color(const char* text_str, int color) {
    if (xSemaphoreTake(xGuiSemaphore, portMAX_DELAY) == pdTRUE) {
        lv_label_set_text(text, text_str);
        lv_obj_center(text);
        if (last_style != nullptr) {
            lv_obj_remove_style(text, last_style, LV_STATE_DEFAULT);
        }
        lv_obj_add_style(text, color_styles[color], LV_STATE_DEFAULT);
        last_style = color_styles[color];
        xSemaphoreGive(xGuiSemaphore);
    }
}

static int generate_part_a(void) {
    int text_color = zero_to_three(my_gen);
    int text_idx = zero_to_three(my_gen);

    const char* text_string = COLOR_NAMES[text_idx];

    set_text_and_color(text_string, text_color);
    generate_switch_leds();

    // the correct answer is the text color
    return text_color;
}

static int generate_part_b(void) {
    int is_color = zero_to_one(my_gen) == 0;
    if (is_color) {
        return generate_part_a();
    }

    int text_color = zero_to_three(my_gen); 
    int text_number = zero_to_three(my_gen);

    const char* text_string = NUM_NAMES[text_number];

    set_text_and_color(text_string, text_color);
    generate_switch_leds();

    // the correct answer is the number
    return text_number;
}

static int generate_part_c(void) {
    int type = zero_to_two(my_gen);
    if (type != 0) {
        return generate_part_b();
    }

    int text_color = zero_to_three(my_gen); 
    const char* text_string = "switch";

    set_text_and_color(text_string, text_color);
    generate_switch_leds();

    // the correct answer is the switch
    return text_color + 4;
}

static int generate_part(void) {
    switch (part) {
        case 0:
        return generate_part_a();
        case 1:
        return generate_part_b();
        case 2:
        return generate_part_c();
    }
    ESP_LOGW(TAG, "`part` (%d) was not to in range 0..=2!", part);
    return -1;
}

static void update_lcd_count(int times) {
    char buf[16] = {0};
    sprintf(buf, "%d/15", times);
    lcd_print(1, 14, buf);
}

static bool play_part(uint32_t time) {
    set_module_time(time);

    lcd_clear();
    switch (part) {
        case 0:
        lcd_print(1, 1, "COLOR");
        led_set(IndicatorLED::LED_LCD, LEDColor::LED_COLOR_PINK);
        break;
        case 1:
        lcd_print(1, 1, "NUMBER");
        led_set(IndicatorLED::LED_LCD, LEDColor::LED_COLOR_BLUE);
        break;
        case 2:
        lcd_print(1, 1, "SWITCH");
        led_set(IndicatorLED::LED_LCD, LEDColor::LED_COLOR_YELLOW);
        break;
    }
    leds_flush();

    int times = 0;
    int correct = generate_part();
    update_lcd_count(times);

    ButtonKey button;
    SwitchKey switch_;

    while (times < 15) {
        while (get_button_pressed(&button)) {
            start_module_timer();
            if ((int)(button) == correct) {
                times++;
                if (times >= 15) break;
                correct = generate_part();
                update_lcd_count(times);
            } else {
                strike("Incorrect action");
            }
        }
        while (get_switch_flipped(&switch_)) {
            start_module_timer();
            if (switch_leds[(int)(switch_)] + 4 == correct) {
                times++;
                if (times >= 15) break;
                correct = generate_part();
                update_lcd_count(times);
            } else {
                strike("Incorrect action");
            }
        }
        if (get_module_time() <= 0) {
            strike("Out of time");
            vTaskDelay(pdMS_TO_TICKS(2000));
            return false;
        }

        vTaskDelay(pdMS_TO_TICKS(10));
    }

    stop_module_timer();
    if (xSemaphoreTake(xGuiSemaphore, portMAX_DELAY) == pdTRUE) {
        lv_label_set_text(text, "");
        xSemaphoreGive(xGuiSemaphore);
    }
    if (part < 2) {
        play_clip_wav(MOUNT_POINT "/partdone.wav", true, false, 0, 0);
        vTaskDelay(pdMS_TO_TICKS(2000));
    } else {
        play_clip_wav(MOUNT_POINT "/stepdone.wav", true, false, 1, 0);
    }
    return true;
}

void step1(void) {
    while (get_switch_flipped(nullptr));

    init_step();
    while (!play_part(40*1000));
    part = 1;
    while (!play_part(35*1000));
    part = 2;
    while (!play_part(30*1000));

    clean_up_step();
    // TODO: flash lights
}
