/* Copyright 2022 @ lokher (https://www.keychron.com)
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#include "action.h"
#include "quantum.h"
#include "bluetooth.h"
#include "report_buffer.h"
#include "lpm.h"
#include "battery.h"
#include "indicator.h"
#include "transport.h"
#include "rtc_timer.h"

extern uint8_t         pairing_indication;
extern host_driver_t   chibios_driver;
extern report_buffer_t kb_rpt;
extern uint32_t        retry_time_buffer;
extern uint8_t         retry;

#ifdef NKRO_ENABLE
extern nkro_t nkro;
#endif

static uint8_t host_index = 0;
static uint8_t led_state  = 0;

extern bluetooth_transport_t bluetooth_transport;
static bluetooth_state_t     bt_state                  = BLUETOOTH_RESET;
static bool                  pincodeEntry              = false;
uint8_t                      bluetooth_report_protocol = true;

/* declarations */
uint8_t bluetooth_keyboard_leds(void);
void    bluetooth_send_keyboard(report_keyboard_t *report);
void    bluetooth_send_mouse(report_mouse_t *report);
void    bluetooth_send_extra(report_extra_t *report);

/* host struct */
host_driver_t bluetooth_driver = {bluetooth_keyboard_leds, bluetooth_send_keyboard, bluetooth_send_mouse, bluetooth_send_extra};

#define BLUETOOTH_EVENT_QUEUE_SIZE 16
bluetooth_event_t bt_event_queue[BLUETOOTH_EVENT_QUEUE_SIZE];
uint8_t           bt_event_queue_head;
uint8_t           bt_event_queue_tail;

void bluetooth_bt_event_queue_init(void) {
    // Initialise the event queue
    memset(&bt_event_queue, 0, sizeof(bt_event_queue));
    bt_event_queue_head = 0;
    bt_event_queue_tail = 0;
}

bool bluetooth_event_queue_enqueue(bluetooth_event_t event) {
    uint8_t next = (bt_event_queue_head + 1) % BLUETOOTH_EVENT_QUEUE_SIZE;
    if (next == bt_event_queue_tail) {
        /* Override the first report */
        bt_event_queue_tail = (bt_event_queue_tail + 1) % BLUETOOTH_EVENT_QUEUE_SIZE;
    }
    bt_event_queue[bt_event_queue_head] = event;
    bt_event_queue_head                 = next;
    return true;
}

static inline bool bluetooth_event_queue_dequeue(bluetooth_event_t *event) {
    if (bt_event_queue_head == bt_event_queue_tail) {
        return false;
    }
    *event              = bt_event_queue[bt_event_queue_tail];
    bt_event_queue_tail = (bt_event_queue_tail + 1) % BLUETOOTH_EVENT_QUEUE_SIZE;
    return true;
}

/*
 * Bluetooth init.
 */
void bluetooth_init(void) {
    bt_state = BLUETOOTH_INITIALIZED;

    bluetooth_bt_event_queue_init();
#ifndef DISABLE_REPORT_BUFFER
    report_buffer_init();
#endif
    indicator_init();
#ifdef BLUETOOTH_INT_INPUT_PIN
    setPinInputHigh(BLUETOOTH_INT_INPUT_PIN);
#endif

    lpm_init();
    rtc_timer_init();

#ifdef BLUETOOTH_NKRO_ENABLE
    keymap_config.raw = eeconfig_read_keymap();
    nkro.bluetooth    = keymap_config.nkro;
#endif
}

/*
 * Bluetooth trasponrt init. Bluetooth module driver shall use this function to register a callback
 * to its implementation.
 */
void bluetooth_set_transport(bluetooth_transport_t *transport) {
    if (transport) memcpy(&bluetooth_transport, transport, sizeof(bluetooth_transport_t));
}

/*
 * Enter pairing with current host index
 */
void bluetooth_pairing(void) {
    if (battery_is_critical_low()) return;

    bluetooth_pairing_ex(0, NULL);
    bt_state = BLUETOOTH_PARING;
}

/*
 * Enter pairing with specified host index and param
 */
