/* Copyright 2021 OpenAnnePro community
 *
 * 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 "hal.h"
#include "annepro2.h"
#include "annepro2_ble.h"
#include "spi_master.h"
#include "ap2_led.h"
#include "protocol.h"

#define RAM_MAGIC_LOCATION 0x20001ffc
#define IAP_MAGIC_VALUE 0x0000fab2

static const SerialConfig led_uart_init_config = {
    .speed = 115200,
};

#ifndef LED_UART_BAUD_RATE
#    define LED_UART_BAUD_RATE 115200
#endif  // LED_UART_BAUD_RATE

static const SerialConfig led_uart_runtine_config = {
    .speed = LED_UART_BAUD_RATE,
};

static const SerialConfig ble_uart_config = {
    .speed = 115200,
};

static uint8_t led_mcu_wakeup[11] = {0x7b, 0x10, 0x43, 0x10, 0x03, 0x00, 0x00, 0x7d, 0x02, 0x01, 0x02};

ble_capslock_t ble_capslock = {._dummy = {0}, .caps_lock = false};

#ifdef RGB_MATRIX_ENABLE
static uint8_t led_enabled = 1;
#endif

void bootloader_jump(void) {
    // Send msg to shine to boot into IAP
    ap2_set_IAP();

    // wait for shine to boot into IAP
    wait_ms(15);

    // Load ble into IAP
    annepro2_ble_bootload();
    wait_ms(15);

    // Magic key to set keyboard to IAP
    // It’s from reversing original boot loader
    // If value is that it stays in boot loader aka IAP
    *((uint32_t *)RAM_MAGIC_LOCATION) = IAP_MAGIC_VALUE;

    // Load the main MCU into IAP
    __disable_irq();
    NVIC_SystemReset();
}

void keyboard_pre_init_kb(void) {
    // Start LED UART
    sdStart(&SD0, &led_uart_init_config);
    /* Let the LED chip settle a bit before switching the mode.
     * That helped at least one person. */
    wait_ms(15);
    sdWrite(&SD0, led_mcu_wakeup, sizeof(led_mcu_wakeup));

    // wait to receive response from wakeup
    wait_ms(15);

    proto_init(&proto, led_command_callback);

    // loop to clear out receive buffer from shine wakeup
    while (!sdGetWouldBlock(&SD0)) sdGet(&SD0);

    sdStart(&SD0, &led_uart_runtine_config);
    keyboard_pre_init_user();
}

void keyboard_post_init_kb(void) {
    // Start BLE UART
    sdStart(&SD1, &ble_uart_config);
    annepro2_ble_startup();

    // Give the send uart thread some time to
    // send out the queue before we read back
    wait_ms(100);

    // loop to clear out receive buffer from ble wakeup
    while (!sdGetWouldBlock(&SD1)) sdGet(&SD1);

    #ifdef RGB_MATRIX_ENABLE
    ap2_led_set_manual_control(1);
    ap2_led_enable();
    #endif

    keyboard_post_init_user();
}

void matrix_scan_kb(void) {
    // if there's stuff on the ble serial buffer
    // read it into the capslock struct
    while (!sdGetWouldBlock(&SD1)) {
        sdReadTimeout(&SD1, (uint8_t *)&ble_capslock, sizeof(ble_capslock_t), 10);
    }

    /* While there's data from LED keyboard sent - read it. */
    while (!sdGetWouldBlock(&SD0)) {
        uint8_t byte = sdGet(&SD0);
        proto_consume(&proto, byte);
    }


    matrix_scan_user();
}

