/* Copyright 2022 @daliusd
 *
 * 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 "flow.h"

extern const uint16_t flow_config[FLOW_COUNT][2];
extern const uint16_t flow_layers_config[FLOW_LAYERS_COUNT][2];

// Represents the states a flow key can be in
typedef enum {
    flow_up_unqueued,
    flow_up_queued,
    flow_up_queued_used,
    flow_down_unused,
    flow_down_used,
} flow_state_t;

#ifdef FLOW_ONESHOT_TERM
const int g_flow_oneshot_term = FLOW_ONESHOT_TERM;
#else
const int g_flow_oneshot_term = 500;
#endif

#ifdef FLOW_ONESHOT_WAIT_TERM
const int g_flow_oneshot_wait_term = FLOW_ONESHOT_WAIT_TERM;
#else
const int g_flow_oneshot_wait_term = 500;
#endif

flow_state_t flow_state[FLOW_COUNT] = { [0 ... FLOW_COUNT - 1] = flow_up_unqueued };
bool flow_pressed[FLOW_COUNT][2] = { [0 ... FLOW_COUNT - 1] = {false, false} };
uint16_t flow_timers[FLOW_COUNT] = { [0 ... FLOW_COUNT - 1] = 0 };
bool flow_timeout_timers_active[FLOW_COUNT] = { [0 ... FLOW_COUNT - 1] = false };
uint16_t flow_timeout_timers_value[FLOW_COUNT] = { [0 ... FLOW_COUNT - 1] = 0 };
uint16_t flow_timeout_wait_timers_value[FLOW_COUNT] = { [0 ... FLOW_COUNT - 1] = 0 };

flow_state_t flow_layers_state[FLOW_LAYERS_COUNT] = {
    [0 ... FLOW_LAYERS_COUNT - 1] = flow_up_unqueued
};
bool flow_layer_timeout_timers_active[FLOW_LAYERS_COUNT] = { [0 ... FLOW_LAYERS_COUNT - 1] = false };
uint16_t flow_layer_timeout_timers_value[FLOW_LAYERS_COUNT] = { [0 ... FLOW_LAYERS_COUNT - 1] = 0 };
uint16_t flow_layer_timeout_wait_timers_value[FLOW_LAYERS_COUNT] = { [0 ... FLOW_LAYERS_COUNT - 1] = 0 };

bool is_flow_ignored_key(uint16_t keycode) {
    for (int i = 0; i < FLOW_COUNT; i++) {
        if (flow_config[i][0] == keycode) {
            return true;
        }
    }

    for (int i = 0; i < FLOW_LAYERS_COUNT; i++) {
        if (flow_layers_config[i][0] == keycode) {
            return true;
        }
    }

    if (keycode == KC_LSFT || keycode == KC_RSFT
            || keycode == KC_LCTL || keycode == KC_RCTL
            || keycode == KC_LALT || keycode == KC_RALT
            || keycode == KC_LGUI || keycode == KC_RGUI) {
        return true;
    }

    return false;
}

bool update_flow_mods(
    uint16_t keycode,
    bool pressed
) {
    bool pass = true;
    bool flow_key_list_triggered[FLOW_COUNT] = { [0 ... FLOW_COUNT - 1] = false };
    bool flow_key_list_pressed[FLOW_COUNT] = { [0 ... FLOW_COUNT - 1] = false };

    bool flow_triggered = false;

    for (uint8_t i = 0; i < FLOW_COUNT; i++) {
        // Layer key
        if (keycode == flow_config[i][0]) {
            if (pressed) {
                flow_pressed[i][0] = true;
            } else {
                flow_pressed[i][0] = false;
            }
        // KC mod key
        } else if (keycode == flow_config[i][1]) {
            if (pressed) {
                if (flow_pressed[i][0]) {
                    flow_pressed[i][1] = true;
                    flow_key_list_triggered[i] = true;
                    flow_triggered = true;
                    flow_key_list_pressed[i] = true;
                    pass = false;
                 }
            } else if (flow_pressed[i][1]) {
                flow_pressed[i][1] = false;
                if (flow_pressed[i][0]) {
                    flow_key_list_triggered[i] = true;
                    flow_triggered = true;
                    pass = false;
                } else if ((flow_state[i] == flow_down_unused)
                        || (flow_state[i] == flow_down_used)) {
                    flow_key_list_triggered[i] = true;
                    flow_triggered = true;
                    pass = false;
                }
            }
        }
    }

    for (uint8_t i = 0; i < FLOW_COUNT; i++) {
        if (flow_key_list_triggered[i]) {
            if (flow_key_list_pressed[i]) {
                if (flow_state[i] == flow_up_unqueued) {
                    register_code(flow_config[i][1]);
                }
                flow_timeout_wait_timers_value[i] = timer_read();
                flow_state[i] = flow_down_unused;
            } else {
                // Trigger keyup
                switch (flow_state[i]) {
                case flow_down_unused:
                    if (!flow_pressed[i][1]) {
                        if (timer_elapsed(flow_timeout_wait_timers_value[i]) > g_flow_oneshot_wait_term) {
                            flow_state[i] = flow_up_unqueued;
                            unregister_code(flow_config[i][1]);
                        } else {
                            // If we didn't use the mod while trigger was held, queue it.
                            flow_state[i] = flow_up_queued;
                            flow_timeout_timers_active[i] = true;
                            flow_timeout_timers_value[i] = timer_read();
                        }
                    }
                    break;
                case flow_down_used:
                    // If we did use the mod while trigger was held, unregister it.
                    if (!flow_pressed[i][1]) {
                        flow_state[i] = flow_up_unqueued;
                        unregister_code(flow_config[i][1]);
                    }
                    break;
                default:
                    break;
                }
            }
        } else if (!flow_triggered) {
            if (pressed) {
                if (!is_flow_ignored_key(keycode)) {
                    switch (flow_state[i]) {
                    case flow_up_queued:
                        flow_state[i] = flow_up_queued_used;
                        flow_timeout_timers_active[i] = false;
                        break;
                    case flow_up_queued_used:
                        flow_state[i] = flow_up_unqueued;
                        unregister_code(flow_config[i][1]);
                        break;
                    default:
                        break;
                    }
                }
            } else {
                if (!is_flow_ignored_key(keycode)) {
                    // On non-ignored keyup, consider the oneshot used.
                    switch (flow_state[i]) {
                    case flow_down_unused:
                        flow_state[i] = flow_down_used;
                        break;
                    case flow_up_queued:
                        flow_state[i] = flow_up_unqueued;
                        unregister_code(flow_config[i][1]);
                        break;
                    case flow_up_queued_used:
                        flow_state[i] = flow_up_unqueued;
                        unregister_code(flow_config[i][1]);
                        break;
                    default:
                        break;
                    }
                }
            }
        }
    }

    return pass;
}

void change_pressed_status(uint16_t keycode, bool pressed) {
    for (int i = 0; i < FLOW_COUNT; i++) {
        if (flow_config[i][0] == keycode) {
            flow_pressed[i][0] = pressed;
        }
    }
}

bool update_flow_layers(
    uint16_t keycode,
    bool pressed,
    keypos_t key_position
) {
    uint8_t key_layer = read_source_layers_cache(key_position);
    bool pass = true;

    for (int i = 0; i < FLOW_LAYERS_COUNT; i++) {
        uint16_t trigger = flow_layers_config[i][0];
        uint16_t layer = flow_layers_config[i][1];

        if (keycode == trigger) {
            if (pressed) {
                // Trigger keydown
                if (flow_layers_state[i] == flow_up_unqueued) {
                    layer_on(layer);
                    change_pressed_status(trigger, true);
                }
                flow_layer_timeout_wait_timers_value[i] = timer_read();
                flow_layers_state[i] = flow_down_unused;
                pass = false;
            } else {
                // Trigger keyup
                switch (flow_layers_state[i]) {
                case flow_down_unused:
                    if (timer_elapsed(flow_layer_timeout_wait_timers_value[i]) > g_flow_oneshot_wait_term) {
                        flow_layers_state[i] = flow_up_unqueued;
                        layer_off(layer);
                        change_pressed_status(trigger, false);
                        pass = false;
                    } else {
                        // If we didn't use the layer while trigger was held, queue it.
                        flow_layers_state[i] = flow_up_queued;
                        flow_layer_timeout_timers_active[i] = true;
                        flow_layer_timeout_timers_value[i] = timer_read();
                        pass = false;
                        change_pressed_status(trigger, true);
                    }
                    break;
                case flow_down_used:
                    // If we did use the layer while trigger was held, turn off it.
                    flow_layers_state[i] = flow_up_unqueued;
                    layer_off(layer);
                    change_pressed_status(trigger, false);
                    pass = false;
                    break;
                default:
                    break;
                }
            }
        } else {
            if (pressed) {
                if (key_layer == layer) {
                    // On non-ignored keyup, consider the oneshot used.
                    switch (flow_layers_state[i]) {
                    case flow_down_unused:
                        flow_layers_state[i] = flow_down_used;
                        break;
                    case flow_up_queued:
                        flow_layers_state[i] = flow_up_queued_used;
                        flow_layer_timeout_timers_active[i] = false;
                        break;
                   case flow_up_queued_used:
                        flow_layers_state[i] = flow_up_unqueued;
                        layer_off(layer);
                        change_pressed_status(trigger, false);
                        pass = false;
                        break;
                    default:
                        break;
                    }
                }
            } else {
                // Ignore key ups from other layers
                if (key_layer == layer) {
                    // On non-ignored keyup, consider the oneshot used.
                    switch (flow_layers_state[i]) {
                    case flow_up_queued:
                        flow_layers_state[i] = flow_up_unqueued;
                        layer_off(layer);
                        change_pressed_status(trigger, false);
                        break;
                    case flow_up_queued_used:
                        flow_layers_state[i] = flow_up_unqueued;
                        layer_off(layer);
                        change_pressed_status(trigger, false);
                        break;
                    default:
                        break;
                    }
                }
            }
        }
    }

    return pass;
}

bool update_flow(
    uint16_t keycode,
    bool pressed,
    keypos_t key_position
) {
    bool pass = update_flow_mods(keycode, pressed);
    pass = update_flow_layers(keycode, pressed, key_position) & pass;
    return pass;
}

void flow_matrix_scan(void) {
    for (int i = 0; i < FLOW_COUNT; i++) {
        if (flow_timeout_timers_active[i]
                && timer_elapsed(flow_timeout_timers_value[i]) > g_flow_oneshot_term) {
            flow_timeout_timers_active[i] = false;
            flow_state[i] = flow_up_unqueued;
            unregister_code(flow_config[i][1]);
        }
    }

    for (int i = 0; i < FLOW_LAYERS_COUNT; i++) {
        if (flow_layer_timeout_timers_active[i]
                && timer_elapsed(flow_layer_timeout_timers_value[i]) > g_flow_oneshot_term) {
            flow_layer_timeout_timers_active[i] = false;
            flow_layers_state[i] = flow_up_unqueued;
            layer_off(flow_layers_config[i][1]);
            change_pressed_status(flow_layers_config[i][0], false);
        }
    }
}