void bluetooth_pairing_ex(uint8_t host_idx, void *param) {
    if (battery_is_critical_low()) return;

    if (bluetooth_transport.pairing_ex) bluetooth_transport.pairing_ex(host_idx, param);
    bt_state = BLUETOOTH_PARING;

    host_index = host_idx;
}

/*
 * Initiate connection request to paired host
 */
void bluetooth_connect(void) {
    /*  Work around empty report after wakeup, which leads to reconneect/disconnected loop */
    if (battery_is_critical_low() || sync_timer_read32() == 0) return;

    bluetooth_transport.connect_ex(0, 0);
    bt_state = BLUETOOTH_RECONNECTING;
}

/*
 * Initiate connection request to paired host with argument
 */
void bluetooth_connect_ex(uint8_t host_idx, uint16_t timeout) {
    if (battery_is_critical_low()) return;

    if (host_idx != 0) {
        if (host_index == host_idx && bt_state == BLUETOOTH_CONNECTED) return;
        host_index = host_idx;
        led_state  = 0;
    }
    bluetooth_transport.connect_ex(host_idx, timeout);
    bt_state = BLUETOOTH_RECONNECTING;
}

/* Initiate a disconnection */
void bluetooth_disconnect(void) {
    if (bluetooth_transport.disconnect) bluetooth_transport.disconnect();
}

/* Called when the BT device is reset. */
static void bluetooth_enter_reset(uint8_t reason) {
    bt_state = BLUETOOTH_RESET;
    bluetooth_enter_reset_kb(reason);
}

/* Enters discoverable state. Upon entering this state we perform the following actions:
 *   - change state to BLUETOOTH_PARING
 *   - set pairing indication
 */
static void bluetooth_enter_discoverable(uint8_t host_idx) {
    bt_state = BLUETOOTH_PARING;
    indicator_set(bt_state, host_idx);
    bluetooth_enter_discoverable_kb(host_idx);
}

/*
 * Enters reconnecting state. Upon entering this state we perform the following actions:
 *   - change state to RECONNECTING
 *   - set reconnect indication
 */
static void bluetooth_enter_reconnecting(uint8_t host_idx) {
    bt_state = BLUETOOTH_RECONNECTING;
    indicator_set(bt_state, host_idx);
    bluetooth_enter_reconnecting_kb(host_idx);
}

/* Enters connected state. Upon entering this state we perform the following actions:
 *   - change state to CONNECTED
 *   - set connected indication
 *   - enable bluetooth NKRO is support
 */
static void bluetooth_enter_connected(uint8_t host_idx) {
    bt_state = BLUETOOTH_CONNECTED;
    indicator_set(bt_state, host_idx);
    host_index = host_idx;

    clear_keyboard();

    /* Enable NKRO since it may be disabled in pin code entry */
#if defined(NKRO_ENABLE) && defined(BLUETOOTH_NKRO_ENABLE)
    keymap_config.nkro = nkro.bluetooth;
#else
    keymap_config.nkro = false;
#endif

    bluetooth_enter_connected_kb(host_idx);
#if defined(BAT_LOW_LED_PIN) || defined(BAT_LOW_LED_PIN_STATE)
    if (battery_is_empty()) {
        indicator_battery_low_enable(true);
    }
#endif
}

/* Enters disconnected state. Upon entering this state we perform the following actions:
 *   - change state to DISCONNECTED
 *   - set disconnected indication
 */
static void bluetooth_enter_disconnected(uint8_t host_idx) {
    uint8_t previous_state = bt_state;
    bt_state               = BLUETOOTH_DISCONNECTED;

    if (previous_state == BLUETOOTH_CONNECTED) {
        lpm_timer_reset();
        indicator_set(BLUETOOTH_SUSPEND, host_idx);
    } else
        indicator_set(bt_state, host_idx);

#ifndef DISABLE_REPORT_BUFFER
    report_buffer_init();
#endif
    retry = 0;
    bluetooth_enter_disconnected_kb(host_idx);
#if defined(BAT_LOW_LED_PIN) || defined(BAT_LOW_LED_PIN_STATE)
    indicator_battery_low_enable(false);
#endif
#if defined(LOW_BAT_IND_INDEX)
    indicator_battery_low_backlit_enable(false);
#endif
}

