/*
 * Copyright (C) 2023 Daniele Lacamera <root@danielinux.net>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser 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 Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
#include <stdint.h>
#include <stdlib.h>
#include "system.h"
#include "button.h"
#include "unicore-mx/stm32/gpio.h"
#include "unicore-mx/stm32/exti.h"
#include "unicore-mx/stm32/rcc.h"
#include "unicore-mx/stm32/adc.h"
#include "unicore-mx/stm32/f7/nvic.h"
#include "unicore-mx/stm32/f4/adc.h"
#include "systick.h"
#include "task.h"
#include "adc.h"

#define BUTTON_DEBOUNCE_TIME 100
#define TS_DEBOUNCE_TIME 0


#define PSEL_0 (1 << 0)
#define PSEL_1 (1 << 1)
#define PSEL_2 (1 << 2)


/* Input lines: PH6, PA8, PB15, PB14 */

#define PLINE_0 (1 << 6)
#define PLINE_1 (1 << 8)
#define PLINE_2 (1 << 15)
#define PLINE_3 (1 << 14)

#define TS_PIN (1 << 13) /* GPIO I13 */

static volatile int button_current_latch = 0;
static uint32_t button_latch_switchtime;
static volatile int button_press_pending = 0;

static void input_run(uint32_t ev, void *arg);
static void input_init(void);

const char button_task_name[] = "Input";

struct task input_task = {
    .init = input_init,
    .run = input_run,
    .events = EV_SYSTICK,
    .name = button_task_name
};


static void button_setup(void)
{
    int i;
    uint32_t exti_irq;
    rcc_periph_clock_enable(RCC_SYSCFG);
    rcc_periph_clock_enable(RCC_GPIOA);
    rcc_periph_clock_enable(RCC_GPIOB);
    rcc_periph_clock_enable(RCC_GPIOH);
    rcc_periph_clock_enable(RCC_GPIOI);

    gpio_mode_setup(GPIOI, GPIO_MODE_OUTPUT, GPIO_PUPD_PULLDOWN, PSEL_0 | PSEL_1 | PSEL_2);
    gpio_clear(GPIOI, PSEL_0 | PSEL_1 | PSEL_2);

    nvic_enable_irq(NVIC_EXTI15_10_IRQ);
    nvic_enable_irq(NVIC_EXTI9_5_IRQ);
    nvic_set_priority(NVIC_EXTI15_10_IRQ, 1);
    nvic_set_priority(NVIC_EXTI9_5_IRQ, 1);
    gpio_mode_setup(GPIOH, GPIO_MODE_INPUT, GPIO_PUPD_PULLDOWN, PLINE_0);
    gpio_mode_setup(GPIOA, GPIO_MODE_INPUT, GPIO_PUPD_PULLDOWN, PLINE_1);
    gpio_mode_setup(GPIOB, GPIO_MODE_INPUT, GPIO_PUPD_PULLDOWN, PLINE_2 | PLINE_3);

    /* Set touch screen interrupt line */
    gpio_mode_setup(GPIOI, GPIO_MODE_INPUT, GPIO_PUPD_NONE, TS_PIN);


    exti_select_source(PLINE_0, GPIOH);
    exti_select_source(PLINE_1, GPIOA);
    exti_select_source(PLINE_2, GPIOB);
    exti_select_source(PLINE_3, GPIOB);
    exti_select_source(TS_PIN, GPIOI);
}

static void button_start_read(void)
{
    exti_set_trigger(PLINE_0 | PLINE_1 | PLINE_2 | PLINE_3 | TS_PIN, EXTI_TRIGGER_RISING);
    exti_enable_request(PLINE_0 | PLINE_1 | PLINE_2 | PLINE_3|TS_PIN);
}

void isr_exti15_10(void)
{
    exti_reset_request(PLINE_2);
    exti_reset_request(PLINE_3);
    if (exti_get_flag_status(TS_PIN))
        exti_reset_request(TS_PIN);
    button_press_pending++;
}

