#include "wires.h"

extern uint32_t current_step;

/// The mutex for accessing `I2C_NUM_1` (wires I2C bus).
SemaphoreHandle_t wires_i2c_mutex;

uint32_t total_strikes;
uint32_t step_strikes[N_STEPS] = {0};
uint32_t step_finish_times[N_STEPS] = {0};

static const char *TAG = "wires";

const uint32_t STRIKE_TIME_PENALTY = 30'000;

static bool button_state;
static bool button_pressed;
static bool button_released;

static uint8_t wires_state;
static uint8_t wires_cut;

static uint8_t buf[8];

static void poll_wires_task(void *arg);
static void receive_wires(void);
static void receive_button(void);

void init_wires(void) {
    i2c_config_t wires_conf = {
        .mode = I2C_MODE_MASTER,
        .sda_io_num = PIN_WIRES_SDA,
        .scl_io_num = PIN_WIRES_SCL,
        .sda_pullup_en = GPIO_PULLUP_ENABLE,
        .scl_pullup_en = GPIO_PULLUP_ENABLE,
        .master = {
            .clk_speed = 100000,
        },
    };

    gpio_reset_pin(PIN_WIRES_SDA);
    gpio_reset_pin(PIN_WIRES_SCL);

    ESP_ERROR_CHECK(i2c_param_config(WIRES_I2C_NUM, &wires_conf));
    ESP_ERROR_CHECK(i2c_driver_install(WIRES_I2C_NUM, wires_conf.mode, 0, 0, 0));

    // Create mutex for wires I2C bus
    wires_i2c_mutex = xSemaphoreCreateMutex();
    assert(wires_i2c_mutex != NULL);

    gpio_config_t int_pin_conf = {};
    // delta_pin_conf.intr_type = GPIO_INTR_LOW_LEVEL;
    int_pin_conf.mode = GPIO_MODE_INPUT;
    int_pin_conf.pin_bit_mask = (1ULL<<WIRES_PIN_DELTA);
    int_pin_conf.pull_up_en = GPIO_PULLUP_ENABLE;
    gpio_config(&int_pin_conf);

    receive_button();
    receive_wires();

    xTaskCreate(poll_wires_task, "poll_wires", 4096, NULL, 10, NULL);
}

uint8_t get_wires(void) {
    return wires_state;
}
uint8_t get_cut_wires(void) {
    uint8_t return_ = wires_cut;
    wires_cut = 0;
    return return_;
}

bool get_help_button(void) {
    return button_state;
}
bool get_help_button_pressed(void) {
    bool return_ = button_pressed;
    button_pressed = false;
    return return_;
}
bool get_help_button_released(void) {
    bool return_ = button_released;
    button_released = false;
    return return_;
}

void clear_wires_pressed_released_cut(void) {
    wires_cut = 0;
    button_pressed = false;
    button_released = false;
}

void strike(const char* reason) {
    ESP_LOGW("strike!", "%s", reason);
    lcd_print(3, 0, reason);
    time_penalty(STRIKE_TIME_PENALTY);
    if (current_step > 0 && current_step <= N_STEPS) {
        total_strikes += 1;
        step_strikes[current_step - 1] += 1;
    }
    uint8_t reg = 6;
    xSemaphoreTake(wires_i2c_mutex, portMAX_DELAY);
    ESP_ERROR_CHECK_WITHOUT_ABORT(i2c_master_write_to_device(WIRES_I2C_NUM, WIRES_I2C_ADDR, &reg, 1, (100 / portTICK_PERIOD_MS)));
    xSemaphoreGive(wires_i2c_mutex);
}

void set_leds(uint8_t led_states) {
    buf[0] = 5; // register 5
    buf[1] = led_states;
    xSemaphoreTake(wires_i2c_mutex, portMAX_DELAY);
    ESP_ERROR_CHECK_WITHOUT_ABORT(i2c_master_write_to_device(WIRES_I2C_NUM, WIRES_I2C_ADDR, buf, 2, (100 / portTICK_PERIOD_MS)));
    xSemaphoreGive(wires_i2c_mutex);
}

static uint8_t receive_delta(void) {
    uint8_t reg = 1;
    buf[0] = 0;
    xSemaphoreTake(wires_i2c_mutex, portMAX_DELAY);
    ESP_ERROR_CHECK_WITHOUT_ABORT(i2c_master_write_read_device(WIRES_I2C_NUM, WIRES_I2C_ADDR, &reg, 1, buf, 1, (100 / portTICK_PERIOD_MS)));
    xSemaphoreGive(wires_i2c_mutex);
    return buf[0];
}

static void receive_wires(void) {
    uint8_t reg = 2;
    buf[0] = 0;
    xSemaphoreTake(wires_i2c_mutex, portMAX_DELAY);
    ESP_ERROR_CHECK_WITHOUT_ABORT(i2c_master_write_read_device(WIRES_I2C_NUM, WIRES_I2C_ADDR, &reg, 1, buf, 1, (100 / portTICK_PERIOD_MS)));
    xSemaphoreGive(wires_i2c_mutex);
    uint8_t new_wires = buf[0];
    
    uint8_t just_cut = ~new_wires & wires_state;
    wires_cut |= just_cut;
    
    wires_state = new_wires;
}

static void receive_button(void) {
    uint8_t reg = 3;
    buf[0] = 0;
    xSemaphoreTake(wires_i2c_mutex, portMAX_DELAY);
    ESP_ERROR_CHECK_WITHOUT_ABORT(i2c_master_write_read_device(WIRES_I2C_NUM, WIRES_I2C_ADDR, &reg, 1, buf, 1, (100 / portTICK_PERIOD_MS)));
    xSemaphoreGive(wires_i2c_mutex);
    bool new_button = buf[0] != 0;

    bool just_pressed = new_button & !button_state;
    button_pressed |= just_pressed;

    bool just_released = !new_button & button_state;
    button_released |= just_released;

    button_state = new_button;
}

static void poll_wires_task(void *arg) {
    while (1) {
        bool new_data = gpio_get_level(WIRES_PIN_DELTA) == 0;
        if (new_data) {
            uint8_t delta = receive_delta();
            if (delta == 0) ESP_LOGW(TAG, "delta pin was low, but delta register returned 0");

            if (delta & (1 << DELTA_BIT_WIRES)) receive_wires();
            if (delta & (1 << DELTA_BIT_BUTTON)) receive_button();
        }
        vTaskDelay(pdMS_TO_TICKS(10));
    }

    vTaskDelete(NULL);
}