/* Enter pin code entry state. */
static void bluetooth_enter_pin_code_entry(void) {
#if defined(NKRO_ENABLE)
    keymap_config.nkro = FALSE;
#endif
    pincodeEntry = true;
    bluetooth_enter_pin_code_entry_kb();
}

/* Exit pin code entry state. */
static void bluetooth_exit_pin_code_entry(void) {
#if defined(NKRO_ENABLE)
    keymap_config.nkro = true;
#endif
    pincodeEntry = false;
    bluetooth_exit_pin_code_entry_kb();
}

__attribute__((weak)) void bluetooth_enter_reset_kb(uint8_t reason){};
__attribute__((weak)) void bluetooth_enter_discoverable_kb(uint8_t host_idx){};
__attribute__((weak)) void bluetooth_enter_reconnecting_kb(uint8_t host_idx){};
__attribute__((weak)) void bluetooth_enter_connected_kb(uint8_t host_idx){};
__attribute__((weak)) void bluetooth_enter_disconnected_kb(uint8_t host_idx){};
__attribute__((weak)) void bluetooth_enter_pin_code_entry_kb(void) {}
__attribute__((weak)) void bluetooth_exit_pin_code_entry_kb(void){};

/*  */
static void bluetooth_hid_set_protocol(bool report_protocol) {
    bluetooth_report_protocol = false;
}

uint8_t bluetooth_keyboard_leds(void) {
    if (bt_state == BLUETOOTH_CONNECTED) {
        return led_state;
    }

    return 0;
}

extern keymap_config_t keymap_config;

void bluetooth_send_keyboard(report_keyboard_t *report) {
    if (bt_state == BLUETOOTH_PARING && !pincodeEntry) return;

    if (bt_state == BLUETOOTH_CONNECTED || (bt_state == BLUETOOTH_PARING && pincodeEntry)) {
#if defined(NKRO_ENABLE)
        if (bluetooth_report_protocol && keymap_config.nkro) {
            if (bluetooth_transport.send_nkro) {
#    ifndef DISABLE_REPORT_BUFFER
                bool firstBuffer = false;
                if (report_buffer_is_empty() && report_buffer_next_inverval() && report_buffer_get_retry() == 0) {
                    firstBuffer = true;
                }

                report_buffer_t report_buffer;
                report_buffer.type = REPORT_TYPE_NKRO;
                memcpy(&report_buffer.keyboard, report, sizeof(report_keyboard_t));
                report_buffer_enqueue(&report_buffer);

                if (firstBuffer) {
                    report_buffer_set_retry(0);
                    report_buffer_task();
                }
#    else
                bluetooth_transport.send_nkro(&report->nkro.mods);
#    endif
            }
        } else
#endif
        {
            // #ifdef KEYBOARD_SHARED_EP
            if (bluetooth_transport.send_keyboard) {
#ifndef DISABLE_REPORT_BUFFER
                if (report_buffer_is_empty() && report_buffer_next_inverval()) {
                    bluetooth_transport.send_keyboard(&report->mods);
                    report_buffer_update_timer();
                } else {
                    report_buffer_t report_buffer;
                    report_buffer.type = REPORT_TYPE_KB;
                    memcpy(&report_buffer.keyboard, report, sizeof(report_keyboard_t));
                    report_buffer_enqueue(&report_buffer);
                }
#else
                bluetooth_transport.send_keyboard(&report->mods);
#endif
            }
            // #endif
        }

    } else if (bt_state != BLUETOOTH_RESET) {
        bluetooth_connect();
    }
}

void bluetooth_send_mouse(report_mouse_t *report) {
    if (bt_state == BLUETOOTH_CONNECTED) {
        if (bluetooth_transport.send_mouse) bluetooth_transport.send_mouse((uint8_t *)report);
    } else if (bt_state != BLUETOOTH_RESET) {
        bluetooth_connect();
    }
}

void bluetooth_send_system(uint16_t data) {
    if (bt_state == BLUETOOTH_CONNECTED) {
        if (bluetooth_transport.send_system) bluetooth_transport.send_system(data);
    } else if (bt_state != BLUETOOTH_RESET) {
        bluetooth_connect();
    }
}

