#include "bottom_half.h"
#include <esp_log.h>
#include "state_tracking.h"
#include "starcode.h"

static const char *TAG = "bottom_half";

static uint16_t keypad_state;
static uint8_t button_state;
static uint8_t switch_state;
static uint8_t switch_touch_state;
static uint8_t touch_state;

static uint16_t keypad_pressed;
static uint8_t button_pressed;
static uint8_t switch_flipped_up;
static uint8_t switch_touch_pressed;
static uint8_t touch_pressed;

static uint16_t keypad_released;
static uint8_t button_released;
static uint8_t switch_flipped_down;
static uint8_t switch_touch_released;
static uint8_t touch_released;

/// read buffer
static uint8_t buf[8];

static void poll_bottom_task(void *arg);
static void receive_keypad();
static void receive_button_switch();
static void receive_touch();

static bool replay_handler(const char* event, char* arg) {
    // no reply neccesary
    return false;
}

// TODO: add intrupt on bottom half delta pin
// static void IRAM_ATTR gpio_isr_handler(void* arg)
// {
//     // TODO: do somthing
//     ESP_LOGI("BOTTOM_HALF", "Hit");
// }

void init_bottom_half() {
    ESP_LOGI(TAG, "Initializing bottom half...");

    gpio_config_t int_conf = {
        .pin_bit_mask = 1ULL << BOTTOM_PIN_INTERUPT,
        .mode = GPIO_MODE_INPUT,
        .pull_up_en = GPIO_PULLUP_ENABLE,
        .pull_down_en = GPIO_PULLDOWN_DISABLE,
        .intr_type = GPIO_INTR_DISABLE,
    };
    ESP_ERROR_CHECK(gpio_config(&int_conf));

    // TODO: do interupt stuff.
    // ESP_ERROR_CHECK(gpio_intr_enable(BOTTOM_PIN_INTERUPT));
    // ESP_ERROR_CHECK(gpio_install_isr_service(ESP_INTR_FLAG))
    //install gpio isr service
    // gpio_install_isr_service(0);
    //hook isr handler for specific gpio pin
    // gpio_isr_handler_add(BOTTOM_INTERUPT_PIN, gpio_isr_handler, NULL);

    receive_keypad();
    receive_button_switch();
    receive_touch();

    xTaskCreate(poll_bottom_task, "poll_bottom", 4096, NULL, 10, NULL);

    register_replay_fn(replay_handler);

    ESP_LOGI(TAG, "Bottom half initialized!");
}

static uint8_t receive_delta(void) {
    uint8_t reg = 1;
    esp_err_t result = i2c_master_write_read_device(BOTTOM_I2C_NUM, BOTTOM_I2C_ADDR, &reg, 1, buf, 1, (100 / portTICK_PERIOD_MS));
    ESP_ERROR_CHECK_WITHOUT_ABORT(result);
    if (result != ESP_OK) {
        return 0;
    }
    return buf[0];
}

static void receive_keypad(void) {
    // TODO: use mutex
    // TODO: change the bottom half polling scheme from a state-based protocol to an event based protocol
    uint8_t reg = 2;
    esp_err_t result = i2c_master_write_read_device(BOTTOM_I2C_NUM, BOTTOM_I2C_ADDR, &reg, 1, buf, 2, (100 / portTICK_PERIOD_MS));
    ESP_ERROR_CHECK_WITHOUT_ABORT(result);
    if (result != ESP_OK) {
        return;
    }
    uint16_t new_keypad_state = buf[0] | (buf[1] << 8);
    uint16_t just_pressed = new_keypad_state & ~keypad_state;
    if (is_state_tracking() && just_pressed) {
        char buf[6];
        sprintf(buf, "%d", just_pressed);
        event_occured("KP_PRESS", buf);
    }

    uint16_t just_released = ~new_keypad_state & keypad_state;
    if (is_state_tracking() && just_released) {
        char buf[6];
        sprintf(buf, "%d", just_released);
        event_occured("KP_RELEASE", buf);
    }

    star_code_handle_keypad(&just_pressed, &just_released);

    keypad_pressed |= just_pressed;
    keypad_released |= just_released;
    keypad_state = new_keypad_state;
}

