/* Copyright 2021 Batuhan Başerdem
 * <baserdem.batuhan@gmail.com> @bbaserdem
 *
 * 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 "bbaserdem.h"
// Language imports
#include <sendstring_dvorak.h>
// Need memcpy and memcmp from string.h along with transfer stuff
#ifdef SPLIT_KEYBOARD
#include "transactions.h"
#include <string.h>
#endif // SPLIT_KEYBOARD

/*-------------------------*\
|*-----KEYBOARD CONFIG-----*|
\*-------------------------*/
userspace_config_t userspace_config;
userspace_runtime_t userspace_runtime;

/*---------------------------------*\
|*----SPLIT KEYBOARD TRANSPORT-----*|
\*---------------------------------*/
#ifdef SPLIT_KEYBOARD
userspace_config_t transport_userspace_config;
userspace_runtime_t transport_userspace_runtime;

// Translate the RPC data to the local variable
void userspace_config_sync(uint8_t in_buflen, const void* in_data, uint8_t out_buflen, void* out_data) {
    // Copy incoming data to local variable
    if (in_buflen == sizeof(transport_userspace_config)) {
        memcpy(&transport_userspace_config, in_data, in_buflen);
    }
    // There is no data to send back; so no output handling
}
void userspace_runtime_sync(uint8_t in_buflen, const void* in_data, uint8_t out_buflen, void* out_data) {
    // Copy incoming data to local variable
    if (in_buflen == sizeof(transport_userspace_runtime)) {
        memcpy(&transport_userspace_runtime, in_data, in_buflen);
    }
    // There is no data to send back; so no output handling
}

// Either send or receive the correct data
void userspace_transport_update(void) {
    if (is_keyboard_master()) {
        // If we are the main device; we want to send info.
        transport_userspace_config.raw = userspace_config.raw;
        transport_userspace_runtime.raw = userspace_runtime.raw;
    } else {
        // If we are the secondary device; we want to receive info, and save to eeprom.
        userspace_config.raw = transport_userspace_config.raw;
        userspace_runtime.raw = transport_userspace_runtime.raw;
    }
}

// Initiate the protocol on sync
void userspace_transport_sync(bool force_sync) {
    if (is_keyboard_master()) {
        // Keep track of the last state
        static userspace_config_t last_userspace_config;
        static userspace_runtime_t last_userspace_runtime;
        bool needs_sync = false;

        // Check if the config values are different
        if (memcmp(&transport_userspace_config, &last_userspace_config, sizeof(transport_userspace_config))) {
            needs_sync = true;
            memcpy(&last_userspace_config, &transport_userspace_config, sizeof(transport_userspace_config));
        }
        // Perform the sync if requested
        if (needs_sync || force_sync) {
            transaction_rpc_send(RPC_ID_CONFIG_SYNC, sizeof(transport_userspace_config), &transport_userspace_config);
            needs_sync = false;
        }

        // Check if the runtime values are different
        if (memcmp(&transport_userspace_runtime, &last_userspace_runtime, sizeof(transport_userspace_runtime))) {
            needs_sync = true;
            memcpy(&last_userspace_runtime, &transport_userspace_runtime, sizeof(transport_userspace_runtime));
        }

        // Perform the sync if requested
        if (needs_sync || force_sync) {
            transaction_rpc_send(RPC_ID_RUNTIME_SYNC, sizeof(transport_userspace_runtime), &transport_userspace_runtime);
            needs_sync = false;
        }
    }
}
#endif // SPLIT_KEYBOARD

/*---------------------------*\
|*-----KEYBOARD PRE INIT-----*|
\*---------------------------*/
/* This code runs before anything is started.
 * Good for early hardware setup
 */
__attribute__ ((weak)) void keyboard_pre_init_keymap(void) {}
__attribute__ ((weak)) void keyboard_pre_init_user(void) {
    // Keymap specific stuff
    keyboard_pre_init_keymap();
}

/*---------------------*\
|*-----MATRIX INIT-----*|
\*---------------------*/
/* This code runs once midway thru the firmware process.
 * So far, sets the base layer and fixes unicode mode
 */