void bluetooth_send_consumer(uint16_t data) {
    if (bt_state == BLUETOOTH_CONNECTED) {
#ifndef DISABLE_REPORT_BUFFER
        if (report_buffer_is_empty() && report_buffer_next_inverval()) {
            if (bluetooth_transport.send_consumer) bluetooth_transport.send_consumer(data);
            report_buffer_update_timer();
        } else {
            report_buffer_t report_buffer;
            report_buffer.type     = REPORT_TYPE_CONSUMER;
            report_buffer.consumer = data;
            report_buffer_enqueue(&report_buffer);
        }
#else
        if (bluetooth_transport.send_consumer) bluetooth_transport.send_consumer(data);
#endif
    } else if (bt_state != BLUETOOTH_RESET) {
        bluetooth_connect();
    }
}

void bluetooth_send_extra(report_extra_t *report) {
    if (report->report_id == REPORT_ID_SYSTEM) {
        bluetooth_send_system(report->usage);
    } else if (report->report_id == REPORT_ID_CONSUMER) {
        bluetooth_send_consumer(report->usage);
    }
}

void bluetooth_low_battery_shutdown(void) {
#if defined(BAT_LOW_LED_PIN) || defined(BAT_LOW_LED_PIN_STATE)
    indicator_battery_low_enable(false);
#endif
#if defined(LOW_BAT_IND_INDEX)
    indicator_battery_low_backlit_enable(false);
#endif
    clear_keyboard();
    send_keyboard_report();

    bluetooth_disconnect();
}

void bluetooth_event_queue_task(void) {
    bluetooth_event_t event;
    while (bluetooth_event_queue_dequeue(&event)) {
        switch (event.evt_type) {
            case EVT_RESET:
                bluetooth_enter_reset(event.params.reason);
                break;
            case EVT_CONNECTED:
                bluetooth_enter_connected(event.params.hostIndex);
                break;
            case EVT_DISCOVERABLE:
                bluetooth_enter_discoverable(event.params.hostIndex);
                break;
            case EVT_RECONNECTING:
                bluetooth_enter_reconnecting(event.params.hostIndex);
                break;
            case EVT_DISCONNECTED:
                led_state = 0;
                bluetooth_enter_disconnected(event.params.hostIndex);
                break;
            case EVT_PINCODE_ENTRY:
                bluetooth_enter_pin_code_entry();
                break;
            case EVT_EXIT_PINCODE_ENTRY:
                bluetooth_exit_pin_code_entry();
                break;
            case EVT_HID_INDICATOR:
                led_state = event.params.led;
                break;
            case EVT_HID_SET_PROTOCOL:
                bluetooth_hid_set_protocol(event.params.protocol);
                break;
            case EVT_CONECTION_INTERVAL:
                report_buffer_set_inverval(event.params.interval);
                break;
            default:
                break;
        }
    }
}

void bluetooth_task(void) {
    bluetooth_transport.task();
    bluetooth_event_queue_task();
#ifndef DISABLE_REPORT_BUFFER
    report_buffer_task();
#endif
    indicator_task();
    battery_task();
    lpm_task();
}

bluetooth_state_t bluetooth_get_state(void) {
    return bt_state;
};

__attribute__((weak)) bool process_record_kb_bt(uint16_t keycode, keyrecord_t *record) {
    return true;
};

bool process_record_kb(uint16_t keycode, keyrecord_t *record) {
    if (!process_record_user(keycode, record)) {
        return false;
    }

    if (get_transport() == TRANSPORT_BLUETOOTH) {
        lpm_timer_reset();

#if defined(BAT_LOW_LED_PIN) || defined(LOW_BAT_IND_INDEX)
        if (battery_is_empty() && bluetooth_get_state() == BLUETOOTH_CONNECTED && record->event.pressed) {
#    if defined(BAT_LOW_LED_PIN) || defined(BAT_LOW_LED_PIN_STATE)
            indicator_battery_low_enable(true);
#    endif
#    if defined(LOW_BAT_IND_INDEX)
            indicator_battery_low_backlit_enable(true);
#    endif
        }
#endif
    }
    return process_record_kb_bt(keycode, record);
    // return process_record_user(keycode, record);
}