static void receive_button_switch(void) {
    uint8_t reg = 3;
    esp_err_t result = i2c_master_write_read_device(BOTTOM_I2C_NUM, BOTTOM_I2C_ADDR, &reg, 1, buf, 2, (100 / portTICK_PERIOD_MS));
    ESP_ERROR_CHECK_WITHOUT_ABORT(result);
    if (result != ESP_OK) {
        return;
    }

    uint8_t new_button_state = buf[1] & 0xF;
    uint8_t new_switch_state = (~buf[0]) & 0xF;
    uint8_t new_switch_touch_state = (buf[1] >> 4) & 0xF;

    // button
    uint8_t just_pressed = new_button_state & ~button_state;
    button_pressed |= just_pressed;
    if (is_state_tracking() && just_pressed) {
        char buf[4];
        sprintf(buf, "%d", just_pressed);
        event_occured("BTN_PRESS", buf);
    }

    uint8_t just_released = ~new_button_state & button_state;
    button_released |= just_released;
    if (is_state_tracking() && just_released) {
        char buf[4];
        sprintf(buf, "%d", just_released);
        event_occured("BTN_RELEASE", buf);
    }

    button_state = new_button_state;

    // switch
    uint8_t just_flipped_up = new_switch_state & ~switch_state;
    switch_flipped_up |= just_flipped_up;
    if (is_state_tracking() && just_flipped_up) {
        char buf[4];
        sprintf(buf, "%d", just_flipped_up);
        event_occured("SW_UP", buf);
    }

    uint8_t just_flipped_down = ~new_switch_state & switch_state;
    switch_flipped_down |= just_flipped_down;
    if (is_state_tracking() && just_flipped_down) {
        char buf[4];
        sprintf(buf, "%d", just_flipped_down);
        event_occured("SW_DOWN", buf);
    }
    
    switch_state = new_switch_state;

    // switch touch
    uint8_t touch_just_pressed = new_switch_touch_state & ~switch_touch_state;
    switch_touch_pressed |= touch_just_pressed;
    if (is_state_tracking() && touch_just_pressed) {
        char buf[4];
        sprintf(buf, "%d", touch_just_pressed);
        event_occured("SW_TOUCH", buf);
    }

    uint8_t touch_just_released = ~new_switch_touch_state & switch_touch_state;
    switch_touch_released |= touch_just_released;
    if (is_state_tracking() && touch_just_released) {
        char buf[4];
        sprintf(buf, "%d", touch_just_released);
        event_occured("SW_UNTOUCH", buf);
    }

    switch_touch_state = new_switch_touch_state;
}

static void receive_touch(void) {
    uint8_t reg = 4;
    buf[0] = 0;
    ESP_ERROR_CHECK_WITHOUT_ABORT(i2c_master_write_read_device(BOTTOM_I2C_NUM, BOTTOM_I2C_ADDR, &reg, 1, buf, 1, (100 / portTICK_PERIOD_MS)));
    bool new_touch_state = buf[0] != 0;

    bool just_pressed = new_touch_state & !touch_state;
    touch_pressed |= just_pressed;
    if (is_state_tracking() && just_pressed) {
        event_occured("FINGER_TOUCHED", NULL);
    }

    bool just_released = (!new_touch_state) & touch_state;
    touch_released |= just_released;
    if (is_state_tracking() && just_released) {
        event_occured("FINGER_UNTOUCHED", NULL);
    }
    
    touch_state = new_touch_state;
}

static void poll_bottom_task(void *arg) {
    // TODO: if using an interupt, switch this to use a queue
    while (1) {
        bool new_data = gpio_get_level(BOTTOM_PIN_INTERUPT) == 0;
        if (new_data) {
            uint8_t delta = receive_delta();
            // ESP_LOGI(_TAG, "delta: %d", delta);
            if (delta == 0) ESP_LOGW(TAG, "delta pin was low, but delta register returned 0");

            if (delta & (1 << DELTA_BIT_KP)) receive_keypad();
            if (delta & (1 << DELTA_BIT_BUTTON_SWITCH)) receive_button_switch();
            if (delta & (1 << DELTA_BIT_TOUCH)) receive_touch();
        }
        vTaskDelay(pdMS_TO_TICKS(10));
    }

    vTaskDelete(NULL);
}

void clear_all_pressed_released(void) {
    keypad_pressed = 0;
    button_pressed = 0;
    switch_flipped_up = 0;
    touch_pressed = 0;
    
    keypad_released = 0;
    button_released = 0;
    switch_flipped_down = 0;
    touch_released = 0;
}

