#include "OBPRingBuffer.h"
#include <algorithm>
#include <limits>
#include <cmath>

template <typename T>
void RingBuffer<T>::initCommon()
{
    MIN_VAL = std::numeric_limits<T>::lowest();
    MAX_VAL = std::numeric_limits<T>::max();
    dblMIN_VAL = static_cast<double>(MIN_VAL);
    dblMAX_VAL = static_cast<double>(MAX_VAL);
    dataName = "";
    dataFmt = "";
    updFreq = -1;
    mltplr = 1;
    smallest = dblMIN_VAL;
    largest = dblMAX_VAL;
    bufLocker = xSemaphoreCreateMutex();
}

template <typename T>
RingBuffer<T>::RingBuffer()
    : capacity(0)
    , head(0)
    , first(0)
    , last(0)
    , count(0)
    , is_Full(false)
{
    initCommon();
    // <buffer> stays empty
}

template <typename T>
RingBuffer<T>::RingBuffer(size_t size)
    : capacity(size)
    , head(0)
    , first(0)
    , last(0)
    , count(0)
    , is_Full(false)
{
    initCommon();

    buffer.reserve(size);
    buffer.resize(size, MAX_VAL); // MAX_VAL indicate invalid values
}

// Specify meta data of buffer content
template <typename T>
void RingBuffer<T>::setMetaData(String name, String format, int updateFrequency, double multiplier, double minValue, double maxValue)
{
    GWSYNCHRONIZED(&bufLocker);
    dataName = name;
    dataFmt = format;
    updFreq = updateFrequency;
    mltplr = multiplier;
    smallest = std::max(dblMIN_VAL, minValue);
    largest = std::min(dblMAX_VAL, maxValue);
}

// Get meta data of buffer content
template <typename T>
bool RingBuffer<T>::getMetaData(String& name, String& format, int& updateFrequency, double& multiplier, double& minValue, double& maxValue)
{
    if (dataName == "" || dataFmt == "" || updFreq == -1) {
        return false; // Meta data not set
    }

    GWSYNCHRONIZED(&bufLocker);
    name = dataName;
    format = dataFmt;
    updateFrequency = updFreq;
    multiplier = mltplr;
    minValue = smallest;
    maxValue = largest;
    return true;
}

// Get meta data of buffer content
template <typename T>
bool RingBuffer<T>::getMetaData(String& name, String& format)
{
    if (dataName == "" || dataFmt == "") {
        return false; // Meta data not set
    }

    GWSYNCHRONIZED(&bufLocker);
    name = dataName;
    format = dataFmt;
    return true;
}

// Get buffer name
template <typename T>
String RingBuffer<T>::getName() const
{
    return dataName;
}

// Get buffer data format
template <typename T>
String RingBuffer<T>::getFormat() const
{
    return dataFmt;
}

// Add a new value to buffer
template <typename T>
void RingBuffer<T>::add(const double& value)
{
    GWSYNCHRONIZED(&bufLocker);
    if (value < smallest || value > largest) {
        buffer[head] = MAX_VAL; // Store MAX_VAL if value is out of range
    } else {
        buffer[head] = static_cast<T>(std::round(value * mltplr));
    }
    last = head;

    if (is_Full) {
        first = (first + 1) % capacity; // Move pointer to oldest element when overwriting
    } else {
        count++;
        if (count == capacity) {
            is_Full = true;
        }
    }
    // Serial.printf("Ringbuffer: value %.3f, multiplier: %.1f, buffer: %d\n", value, mltplr, buffer[head]);
    head = (head + 1) % capacity;
}

// Get value at specific position (0-based index from oldest to newest)
template <typename T>
double RingBuffer<T>::get(size_t index) const
{
    GWSYNCHRONIZED(&bufLocker);
    if (isEmpty() || index < 0 || index >= count) {
        return dblMAX_VAL;
    }

    size_t realIndex = (first + index) % capacity;
    return static_cast<double>(buffer[realIndex] / mltplr);
}

// Operator[] for convenient access (same as get())
template <typename T>
double RingBuffer<T>::operator[](size_t index) const
{
    return get(index);
}

// Get the first (oldest) value in the buffer
template <typename T>
double RingBuffer<T>::getFirst() const
{
    if (isEmpty()) {
        return dblMAX_VAL;
    }
    return get(0);
}

// Get the last (newest) value in the buffer
template <typename T>
double RingBuffer<T>::getLast() const
{
    if (isEmpty()) {
        return dblMAX_VAL;
    }
    return get(count - 1);
}

// Get the lowest value in the buffer
template <typename T>
double RingBuffer<T>::getMin() const
{
    if (isEmpty()) {
        return dblMAX_VAL;
    }

    double minVal = dblMAX_VAL;
    double value;
    for (size_t i = 0; i < count; i++) {
        value = get(i);
        if (value < minVal && value != dblMAX_VAL) {
            minVal = value;
        }
    }
    return minVal;
}

// Get minimum value of the last <amount> values of buffer
template <typename T>
double RingBuffer<T>::getMin(size_t amount) const
{
    if (isEmpty() || amount <= 0) {
        return dblMAX_VAL;
    }
    if (amount > count)
        amount = count;

    double minVal = dblMAX_VAL;
    double value;
    for (size_t i = 0; i < amount; i++) {
        value = get(count - 1 - i);
        if (value < minVal && value != dblMAX_VAL) {
            minVal = value;
        }
    }
    return minVal;
}