void isr_exti9_5(void)
{
    exti_reset_request(PLINE_1);
    exti_reset_request(PLINE_0);
    button_press_pending++;
}


/*** Event generation interface ***/


struct input_status Input_status = {};

#define TS_TOUCH_NONE     0
#define TS_TOUCH_DETECTED 1

int input_detect_touch(void)
{
    uint8_t n_touch = 0;
    n_touch = ft5336_TS_DetectTouch(TS_I2C_ADDR);
    if (n_touch > 0) {
        uint16_t x, y;
        ft5336_TS_GetXY(TS_I2C_ADDR, &y, &x);
        Input_status.ts.Y = y;
        Input_status.ts.X = x;
        Input_status.start_touch = jiffies;
        return TS_TOUCH_DETECTED;
    } else 
        return TS_TOUCH_NONE;
}
    
static int input_process_buttons(void)
{
    int b_pressed = -1;
    struct user_button *b;
    uint8_t n_touch = 0;

    if (button_press_pending) {
        n_touch = input_detect_touch();
        if (n_touch  != TS_TOUCH_NONE) {
            b = &Input_status.b[TOUCHSCREEN];
            b->pressed = 1;
            return 1;
        } else if (gpio_get(GPIOH, PLINE_0))
            b_pressed = 0;
        else if (gpio_get(GPIOA, PLINE_1))
            b_pressed = 1;
        else if (gpio_get(GPIOB, PLINE_2))
            b_pressed = 2;
        else if (gpio_get(GPIOB, PLINE_3))
            b_pressed = 3;
        else {
            button_press_pending = 0;
            return 0;
        }

        b_pressed += 4 * button_current_latch;
        if (b_pressed >= N_BUTTONS)
            return 0;

        b = &Input_status.b[b_pressed];

        if (b->transition_start_timestamp == 0U) {
            b->transition_start_timestamp = jiffies;
            return 0;
        }
        if ((jiffies - b->transition_start_timestamp) > BUTTON_DEBOUNCE_TIME) {
            button_press_pending = 0;
            b->pressed = 1;
            return 1;
        }
    } else if (jiffies >= button_latch_switchtime) {
        int i, start = 4 *button_current_latch,
            end = start + 4;
        button_latch_switchtime = jiffies + 5;
        for (i = start; i < end; i++)
            Input_status.b[i].transition_start_timestamp = 0;
        switch (button_current_latch) {
            case 0:
                gpio_clear(GPIOI, PSEL_0 | PSEL_2);
                gpio_set(GPIOI, PSEL_1);
                button_current_latch = 1;
                break;
            case 1:
                gpio_clear(GPIOI, PSEL_0 | PSEL_1);
                gpio_set(GPIOI, PSEL_2);
                button_current_latch = 2;
                break;
            case 2:
            default:
                gpio_clear(GPIOI, PSEL_1 | PSEL_2);
                gpio_set(GPIOI, PSEL_0);
                button_current_latch = 0;
                break;
        }
    }
    return 0;
}

int input_get_swl(void)
{
    return gpio_get(GPIOF, (1 << 10) );
}

int input_get_swr(void)
{
    return gpio_get(GPIOF, (1 << 7) );
}

static void adc_setup(void)
{
    int i;
    rcc_periph_clock_enable(RCC_ADC3);
    rcc_periph_clock_enable(RCC_GPIOA);
    rcc_periph_clock_enable(RCC_GPIOF);
    gpio_mode_setup(GPIOA, GPIO_MODE_ANALOG, 0,  GPIO0);
    gpio_mode_setup(GPIOF, GPIO_MODE_ANALOG, 0, GPIO6 | GPIO8 | GPIO9);
    adc_init();
}

void input_init(void)
{
    button_setup();
    adc_setup();
    button_start_read();
}

void input_run(uint32_t ev, void *arg)
{
    int p = input_process_buttons();
    if (p > 0) {
        trigger_event(EV_BUTTON);
    }
}

void input_setup(void)
{
    register_task(&input_task);
}

