#include "watchdog.h"
#include "stm32f7xx_hal.h"
#include <string.h>

/* ================================================================
 * IWDG Hardware Configuration
 * ================================================================ */

/* LSI frequency: approximately 32 kHz (typical, 20-40 kHz) */
#define LSI_FREQUENCY_HZ    32000

/* IWDG prescaler values */
#define IWDG_PSC_4          0   /* Divider = 4 */
#define IWDG_PSC_8          1   /* Divider = 8 */
#define IWDG_PSC_16         2   /* Divider = 16 */
#define IWDG_PSC_32         3   /* Divider = 32 */
#define IWDG_PSC_64         4   /* Divider = 64 */
#define IWDG_PSC_128        5   /* Divider = 128 */
#define IWDG_PSC_256        6   /* Divider = 256 */

/* Reload register range: 0-4095 */
#define IWDG_RELOAD_MIN     1
#define IWDG_RELOAD_MAX     4095

/* ================================================================
 * Watchdog State
 * ================================================================ */

typedef struct {
    bool is_initialized;        /* Whether watchdog has been initialized */
    bool is_running;            /* Whether watchdog is currently active */
    uint32_t timeout_ms;        /* Configured timeout in milliseconds */
    uint8_t prescaler;          /* IWDG prescaler value */
    uint16_t reload_value;      /* IWDG reload register value */
} WatchdogState;

static WatchdogState s_watchdog = {
    .is_initialized = false,
    .is_running = false,
    .timeout_ms = 0,
    .prescaler = IWDG_PSC_32,
    .reload_value = 0
};

static IWDG_HandleTypeDef s_iwdg_handle = {0};

/* ================================================================
 * Helper Functions
 * ================================================================ */

/* Calculate prescaler and reload values for desired timeout */
static bool watchdog_calculate_config(uint32_t timeout_ms,
                                       uint8_t *out_prescaler,
                                       uint16_t *out_reload)
{
    if (timeout_ms < 1 || timeout_ms > 32000) {
        return false;  /* Out of reasonable range */
    }

    /* Try prescalers from smallest to largest */
    const uint8_t prescalers[] = {IWDG_PSC_4, IWDG_PSC_8, IWDG_PSC_16,
                                   IWDG_PSC_32, IWDG_PSC_64, IWDG_PSC_128,
                                   IWDG_PSC_256};
    const uint16_t dividers[] = {4, 8, 16, 32, 64, 128, 256};

    for (int i = 0; i < 7; i++) {
        uint16_t divider = dividers[i];
        /* timeout_ms = (reload * divider * 1000) / LSI_FREQUENCY_HZ */
        uint32_t reload = (timeout_ms * LSI_FREQUENCY_HZ) / (divider * 1000);

        if (reload >= IWDG_RELOAD_MIN && reload <= IWDG_RELOAD_MAX) {
            *out_prescaler = prescalers[i];
            *out_reload = (uint16_t)reload;
            return true;
        }
    }

    return false;  /* No suitable prescaler found */
}

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

bool watchdog_init(uint32_t timeout_ms)
{
    if (s_watchdog.is_initialized) {
        return false;  /* Already initialized */
    }

    /* Validate and calculate prescaler/reload values */
    uint8_t prescaler;
    uint16_t reload;
    if (!watchdog_calculate_config(timeout_ms, &prescaler, &reload)) {
        return false;
    }

    s_watchdog.prescaler = prescaler;
    s_watchdog.reload_value = reload;
    s_watchdog.timeout_ms = timeout_ms;

    /* Configure and start IWDG */
    s_iwdg_handle.Instance = IWDG;
    s_iwdg_handle.Init.Prescaler = prescaler;
    s_iwdg_handle.Init.Reload = reload;
    s_iwdg_handle.Init.Window = reload;  /* Window == Reload means full timeout */

    HAL_IWDG_Init(&s_iwdg_handle);

    s_watchdog.is_initialized = true;
    s_watchdog.is_running = true;

    return true;
}

void watchdog_kick(void)
{
    if (s_watchdog.is_running) {
        HAL_IWDG_Refresh(&s_iwdg_handle);  /* Reset IWDG counter */
    }
}

uint32_t watchdog_get_timeout(void)
{
    return s_watchdog.timeout_ms;
}

bool watchdog_is_running(void)
{
    return s_watchdog.is_running;
}

bool watchdog_was_reset_by_watchdog(void)
{
    /* Check RCC reset source flags */
    /* IWDG reset sets the IWDGRSTF flag in RCC_CSR */
    uint32_t reset_flags = RCC->CSR;

    /* IWDGRSTF is bit 29 of RCC_CSR */
    bool was_iwdg_reset = (reset_flags & RCC_CSR_IWDGRSTF) != 0;

    /* Clear the flag by writing to RMVF (Bit 24) */
    if (was_iwdg_reset) {
        RCC->CSR |= RCC_CSR_RMVF;  /* Clear reset flags */
    }

    return was_iwdg_reset;
}