// Get the highest value in the buffer
template <typename T>
double RingBuffer<T>::getMax() const
{
    if (isEmpty()) {
        return dblMAX_VAL;
    }

    double maxVal = dblMIN_VAL;
    double value;
    for (size_t i = 0; i < count; i++) {
        value = get(i);
        if (value > maxVal && value != dblMAX_VAL) {
            maxVal = value;
        }
    }
    if (maxVal == dblMIN_VAL) { // no change of initial value -> buffer has only invalid values (MAX_VAL)
        maxVal = dblMAX_VAL;
    }
    return maxVal;
}

// Get maximum value of the last <amount> values of buffer
template <typename T>
double RingBuffer<T>::getMax(size_t amount) const
{
    if (isEmpty() || amount <= 0) {
        return dblMAX_VAL;
    }
    if (amount > count)
        amount = count;

    double maxVal = dblMIN_VAL;
    double value;
    for (size_t i = 0; i < amount; i++) {
        value = get(count - 1 - i);
        if (value > maxVal && value != dblMAX_VAL) {
            maxVal = value;
        }
    }
    if (maxVal == dblMIN_VAL) { // no change of initial value -> buffer has only invalid values (MAX_VAL)
        maxVal = dblMAX_VAL;
    }
    return maxVal;
}

// Get mid value between <min> and <max> value in the buffer
template <typename T>
double RingBuffer<T>::getMid() const
{
    if (isEmpty()) {
        return dblMAX_VAL;
    }

    return (getMin() + getMax()) / 2;
}

// Get mid value between <min> and <max> value of the last <amount> values of buffer
template <typename T>
double RingBuffer<T>::getMid(size_t amount) const
{
    if (isEmpty() || amount <= 0) {
        return dblMAX_VAL;
    }

    if (amount > count)
        amount = count;

    return (getMin(amount) + getMax(amount)) / 2;
}

// Get the median value in the buffer
template <typename T>
double RingBuffer<T>::getMedian() const
{
    if (isEmpty()) {
        return dblMAX_VAL;
    }

    // Create a temporary vector with current valid elements
    std::vector<T> temp;
    temp.reserve(count);

    for (size_t i = 0; i < count; i++) {
        temp.push_back(get(i));
    }

    // Sort to find median
    std::sort(temp.begin(), temp.end());

    if (count % 2 == 1) {
        // Odd number of elements
        return static_cast<double>(temp[count / 2]);
    } else {
        // Even number of elements - return average of middle two
        // Note: For integer types, this truncates. For floating point, it's exact.
        return static_cast<double>((temp[count / 2 - 1] + temp[count / 2]) / 2);
    }
}

// Get the median value of the last <amount> values of buffer
template <typename T>
double RingBuffer<T>::getMedian(size_t amount) const
{
    if (isEmpty() || amount <= 0) {
        return dblMAX_VAL;
    }
    if (amount > count)
        amount = count;

    // Create a temporary vector with current valid elements
    std::vector<T> temp;
    temp.reserve(amount);

    for (size_t i = 0; i < amount; i++) {
        temp.push_back(get(count - 1 - i));
    }

    // Sort to find median
    std::sort(temp.begin(), temp.end());

    if (amount % 2 == 1) {
        // Odd number of elements
        return static_cast<double>(temp[amount / 2]);
    } else {
        // Even number of elements - return average of middle two
        // Note: For integer types, this truncates. For floating point, it's exact.
        return static_cast<double>((temp[amount / 2 - 1] + temp[amount / 2]) / 2);
    }
}

// Get the buffer capacity (maximum size)
template <typename T>
size_t RingBuffer<T>::getCapacity() const
{
    return capacity;
}

// Get the current number of elements in the buffer
template <typename T>
size_t RingBuffer<T>::getCurrentSize() const
{
    return count;
}

// Get the first index of buffer
template <typename T>
size_t RingBuffer<T>::getFirstIdx() const
{
    return first;
}

// Get the last index of buffer
template <typename T>
size_t RingBuffer<T>::getLastIdx() const
{
    return last;
}

// Check if buffer is empty
template <typename T>
bool RingBuffer<T>::isEmpty() const
{
    return count == 0;
}

// Check if buffer is full
template <typename T>
bool RingBuffer<T>::isFull() const
{
    return is_Full;
}

// Get lowest possible value for buffer
template <typename T>
double RingBuffer<T>::getMinVal() const
{
    return dblMIN_VAL;
}

// Get highest possible value for buffer; used for unset/invalid buffer data
template <typename T>
double RingBuffer<T>::getMaxVal() const
{
    return dblMAX_VAL;
}

// Clear buffer
template <typename T>
void RingBuffer<T>::clear()
{
    GWSYNCHRONIZED(&bufLocker);
    head = 0;
    first = 0;
    last = 0;
    count = 0;
    is_Full = false;
}

// Delete buffer and set new size
template <typename T>
void RingBuffer<T>::resize(size_t newSize)
{
    GWSYNCHRONIZED(&bufLocker);
    capacity = newSize;
    head = 0;
    first = 0;
    last = 0;
    count = 0;
    is_Full = false;

    buffer.clear();
    buffer.reserve(newSize);
    buffer.resize(newSize, MAX_VAL);
}

// Get all current values in native buffer format as a vector
template <typename T>
std::vector<double> RingBuffer<T>::getAllValues() const
{
    std::vector<double> result;
    result.reserve(count);

    for (size_t i = 0; i < count; i++) {
        result.push_back(get(i));
    }

    return result;
}

// Get last <amount> values in native buffer format as a vector
template <typename T>
std::vector<double> RingBuffer<T>::getAllValues(size_t amount) const
{
    std::vector<double> result;

    if (isEmpty() || amount <= 0) {
        return result;
    }
    if (amount > count)
        amount = count;

    result.reserve(amount);

    for (size_t i = 0; i < amount; i++) {
        result.push_back(get(count - 1 - i));
    }

    return result;
}