#include "ultrasonic.h"
#include "stm32f7xx_hal.h"
#include "config.h"

/* ================================================================
 * HC-SR04 Ultrasonic Sensor Parameters
 * ================================================================ */

#define TRIGGER_PIN         GPIO_PIN_0
#define TRIGGER_PORT        GPIOA
#define ECHO_PIN            GPIO_PIN_1
#define ECHO_PORT           GPIOA
#define ECHO_TIM            TIM1
#define ECHO_TIM_CHANNEL    TIM_CHANNEL_2

/* Trigger pulse duration (10µs recommended) */
#define TRIGGER_PULSE_US    10

/* Echo timeout: max 30ms (corresponds to ~5m distance) */
#define ECHO_TIMEOUT_MS     30

/* Speed of sound: ~343 m/s @ 20°C
 * Distance = (pulse_time_us / 2) / (1000000 / 343000) mm
 * Distance = (pulse_time_us / 2) / 2.914 mm ≈ pulse_time_us / 5.828 mm
 * Or: distance_mm = (pulse_us * 343) / 2000000 = pulse_us / 5.828
 * Using approximation: distance_mm ≈ (pulse_us * 1000) / 5830
 */
#define US_TO_MM_NUMERATOR  1000
#define US_TO_MM_DENOMINATOR 5830

/* ================================================================
 * Internal State Machine
 * ================================================================ */

typedef struct {
    UltrasonicState state;
    uint32_t trigger_time_ms;           /* When trigger pulse was sent */
    uint32_t echo_start_ticks;          /* TIM1 counter at rising edge */
    uint32_t echo_end_ticks;            /* TIM1 counter at falling edge */
    uint32_t echo_width_us;             /* Calculated pulse width in µs */
    uint16_t distance_mm;               /* Last measured distance */
    bool last_valid;                    /* Was last measurement valid */
    ultrasonic_callback_t callback;     /* Result callback (optional) */
} UltrasonicState_t;

static UltrasonicState_t s_ultrasonic = {
    .state = ULTRASONIC_IDLE,
    .callback = NULL
};

static TIM_HandleTypeDef s_htim1 = {0};  /* Timer handle for IRQ handler */

/* ================================================================
 * Hardware Initialization
 * ================================================================ */

void ultrasonic_init(void)
{
    /* Enable GPIO and timer clocks */
    __HAL_RCC_GPIOA_CLK_ENABLE();
    __HAL_RCC_TIM1_CLK_ENABLE();

    /* Configure PA0 as trigger output (push-pull, fast slew) */
    GPIO_InitTypeDef gpio_init = {0};
    gpio_init.Pin = TRIGGER_PIN;
    gpio_init.Mode = GPIO_MODE_OUTPUT_PP;
    gpio_init.Pull = GPIO_NOPULL;
    gpio_init.Speed = GPIO_SPEED_HIGH;
    HAL_GPIO_Init(TRIGGER_PORT, &gpio_init);
    HAL_GPIO_WritePin(TRIGGER_PORT, TRIGGER_PIN, GPIO_PIN_RESET);

    /* Configure PA1 as alternate function (TIM1_CH2) */
    gpio_init.Pin = ECHO_PIN;
    gpio_init.Mode = GPIO_MODE_AF_PP;
    gpio_init.Pull = GPIO_PULLDOWN;
    gpio_init.Speed = GPIO_SPEED_HIGH;
    gpio_init.Alternate = GPIO_AF1_TIM1;
    HAL_GPIO_Init(ECHO_PORT, &gpio_init);

    /* Configure TIM1 for input capture on PA1 (TIM1_CH2)
     * Clock: 216MHz / PSC = 216 counts/µs (PSC=1 gives 1 count per ~4.6ns)
     * Use PSC=216 to get 1MHz clock → 1 count = 1µs
     * ARR=0xFFFF for 16-bit capture (max 65535µs ≈ 9.6m)
     */
    s_htim1.Instance = ECHO_TIM;
    s_htim1.Init.Prescaler = 216 - 1;        /* 216MHz / 216 = 1MHz (1µs per count) */
    s_htim1.Init.CounterMode = TIM_COUNTERMODE_UP;
    s_htim1.Init.Period = 0xFFFF;            /* 16-bit counter */
    s_htim1.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
    s_htim1.Init.RepetitionCounter = 0;
    HAL_TIM_IC_Init(&s_htim1);

    /* Configure input capture: CH2 on PA1, both rising and falling edges
     * TIM1_CH2 captures on both edges to measure echo pulse width
     */
    TIM_IC_InitTypeDef ic_init = {0};
    ic_init.ICPolarity = TIM_ICPOLARITY_RISING;  /* Start with rising edge */
    ic_init.ICSelection = TIM_ICSELECTION_DIRECTTI;
    ic_init.ICPrescaler = TIM_ICPSC_DIV1;        /* No prescaler */
    ic_init.ICFilter = 0;                         /* No filter */
    HAL_TIM_IC_ConfigChannel(&s_htim1, &ic_init, ECHO_TIM_CHANNEL);
    HAL_TIM_IC_Start_IT(&s_htim1, ECHO_TIM_CHANNEL);

    /* Enable input capture interrupt */
    HAL_NVIC_SetPriority(TIM1_CC_IRQn, 6, 0);
    HAL_NVIC_EnableIRQ(TIM1_CC_IRQn);

    /* Start the timer */
    HAL_TIM_Base_Start(&s_htim1);

    s_ultrasonic.state = ULTRASONIC_IDLE;
}

/* ================================================================
 * Public API
 * ================================================================ */

