/*
 * This program source code file is part of the KTag project, a DIY laser tag
 * game with customizable features and wide interoperability.
 *
 * 🛡 <https://ktag.clubk.club> 🃞
 *
 * Copyright © 2024-2026 Joseph P. Kearney and the KTag developers.
 *
 * This program is free software: you can redistribute it and/or modify it under
 * the terms of the GNU Affero General Public License as published by the Free
 * Software Foundation, either version 3 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 Affero General Public License for more
 * details.
 *
 * There should be a copy of the GNU Affero General Public License in the LICENSE
 * file in the root of this repository.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <SystemK.h>
#include <driver/gpio.h>
#include <iot_button.h>
#include <button_gpio.h>
#include <freertos/FreeRTOS.h>
#include <freertos/portmacro.h>

static const char *TAG = "Switches";

// GPIO assignments
#define TRIGGER_GPIO GPIO_NUM_1
#define ACCESSORY_GPIO GPIO_NUM_4

static const button_config_t Button_Config = {
    .long_press_time = CONFIG_BUTTON_LONG_PRESS_TIME_MS,
    .short_press_time = CONFIG_BUTTON_SHORT_PRESS_TIME_MS,
};

static const button_gpio_config_t Trigger_GPIO_Config = {
    .gpio_num = TRIGGER_GPIO,
    .active_level = 0,
};

static const button_gpio_config_t Accessory_GPIO_Config = {
    .gpio_num = ACCESSORY_GPIO,
    .active_level = 0,
};

static button_handle_t Trigger_Button;
static button_handle_t Accessory_Button;

static TickType_t TicksAtTriggerPress = 0;
static TickType_t TicksAtAccessoryPress = 0;
static TickType_t TicksAtAccessoryRelease = 0;

static void trigger_press_cb(void *arg, void *usr_data)
{
    portBASE_TYPE xHigherPriorityTaskWoken = pdFALSE;
    TicksAtTriggerPress = xTaskGetTickCountFromISR();
    KEvent_T switch_event = {.ID = KEVENT_TRIGGER_SWITCH_PRESSED, .Data = NULL};
    Post_KEvent_From_ISR(&switch_event, &xHigherPriorityTaskWoken);
}

static void trigger_release_cb(void *arg, void *usr_data)
{
    portBASE_TYPE xHigherPriorityTaskWoken = pdFALSE;
    uint32_t triggerPressDurationInms = pdTICKS_TO_MS(xTaskGetTickCountFromISR() - TicksAtTriggerPress);
    KEvent_T switch_event = {.ID = KEVENT_TRIGGER_SWITCH_RELEASED, .Data = (void *)triggerPressDurationInms};
    Post_KEvent_From_ISR(&switch_event, &xHigherPriorityTaskWoken);
}

static void accessory_press_cb(void *arg, void *usr_data)
{
    portBASE_TYPE xHigherPriorityTaskWoken = pdFALSE;
    TicksAtAccessoryPress = xTaskGetTickCountFromISR();
    uint32_t accessoryTimeReleasedInms = pdTICKS_TO_MS(xTaskGetTickCountFromISR() - TicksAtAccessoryRelease);
    KEvent_T switch_event = {.ID = KEVENT_ACCESSORY_SWITCH_PRESSED, .Data = (void *)accessoryTimeReleasedInms};
    Post_KEvent_From_ISR(&switch_event, &xHigherPriorityTaskWoken);
}

static void accessory_release_cb(void *arg, void *usr_data)
{
    portBASE_TYPE xHigherPriorityTaskWoken = pdFALSE;
    TicksAtAccessoryRelease = xTaskGetTickCountFromISR();
    uint32_t accessoryPressDurationInms = pdTICKS_TO_MS(xTaskGetTickCountFromISR() - TicksAtAccessoryPress);
    KEvent_T switch_event = {.ID = KEVENT_ACCESSORY_SWITCH_RELEASED, .Data = (void *)accessoryPressDurationInms};
    Post_KEvent_From_ISR(&switch_event, &xHigherPriorityTaskWoken);
}

esp_err_t Initialize_Switches(void)
{
    KLOG_INFO(TAG, "Initializing Switches...");

    esp_err_t err;

    err = iot_button_new_gpio_device(
        &Button_Config,
        &Trigger_GPIO_Config,
        &Trigger_Button);

    if (err != ESP_OK) {
        KLOG_ERROR(TAG, "Failed to create Trigger button (%s)",
                   esp_err_to_name(err));
        return err;
    }

    err = iot_button_new_gpio_device(
        &Button_Config,
        &Accessory_GPIO_Config,
        &Accessory_Button);

    if (err != ESP_OK) {
        KLOG_ERROR(TAG, "Failed to create Accessory button (%s)",
                   esp_err_to_name(err));
        return err;
    }

    err = iot_button_register_cb(
        Trigger_Button,
        BUTTON_PRESS_DOWN,
        NULL,
        trigger_press_cb,
        NULL);

    if (err != ESP_OK) {
        KLOG_ERROR(TAG, "Trigger PRESS_DOWN cb failed (%s)",
                   esp_err_to_name(err));
        return err;
    }

    err = iot_button_register_cb(
        Trigger_Button,
        BUTTON_PRESS_UP,
        NULL,
        trigger_release_cb,
        NULL);

    if (err != ESP_OK) {
        KLOG_ERROR(TAG, "Trigger PRESS_UP cb failed (%s)",
                   esp_err_to_name(err));
        return err;
    }

    err = iot_button_register_cb(
        Accessory_Button,
        BUTTON_PRESS_DOWN,
        NULL,
        accessory_press_cb,
        NULL);

    if (err != ESP_OK) {
        KLOG_ERROR(TAG, "Accessory PRESS_DOWN cb failed (%s)",
                   esp_err_to_name(err));
        return err;
    }

    err = iot_button_register_cb(
        Accessory_Button,
        BUTTON_PRESS_UP,
        NULL,
        accessory_release_cb,
        NULL);

    if (err != ESP_OK) {
        KLOG_ERROR(TAG, "Accessory PRESS_UP cb failed (%s)",
                   esp_err_to_name(err));
        return err;
    }

    KLOG_INFO(TAG, "Switch initialization complete");
    return ESP_OK;
}