__attribute__ ((weak)) void matrix_init_keymap(void) {}
void matrix_init_user (void) {
    // Keymap specific things
    matrix_init_keymap();
}

/*----------------------------*\
|*-----KEYBOARD POST INIT-----*|
\*----------------------------*/
/* This code runs after anything is started.
 * Good for late hardware setup, like setting up layer specifications
 */
__attribute__ ((weak)) void keyboard_post_init_keymap(void) {}
__attribute__ ((weak)) void keyboard_post_init_user(void) {
    // Fix beginning base layer, in case some other firmware was flashed
    //  set_single_persistent_default_layer(_BASE);

    // Unicode mode
#   ifdef UNICODEMAP_ENABLE
    set_unicode_input_mode(UC_LNX);
#   endif // UNICODEMAP_ENABLE

    // Split keyboard halves communication
#   ifdef SPLIT_KEYBOARD
    // Register the transactions
    transaction_register_rpc( RPC_ID_CONFIG_SYNC, userspace_config_sync );
    transaction_register_rpc(RPC_ID_RUNTIME_SYNC, userspace_runtime_sync);
    // Load default config values
    if (is_keyboard_master()) { 
        // If we are main; load from eeconfig
        userspace_config.raw = eeconfig_read_user();
        // And update the transport variable
        userspace_transport_update();
        // Do one forced transfer to sync halves
        userspace_transport_sync(true);
    } else {
        // Just sync the data received
        userspace_transport_update();
    }
#   else // SPLIT_KEYBOARD
    // If we are not split; just load from eeprom
    userspace_config.raw = eeconfig_read_user();
#   endif // SPLIT_KEYBOARD

    // Backlight LED
#   ifdef BACKLIGHT_ENABLE
    keyboard_post_init_backlight();
#   endif // BACKLIGHT_ENABLE

    // RGB underglow
#   ifdef RGBLIGHT_ENABLE
    keyboard_post_init_underglow();
#   endif // RGBLIGHT_ENABLE

    // Keymap specific stuff
    keyboard_post_init_keymap();
}

/*---------------------------*\
|*-----HOUSEKEEPING TASK-----*|
\*---------------------------*/
/* I have no idea what this does
 */
__attribute__ ((weak)) void housekeeping_task_keymap(void) {}
void housekeeping_task_user(void) {
    // Check eeprom every now and then
    static userspace_config_t prev_userspace_config;
    static fast_timer_t throttle_timer = 0;
    static bool init_flag = true;

    // Read this if we never read it before
    if (init_flag) {
        init_flag = false;
        prev_userspace_config.raw = eeconfig_read_user();
    }

    // Throttled tasks here
    if (timer_elapsed_fast(throttle_timer) >= HOUSEKEEPING_THROTTLE_INTERVAL_MS) {
        // Refresh timer
        throttle_timer = timer_read_fast();
        // Check userspace config for eeprom updates
        if (memcmp(&prev_userspace_config, &userspace_config, sizeof(userspace_config))) {
            memcpy(&prev_userspace_config, &userspace_config, sizeof(userspace_config));
            eeconfig_update_user(userspace_config.raw);
        }
    }

    // Do transport stuff
#   ifdef SPLIT_KEYBOARD
    userspace_transport_update();
    userspace_transport_sync(false);
#   endif // SPLIT_KEYBOARD

    // Hook to keymap code
    housekeeping_task_keymap();
}

/*-----------------------*\
|*-----EECONFIG INIT-----*|
\*-----------------------*/
/* Default values to send to the eeprom
 */
void eeconfig_init_user(void) {
    // Set everything to default
    userspace_config.raw = 0;
    // Set encoder states to sane defaults if enabled
#   ifdef ENCODER_ENABLE
    reset_encoder_state();
#   endif // ENCODER_ENABLE
}

/*------------------------*\
|*-----PROCESS RECORD-----*|
\*------------------------*/
/* Process record: custom keycodes to process here
 * Allow also the following codes to hook here as well;
 *  Macro definitions
 *  Audio hooks
 */