bool ultrasonic_trigger(void)
{
    /* Only trigger if not currently measuring */
    if (s_ultrasonic.state != ULTRASONIC_IDLE && s_ultrasonic.state != ULTRASONIC_COMPLETE) {
        return false;
    }

    /* Send 10µs trigger pulse on PA0 */
    HAL_GPIO_WritePin(TRIGGER_PORT, TRIGGER_PIN, GPIO_PIN_SET);

    /* Busy-wait for 10µs (non-ideal but simple and precise)
     * At 216MHz: 1 clock = ~4.6ns, so ~2160 clocks for 10µs
     */
    uint32_t start = HAL_GetTick();
    while ((HAL_GetTick() - start) < 1) {
        /* Wait at least 10µs — use sysclock counter for precision */
        for (volatile int i = 0; i < 2200; i++) __NOP();
    }
    HAL_GPIO_WritePin(TRIGGER_PORT, TRIGGER_PIN, GPIO_PIN_RESET);

    s_ultrasonic.state = ULTRASONIC_TRIGGERED;
    s_ultrasonic.trigger_time_ms = HAL_GetTick();
    s_ultrasonic.echo_start_ticks = 0;
    s_ultrasonic.echo_end_ticks = 0;

    return true;
}

void ultrasonic_set_callback(ultrasonic_callback_t callback)
{
    s_ultrasonic.callback = callback;
}

UltrasonicState ultrasonic_get_state(void)
{
    return s_ultrasonic.state;
}

bool ultrasonic_get_result(uint16_t *distance_mm, bool *is_valid)
{
    if (s_ultrasonic.state != ULTRASONIC_COMPLETE) {
        return false;
    }

    if (distance_mm) *distance_mm = s_ultrasonic.distance_mm;
    if (is_valid) *is_valid = s_ultrasonic.last_valid;

    s_ultrasonic.state = ULTRASONIC_IDLE;
    return true;
}

void ultrasonic_tick(uint32_t now_ms)
{
    /* Timeout detection: if measurement takes too long, mark as error */
    if (s_ultrasonic.state == ULTRASONIC_MEASURING) {
        if ((now_ms - s_ultrasonic.trigger_time_ms) > ECHO_TIMEOUT_MS) {
            s_ultrasonic.state = ULTRASONIC_COMPLETE;
            s_ultrasonic.distance_mm = 0;
            s_ultrasonic.last_valid = false;

            if (s_ultrasonic.callback) {
                s_ultrasonic.callback(0, false);
            }
        }
    }
}

/* ================================================================
 * TIM1 Input Capture Interrupt Handler
 * ================================================================ */

void TIM1_CC_IRQHandler(void)
{
    /* Check if capture interrupt on CH2 */
    if (__HAL_TIM_GET_FLAG(&s_htim1, TIM_FLAG_CC2) != RESET) {
        __HAL_TIM_CLEAR_FLAG(&s_htim1, TIM_FLAG_CC2);

        uint32_t capture_value = HAL_TIM_ReadCapturedValue(&s_htim1, ECHO_TIM_CHANNEL);

        if (s_ultrasonic.state == ULTRASONIC_TRIGGERED || s_ultrasonic.state == ULTRASONIC_MEASURING) {
            if (s_ultrasonic.echo_start_ticks == 0) {
                /* Rising edge: mark start of echo pulse */
                s_ultrasonic.echo_start_ticks = capture_value;
                s_ultrasonic.state = ULTRASONIC_MEASURING;

                /* Switch to falling edge detection for end of echo */
                TIM_IC_InitTypeDef ic_init = {0};
                ic_init.ICPolarity = TIM_ICPOLARITY_FALLING;
                ic_init.ICSelection = TIM_ICSELECTION_DIRECTTI;
                ic_init.ICPrescaler = TIM_ICPSC_DIV1;
                ic_init.ICFilter = 0;
                HAL_TIM_IC_ConfigChannel(&s_htim1, &ic_init, ECHO_TIM_CHANNEL);
            } else {
                /* Falling edge: mark end of echo pulse and calculate distance */
                s_ultrasonic.echo_end_ticks = capture_value;

                /* Calculate pulse width (accounting for timer overflow) */
                uint32_t width = (s_ultrasonic.echo_end_ticks >= s_ultrasonic.echo_start_ticks)
                    ? (s_ultrasonic.echo_end_ticks - s_ultrasonic.echo_start_ticks)
                    : (0xFFFF - s_ultrasonic.echo_start_ticks + s_ultrasonic.echo_end_ticks + 1);

                s_ultrasonic.echo_width_us = width;

                /* Convert pulse width to distance: distance_mm = pulse_us / 5.828
                 * Using integer arithmetic: (pulse_us * 1000) / 5830
                 */
                s_ultrasonic.distance_mm = (width * US_TO_MM_NUMERATOR) / US_TO_MM_DENOMINATOR;

                /* Validate range: 20-5000mm */
                s_ultrasonic.last_valid = (s_ultrasonic.distance_mm >= 20 && s_ultrasonic.distance_mm <= 5000);
                if (!s_ultrasonic.last_valid) {
                    s_ultrasonic.distance_mm = 0;
                }

                s_ultrasonic.state = ULTRASONIC_COMPLETE;

                /* Call callback if registered */
                if (s_ultrasonic.callback) {
                    s_ultrasonic.callback(s_ultrasonic.distance_mm, s_ultrasonic.last_valid);
                }

                /* Reset for next measurement */
                s_ultrasonic.echo_start_ticks = 0;
                s_ultrasonic.echo_end_ticks = 0;
            }
        }
    }

    HAL_TIM_IRQHandler(&s_htim1);
}