bool process_record_kb(uint16_t keycode, keyrecord_t *record) {
    if (record->event.pressed) {
        if (ap2_led_status.matrix_enabled && ap2_led_status.is_reactive) {
            ap2_led_forward_keypress(record->event.key.row, record->event.key.col);
        }

        const ap2_led_t blue = {
            .p.blue  = 0xff,
            .p.red   = 0x00,
            .p.green = 0x00,
            .p.alpha = 0xff,
        };

        switch (keycode) {
            case KC_AP2_BT1:
                annepro2_ble_broadcast(0);
                /* FIXME: This hardcodes col/row position */
                ap2_led_blink(record->event.key.row, record->event.key.col, blue, 8, 50);
                return false;

            case KC_AP2_BT2:
                annepro2_ble_broadcast(1);
                ap2_led_blink(record->event.key.row, record->event.key.col, blue, 8, 50);
                return false;

            case KC_AP2_BT3:
                annepro2_ble_broadcast(2);
                ap2_led_blink(record->event.key.row, record->event.key.col, blue, 8, 50);
                return false;

            case KC_AP2_BT4:
                annepro2_ble_broadcast(3);
                ap2_led_blink(record->event.key.row, record->event.key.col, blue, 8, 50);
                return false;

            case KC_AP2_USB:
                annepro2_ble_disconnect();
                return false;

            case KC_AP2_BT_UNPAIR:
                annepro2_ble_unpair();
                return false;

            case KC_AP_LED_OFF:
                ap2_led_disable();
                break;

            case KC_AP_LED_ON:
                if (ap2_led_status.matrix_enabled) {
                    ap2_led_next_profile();
                } else {
                    ap2_led_enable();
                }
                ap2_led_reset_foreground_color();
                break;

            case KC_AP_LED_TOG:
                if (ap2_led_status.matrix_enabled) {
                    ap2_led_disable();
                } else {
                    ap2_led_enable();
                    ap2_led_reset_foreground_color();
                }
                break;

            case KC_AP_LED_NEXT_PROFILE:
                ap2_led_next_profile();
                ap2_led_reset_foreground_color();
                break;

            case KC_AP_LED_PREV_PROFILE:
                ap2_led_prev_profile();
                ap2_led_reset_foreground_color();
                break;

            case KC_AP_LED_NEXT_INTENSITY:
                ap2_led_next_intensity();
                ap2_led_reset_foreground_color();
                return false;

            case KC_AP_LED_SPEED:
                ap2_led_next_animation_speed();
                ap2_led_reset_foreground_color();
                return false;
            #ifdef RGB_MATRIX_ENABLE
            case RGB_TOG:
                if(rgb_matrix_is_enabled()) ap2_led_disable();
                else ap2_led_enable();
                return true;

            case KC_AP_RGB_VAI:
                if (record->event.pressed) {
                    if (get_mods() & MOD_MASK_SHIFT) {
                        rgb_matrix_increase_hue();
                        return false;
                    } else if (get_mods() & MOD_MASK_CTRL) {
                        rgb_matrix_decrease_hue();
                        return false;
                    } else {
                        rgb_matrix_increase_val();
                    }
                }
                return true;

            case KC_AP_RGB_VAD:
                if (record->event.pressed) {
                    if (get_mods() & MOD_MASK_SHIFT) {
                        rgb_matrix_increase_sat();
                        return false;
                    } else if (get_mods() & MOD_MASK_CTRL) {
                        rgb_matrix_decrease_sat();
                        return false;
                    } else {
                        rgb_matrix_decrease_val();
                    }
                }
                return true;

            case KC_AP_RGB_TOG:
                if (record->event.pressed) {
                    if (get_mods() & MOD_MASK_SHIFT) {
                        rgb_matrix_increase_speed();
                        return false;
                    } else if (get_mods() & MOD_MASK_CTRL) {
                        rgb_matrix_decrease_speed();
                        return false;
                    } else {
                        if (led_enabled) {
                            ap2_led_disable();
                            rgb_matrix_disable();
                            led_enabled = 0;
                        } else {
                            ap2_led_enable();
                            rgb_matrix_enable();
                            led_enabled = 1;
                        }
                        return true;
                    }
                }
                return true;

            case KC_AP_RGB_MOD:
                if (record->event.pressed) {
                    if (get_mods() & MOD_MASK_CTRL) {
                        rgb_matrix_step_reverse();
                        return false;
                    } else {
                        rgb_matrix_step();
                    }
                }
                return true;
            #endif

            default:
                break;
        }
    }
    return process_record_user(keycode, record);
}