__attribute__ ((weak))
bool process_record_keymap(uint16_t keycode, keyrecord_t *record) {
    return true;
}
bool process_record_user(uint16_t keycode, keyrecord_t *record) {
    // Return after running through all individual hooks
    return
        process_record_keymap(keycode, record)  &&
#       ifdef AUDIO_ENABLE
        process_record_audio(keycode, record)   &&
#       endif // AUDIO_ENABLE
#       ifdef ENCODER_ENABLE
        process_record_encoder(keycode, record) &&
#       endif // ENCODER_ENABLE
        process_record_macro(keycode, record);
}

/*---------------------*\
|*-----MATRIX SCAN-----*|
\*---------------------*/
/* This code runs every frame
 * I used to check for layer switching here, but layer state is better used.
 * Try to not put anything here; as it runs hundreds time per second-ish
 */
__attribute__ ((weak)) void matrix_scan_keymap(void) { }
void matrix_scan_user (void) {
    // Keymap specific scan function
    matrix_scan_keymap();
}

/*---------------------*\
|*-----LAYER STATE-----*|
\*---------------------*/
/* This code runs after every layer change
 * State represents the new layer state.
 */
__attribute__ ((weak))
layer_state_t layer_state_set_keymap (layer_state_t state) {
    return state;
}
layer_state_t layer_state_set_user(layer_state_t state) {
    // Keymap layer state setting
    state = layer_state_set_keymap(state);
    // For underglow stuff
#   ifdef RGBLIGHT_ENABLE
    state = layer_state_set_underglow(state);
#   endif // RGBLIGHT_ENABLE
    // Audio playback
#   ifdef AUDIO_ENABLE
    state = layer_state_set_audio(state);
#   endif // AUDIO_ENABLE

    return state;
}

/*-----------------------------*\
|*-----DEFAULT LAYER STATE-----*|
\*-----------------------------*/
/* This code runs after every time default base layer is changed
 */
__attribute__ ((weak))
layer_state_t default_layer_state_set_keymap (layer_state_t state) {
    return state;
}
layer_state_t default_layer_state_set_user(layer_state_t state) {
    // Keymap level code
    state = default_layer_state_set_keymap(state);
    return state;
}

/*------------------------*\
|*-----LED SET KEYMAP-----*|
\*------------------------*/
/* Code for LED indicators
 * I'm not sure when exactly does this code run
 */
__attribute__ ((weak)) void led_set_keymap(uint8_t usb_led) {}
void led_set_user(uint8_t usb_led) {
    led_set_keymap(usb_led);
}

/*-----------------*\
|*-----SUSPEND-----*|
\*-----------------*/
/* Suspend stuff here, mostly for the rgb lighting. 
 */
__attribute__ ((weak)) void suspend_power_down_keymap (void) { }
void suspend_power_down_user(void) {
    suspend_power_down_keymap();
    // RGB matrix sleep hook
#   ifdef RGB_MATRIX_ENABLE
    suspend_power_down_rgb();
#   endif // RGB_MATRIX_ENABLE
}
__attribute__ ((weak)) void suspend_wakeup_init_keymap (void) { }
void suspend_wakeup_init_user(void) {
    suspend_wakeup_init_keymap();
    // RGB matrix sleep hook
#   ifdef RGB_MATRIX_ENABLE
    suspend_wakeup_init_rgb();
#   endif // RGB_MATRIX_ENABLE
}

/*------------------*\
|*-----SHUTDOWN-----*|
\*------------------*/
/* Shutdown stuff here; for when entering bootmode.
 */
__attribute__ ((weak)) void shutdown_keymap (void) { }
void shutdown_user(void) {
    // Underglow LED hook on boot
#   ifdef RGBLIGHT_ENABLE
    shutdown_underglow();
#   endif // RGBLIGHT_ENABLE
    // Perkey led hook on boot
#   ifdef RGB_MATRIX_ENABLE
    shutdown_rgb();
#   endif // RGB_MATRIX_ENABLE
    // Keymap hooks
    shutdown_keymap();
}