// TODO: this is public, but it won't need to be after the event-based protocol refactor
bool take_key(KeypadKey* kp, uint16_t* keypad_bitfield) {
    for (int i = 0; i < 16; i++) {
        int bit_selector = (1 << i);
        if ((*keypad_bitfield) & bit_selector) {
            if (kp != nullptr) {
                *kp = (KeypadKey) i;
            }
            // clear bit
            *keypad_bitfield &= ~bit_selector;
            return true;
        }
    }
    return false;
}

bool get_keypad_pressed(KeypadKey* kp) {
    return take_key(kp, &keypad_pressed);
}
bool get_keypad_released(KeypadKey* kp) {
    return take_key(kp, &keypad_released);
}

char char_of_keypad_key(KeypadKey kp) {
    switch (kp) {
        case KeypadKey::k1:
            return '1';
        case KeypadKey::k2:
            return '2';
        case KeypadKey::k3:
            return '3';
        case KeypadKey::k4:
            return '4';
        case KeypadKey::k5:
            return '5';
        case KeypadKey::k6:
            return '6';
        case KeypadKey::k7:
            return '7';
        case KeypadKey::k8:
            return '8';
        case KeypadKey::k9:
            return '9';
        case KeypadKey::k0:
            return '0';
        case KeypadKey::ka:
            return 'A';
        case KeypadKey::kb:
            return 'B';
        case KeypadKey::kc:
            return 'C';
        case KeypadKey::kd:
            return 'D';
        case KeypadKey::star:
            return '*';
        case KeypadKey::pound:
            return '#';
        default:
            return ' ';
    }
}

static bool _take_button(ButtonKey* button, uint8_t* button_bitfield) {
    if (((*button_bitfield) & 0xF) == 0) return false;

    // scan the 4 button bits for one that is set
    for (int i = 0; i < 4; i++) {
        int bit_selector = (1 << i);
        if ((*button_bitfield) & bit_selector) {
            if (button != nullptr) {
                *button = (ButtonKey) i;
            }
            // clear bit
            *button_bitfield &= ~bit_selector;
            return true;
        }
    }
    return false;
}
static bool _take_switch(SwitchKey* switch_, uint8_t* switch_bitfield) {
    if (((*switch_bitfield) & 0xF) == 0) return false;

    // scan the 4 switch bits for one that is set
    for (int i = 0; i < 4; i++) {
        int bit_selector = (1 << i);
        if ((*switch_bitfield) & bit_selector) {
            if (switch_ != nullptr) {
                *switch_ = (SwitchKey) i;
            }
            // clear bit
            *switch_bitfield &= ~bit_selector;
            return true;
        }
    }
    return false;
}

bool get_button_pressed(ButtonKey* button) {
    return _take_button(button, &button_pressed);
}
bool get_button_released(ButtonKey* button) {
    return _take_button(button, &button_released);
}
uint8_t get_button_state() {
    return button_state;
}

bool get_switch_flipped_up(SwitchKey* switch_) {
    return _take_switch(switch_, &switch_flipped_up);
}
bool get_switch_flipped_down(SwitchKey* switch_) {
    return _take_switch(switch_, &switch_flipped_down);
}
bool get_switch_flipped(SwitchKey* switch_) {
    for (int i = 0; i < 4; i++) {
        int bit_selector = (1 << i);
        if (
            ((switch_flipped_up & bit_selector) != 0) || 
            ((switch_flipped_down & bit_selector) != 0)
        ) {
            if (switch_ != nullptr) {
                *switch_ = (SwitchKey) i;
            }
            // clear bit
            switch_flipped_up &= ~bit_selector;
            switch_flipped_down &= ~bit_selector;
            return true;
        }
    }
    return false;
}
uint8_t get_switch_state() {
    return switch_state;
}

bool get_switch_touch_pressed(SwitchKey* switch_) {
    return _take_switch(switch_, &switch_touch_pressed);
}
bool get_switch_touch_released(SwitchKey* switch_) {
    return _take_switch(switch_, &switch_touch_released);
}
uint8_t get_switch_touch_state(){
    return switch_touch_state;
}

bool get_touch_state(void) {
    return touch_state;
}
bool get_touch_pressed(void) {
    bool return_ = touch_pressed;
    touch_pressed = false;
    return return_;
}
bool get_touch_released(void) {
    bool return_ = touch_released;
    touch_released = false;
    return return_;
}
