/*
 * @Author: mypx
 * @Date: 2025-09-05 15:17:06
 * @LastEditTime: 2025-09-05 15:51:59
 * @LastEditors: mypx mypx_coder@163.com
 * @Description: Slope calculation module - Calculate data sequence slope using least squares method
 */
#include "etk_slope.h"
#include <math.h>

void etk_slope_init(etk_slope_t *slope, void *buffer, size_t buffer_size, EtkRingBufDataType type)
{
    if (slope == NULL || buffer == NULL || buffer_size == 0)
    {
        return;
    }
    etk_ringbuffer_init(&slope->rb, buffer, buffer_size, type);
}

/**
 * @brief Read value from buffer at specified index and convert to double type
 * @param slope Slope calculation instance
 * @param index Data index
 * @return double Converted value
 */
static double read_value_as_double(etk_slope_t *slope, int index)
{
    if (slope == NULL || index < 0)
    {
        return 0.0;
    }

    switch (slope->rb.data_type)
    {
    case ETK_RB_UINT16:
        return (double)etk_ringbuffer_peek_u16(&slope->rb, index);
    case ETK_RB_UINT32:
        return (double)etk_ringbuffer_peek_u32(&slope->rb, index);
    case ETK_RB_FLOAT:
        return (double)etk_ringbuffer_peek_f32(&slope->rb, index);
    case ETK_RB_DOUBLE:
        return etk_ringbuffer_peek_f64(&slope->rb, index);
    default:
        return 0.0;
    }
}

/**
 * @brief Update data and calculate current slope
 * @param slope Slope calculation instance
 * @param new_value New data value
 * @return double Calculated slope value, returns 0.0 when insufficient data
 */
double etk_slope_update_slope(etk_slope_t *slope, double new_value)
{
    if (slope == NULL)
    {
        return 0.0;
    }

    // 1. Write new value to ring buffer
    switch (slope->rb.data_type)
    {
    case ETK_RB_UINT16:
    {
        uint16_t val = (uint16_t)new_value;
        etk_ringbuffer_write_u16(&slope->rb, val);
        break;
    }
    case ETK_RB_UINT32:
    {
        uint32_t val = (uint32_t)new_value;
        etk_ringbuffer_write_u32(&slope->rb, val);
        break;
    }
    case ETK_RB_FLOAT:
    {
        float val = (float)new_value;
        etk_ringbuffer_write_f32(&slope->rb, val);
        break;
    }
    case ETK_RB_DOUBLE:
    {
        double val = (double)new_value;
        etk_ringbuffer_write_f64(&slope->rb, val);
        break;
    }
    default:
        break;
    }

    size_t count = etk_ringbuffer_get_count(&slope->rb);

    // 2. Cannot calculate slope with less than 2 data points
    if (count < 2)
        return 0.0;

    // 3. Calculate slope using optimized least squares method
    // y = kx + b, k = [n*Σ(xi*yi) - Σxi*Σyi] / [n*Σ(xi^2) - (Σxi)^2]

    // Use more efficient single-pass algorithm
    double sum_x = 0.0, sum_y = 0.0;
    double sum_xy = 0.0, sum_x2 = 0.0;

    for (size_t i = 0; i < count; i++)
    {
        double xi = i;
        double yi = read_value_as_double(slope, i);

        sum_x  += xi;
        sum_y  += yi;
        sum_xy += xi * yi;
        sum_x2 += xi * xi;
    }

    double denominator = count * sum_x2 - sum_x * sum_x;

    // Prevent division by zero using configurable precision threshold
    if (fabs(denominator) < ETK_SLOPE_EPSILON)
        return 0.0;

    double numerator = count * sum_xy - sum_x * sum_y;
    return numerator / denominator; // Return slope
}

/**
 * @brief Get current slope from buffer data (without adding new data)
 * @param slope Slope calculation instance
 * @return double Calculated slope value, returns 0.0 when insufficient data
 */
double etk_slope_get_current(etk_slope_t *slope)
{
    if (slope == NULL)
    {
        return 0.0;
    }

    size_t count = etk_ringbuffer_get_count(&slope->rb);

    // Cannot calculate slope with less than 2 data points
    if (count < 2)
        return 0.0;

    // Calculate slope using optimized least squares method
    double sum_x = 0.0, sum_y = 0.0;
    double sum_xy = 0.0, sum_x2 = 0.0;

    for (size_t i = 0; i < count; i++)
    {
        double xi = i;
        double yi = read_value_as_double(slope, i);

        sum_x  += xi;
        sum_y  += yi;
        sum_xy += xi * yi;
        sum_x2 += xi * xi;
    }

    double denominator = count * sum_x2 - sum_x * sum_x;

    // Prevent division by zero using configurable precision threshold
    if (fabs(denominator) < ETK_SLOPE_EPSILON)
        return 0.0;

    double numerator = count * sum_xy - sum_x * sum_y;
    return numerator / denominator;
}

/**
 * @brief Reset slope calculator, clear all historical data
 * @param slope Slope calculation instance
 */
void etk_slope_reset(etk_slope_t *slope)
{
    if (slope == NULL)
    {
        return;
    }

    etk_ringbuffer_clear(&slope->rb);
}
