#include "fan.h"
#include "stm32f7xx_hal.h"
#include "config.h"
#include <string.h>

/* ================================================================
 * Fan Hardware Configuration
 * ================================================================ */

#define FAN_PIN             GPIO_PIN_9
#define FAN_PORT            GPIOA
#define FAN_TIM             TIM1
#define FAN_TIM_CHANNEL     TIM_CHANNEL_2
#define FAN_PWM_FREQ_HZ     25000   /* 25 kHz for brushless fan */

/* ================================================================
 * Temperature Curve Parameters
 * ================================================================ */

#define TEMP_OFF            40      /* Fan off below this (°C) */
#define TEMP_LOW            50      /* Low speed threshold (°C) */
#define TEMP_HIGH           70      /* High speed threshold (°C) */

#define SPEED_OFF           0       /* Speed at TEMP_OFF (%) */
#define SPEED_LOW           30      /* Speed at TEMP_LOW (%) */
#define SPEED_HIGH          100     /* Speed at TEMP_HIGH (%) */

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

typedef struct {
    uint8_t current_speed;          /* Current speed 0-100% */
    uint8_t target_speed;           /* Target speed 0-100% */
    int16_t last_temperature;       /* Last temperature reading (°C) */
    float ramp_rate_per_ms;         /* Speed change rate (%/ms) */
    uint32_t last_ramp_time_ms;     /* When last ramp update occurred */
    bool is_ramping;                /* Speed is transitioning */
} FanState_t;

static FanState_t s_fan = {
    .current_speed = 0,
    .target_speed = 0,
    .last_temperature = 0,
    .ramp_rate_per_ms = 0.05f,     /* 5% per 100ms default */
    .last_ramp_time_ms = 0,
    .is_ramping = false
};

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

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

    /* Configure PA9 as TIM1_CH2 PWM output */
    GPIO_InitTypeDef gpio_init = {0};
    gpio_init.Pin = FAN_PIN;
    gpio_init.Mode = GPIO_MODE_AF_PP;
    gpio_init.Pull = GPIO_NOPULL;
    gpio_init.Speed = GPIO_SPEED_HIGH;
    gpio_init.Alternate = GPIO_AF1_TIM1;
    HAL_GPIO_Init(FAN_PORT, &gpio_init);

    /* Configure TIM1 for PWM:
     * Clock: 216MHz / PSC = output frequency
     * For 25kHz frequency: PSC = 346, ARR = 25
     * Duty cycle = CCR / ARR (e.g., 12.5/25 = 50%)
     */
    TIM_HandleTypeDef htim1 = {0};
    htim1.Instance = FAN_TIM;
    htim1.Init.Prescaler = 346 - 1;         /* 216MHz / 346 ≈ 624kHz clock */
    htim1.Init.CounterMode = TIM_COUNTERMODE_UP;
    htim1.Init.Period = 25 - 1;             /* 624kHz / 25 = 25kHz */
    htim1.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
    htim1.Init.RepetitionCounter = 0;
    HAL_TIM_PWM_Init(&htim1);

    /* Configure PWM on CH2: 0% duty initially (fan off) */
    TIM_OC_InitTypeDef oc_init = {0};
    oc_init.OCMode = TIM_OCMODE_PWM1;
    oc_init.Pulse = 0;                      /* Start at 0% duty (off) */
    oc_init.OCPolarity = TIM_OCPOLARITY_HIGH;
    oc_init.OCFastMode = TIM_OCFAST_DISABLE;
    HAL_TIM_PWM_ConfigChannel(&htim1, &oc_init, FAN_TIM_CHANNEL);

    /* Start PWM generation */
    HAL_TIM_PWM_Start(FAN_TIM, FAN_TIM_CHANNEL);

    s_fan.current_speed = 0;
    s_fan.target_speed = 0;
    s_fan.last_ramp_time_ms = 0;
}

/* ================================================================
 * Temperature Curve Calculation
 * ================================================================ */

static uint8_t fan_calculate_speed_from_temp(int16_t temp_celsius)
{
    if (temp_celsius < TEMP_OFF) {
        return SPEED_OFF;  /* Off below 40°C */
    }

    if (temp_celsius < TEMP_LOW) {
        /* Linear ramp from 0% to 30% between 40-50°C */
        int32_t temp_offset = temp_celsius - TEMP_OFF;  /* 0-10 */
        int32_t temp_range = TEMP_LOW - TEMP_OFF;       /* 10 */
        int32_t speed_range = SPEED_LOW - SPEED_OFF;    /* 30 */
        uint8_t speed = SPEED_OFF + (temp_offset * speed_range) / temp_range;
        return (speed > 100) ? 100 : speed;
    }

    if (temp_celsius < TEMP_HIGH) {
        /* Linear ramp from 30% to 100% between 50-70°C */
        int32_t temp_offset = temp_celsius - TEMP_LOW;  /* 0-20 */
        int32_t temp_range = TEMP_HIGH - TEMP_LOW;      /* 20 */
        int32_t speed_range = SPEED_HIGH - SPEED_LOW;   /* 70 */
        uint8_t speed = SPEED_LOW + (temp_offset * speed_range) / temp_range;
        return (speed > 100) ? 100 : speed;
    }

    return SPEED_HIGH;  /* 100% at 70°C and above */
}

/* ================================================================
 * PWM Duty Cycle Control
 * ================================================================ */

static void fan_set_pwm_duty(uint8_t percentage)
{
    /* Clamp to 0-100% */
    if (percentage > 100) percentage = 100;

    /* Convert percentage to PWM counts
     * ARR = 25 (0-24 counts for 0-96%, scale up to 25 for 100%)
     * Duty = (percentage * 25) / 100
     */
    uint32_t duty = (percentage * 25) / 100;
    if (duty > 25) duty = 25;

    /* Update CCR2 for TIM1_CH2 */
    TIM1->CCR2 = duty;
}

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

bool fan_set_speed(uint8_t percentage)
{
    if (percentage > 100) {
        return false;
    }

    s_fan.current_speed = percentage;
    s_fan.target_speed = percentage;
    s_fan.is_ramping = false;
    fan_set_pwm_duty(percentage);

    return true;
}

uint8_t fan_get_speed(void)
{
    return s_fan.current_speed;
}

bool fan_set_target_speed(uint8_t percentage)
{
    if (percentage > 100) {
        return false;
    }

    s_fan.target_speed = percentage;
    if (percentage == s_fan.current_speed) {
        s_fan.is_ramping = false;
    } else {
        s_fan.is_ramping = true;
    }

    return true;
}

void fan_update_temperature(int16_t temp_celsius)
{
    s_fan.last_temperature = temp_celsius;

    /* Calculate target speed from temperature curve */
    uint8_t new_target = fan_calculate_speed_from_temp(temp_celsius);
    fan_set_target_speed(new_target);
}

int16_t fan_get_temperature(void)
{
    return s_fan.last_temperature;
}

FanState fan_get_state(void)
{
    if (s_fan.current_speed == 0) return FAN_OFF;
    if (s_fan.current_speed <= 30) return FAN_LOW;
    if (s_fan.current_speed <= 60) return FAN_MEDIUM;
    if (s_fan.current_speed <= 99) return FAN_HIGH;
    return FAN_FULL;
}

void fan_set_ramp_rate(float percentage_per_ms)
{
    if (percentage_per_ms <= 0) {
        s_fan.ramp_rate_per_ms = 0.01f;  /* Minimum rate */
    } else if (percentage_per_ms > 10.0f) {
        s_fan.ramp_rate_per_ms = 10.0f;  /* Maximum rate */
    } else {
        s_fan.ramp_rate_per_ms = percentage_per_ms;
    }
}

bool fan_is_ramping(void)
{
    return s_fan.is_ramping;
}

void fan_tick(uint32_t now_ms)
{
    if (!s_fan.is_ramping) {
        return;
    }

    /* Calculate time elapsed since last ramp */
    if (s_fan.last_ramp_time_ms == 0) {
        s_fan.last_ramp_time_ms = now_ms;
        return;
    }

    uint32_t elapsed = now_ms - s_fan.last_ramp_time_ms;
    if (elapsed == 0) {
        return;  /* No time has passed */
    }

    /* Calculate speed change allowed in this time interval */
    float speed_change = s_fan.ramp_rate_per_ms * elapsed;
    int32_t new_speed;

    if (s_fan.target_speed > s_fan.current_speed) {
        /* Ramp up */
        new_speed = s_fan.current_speed + (int32_t)speed_change;
        if (new_speed >= s_fan.target_speed) {
            s_fan.current_speed = s_fan.target_speed;
            s_fan.is_ramping = false;
        } else {
            s_fan.current_speed = (uint8_t)new_speed;
        }
    } else {
        /* Ramp down */
        new_speed = s_fan.current_speed - (int32_t)speed_change;
        if (new_speed <= s_fan.target_speed) {
            s_fan.current_speed = s_fan.target_speed;
            s_fan.is_ramping = false;
        } else {
            s_fan.current_speed = (uint8_t)new_speed;
        }
    }

    /* Update PWM duty cycle */
    fan_set_pwm_duty(s_fan.current_speed);
    s_fan.last_ramp_time_ms = now_ms;
}

void fan_disable(void)
{
    fan_set_speed(0);
}
