#include "imagebuffer.h"
#include <algorithm>
#include <numeric>
#include <cstring>
#include <stdexcept>
#include <limits>

// Implementation of template methods for ImageBuffer

template <typename T>
ImageBuffer<T>::ImageBuffer()
    : m_width(0), m_height(0), m_channels(0), m_ownData(true)
{
}

template <typename T>
ImageBuffer<T>::ImageBuffer(int width, int height, int channels)
    : m_width(width), m_height(height), m_channels(channels), m_ownData(true)
{
    if (width <= 0 || height <= 0 || channels <= 0)
    {
        throw std::invalid_argument("Invalid dimensions for ImageBuffer");
    }

    m_data.resize(width * height * channels);
}

template <typename T>
ImageBuffer<T>::ImageBuffer(T* data, int width, int height, int channels, bool copy)
    : m_width(width), m_height(height), m_channels(channels), m_ownData(copy)
{
    if (width <= 0 || height <= 0 || channels <= 0)
    {
        throw std::invalid_argument("Invalid dimensions for ImageBuffer");
    }

    if (copy)
    {
        m_data.resize(width * height * channels);
        std::memcpy(m_data.data(), data, width * height * channels * sizeof(T));
    }
    else
    {
        // This is a bit tricky with std::vector, we'd need a custom allocator
        // For now, we'll just copy the data
        m_data.resize(width * height * channels);
        std::memcpy(m_data.data(), data, width * height * channels * sizeof(T));
        m_ownData = true;
    }
}

template <typename T>
ImageBuffer<T>::~ImageBuffer()
{
    // Vector will clean up automatically
}

template <typename T>
size_t ImageBuffer<T>::getIndex(int x, int y, int channel) const
{
    if (x < 0 || x >= m_width || y < 0 || y >= m_height || channel < 0 || channel >= m_channels)
    {
        throw std::out_of_range("Pixel coordinates out of range");
    }

    return (y * m_width + x) * m_channels + channel;
}

template <typename T>
T ImageBuffer<T>::getPixel(int x, int y, int channel) const
{
    return m_data[getIndex(x, y, channel)];
}

template <typename T>
void ImageBuffer<T>::setPixel(int x, int y, T value, int channel)
{
    m_data[getIndex(x, y, channel)] = value;
}

template <typename T>
void ImageBuffer<T>::add(const ImageBuffer<T> &other)
{
    if (!hasSameDimensions(other))
    {
        throw std::invalid_argument("Cannot add buffers with different dimensions");
    }

    // Use SIMD if available
#ifdef HAVE_SSE
    // SSE implementation for x86_64
    if constexpr (std::is_same<T, uint8_t>::value)
    {
        // Implementation will be in simd_x86.cpp
        // For now, use a simple loop
        for (size_t i = 0; i < m_data.size(); ++i)
        {
            // Clamp to avoid overflow
            int sum = static_cast<int>(m_data[i]) + static_cast<int>(other.m_data[i]);
            m_data[i] = static_cast<T>(std::min(static_cast<int>(std::numeric_limits<T>::max()), sum));
        }
    }
    else
    {
        // 16-bit implementation
        for (size_t i = 0; i < m_data.size(); ++i)
        {
            // Clamp to avoid overflow
            int sum = static_cast<int>(m_data[i]) + static_cast<int>(other.m_data[i]);
            m_data[i] = static_cast<T>(std::min(static_cast<int>(std::numeric_limits<T>::max()), sum));
        }
    }
#elif defined(HAVE_NEON)
    // NEON implementation for ARM64
    // Implementation will be in simd_arm.cpp
    // For now, use a simple loop
    for (size_t i = 0; i < m_data.size(); ++i)
    {
        // Clamp to avoid overflow
        int sum = static_cast<int>(m_data[i]) + static_cast<int>(other.m_data[i]);
        m_data[i] = static_cast<T>(std::min(static_cast<int>(std::numeric_limits<T>::max()), sum));
    }
#else
    // Generic implementation
    for (size_t i = 0; i < m_data.size(); ++i)
    {
        // Clamp to avoid overflow
        int sum = static_cast<int>(m_data[i]) + static_cast<int>(other.m_data[i]);
        m_data[i] = static_cast<T>(std::min(static_cast<int>(std::numeric_limits<T>::max()), sum));
    }
#endif
}

template <typename T>
void ImageBuffer<T>::subtract(const ImageBuffer<T> &other)
{
    if (!hasSameDimensions(other))
    {
        throw std::invalid_argument("Cannot subtract buffers with different dimensions");
    }

    // Use SIMD if available
#ifdef HAVE_SSE
    // SSE implementation for x86_64
    // Implementation will be in simd_x86.cpp
    // For now, use a simple loop
    for (size_t i = 0; i < m_data.size(); ++i)
    {
        // Clamp to avoid underflow
        int diff = static_cast<int>(m_data[i]) - static_cast<int>(other.m_data[i]);
        m_data[i] = static_cast<T>(std::max(0, diff));
    }
#elif defined(HAVE_NEON)
    // NEON implementation for ARM64
    // Implementation will be in simd_arm.cpp
    // For now, use a simple loop
    for (size_t i = 0; i < m_data.size(); ++i)
    {
        // Clamp to avoid underflow
        int diff = static_cast<int>(m_data[i]) - static_cast<int>(other.m_data[i]);
        m_data[i] = static_cast<T>(std::max(0, diff));
    }
#else
    // Generic implementation
    for (size_t i = 0; i < m_data.size(); ++i)
    {
        // Clamp to avoid underflow
        int diff = static_cast<int>(m_data[i]) - static_cast<int>(other.m_data[i]);
        m_data[i] = static_cast<T>(std::max(0, diff));
    }
#endif
}

template <typename T>
void ImageBuffer<T>::multiply(float factor)
{
    // Use SIMD if available
#ifdef HAVE_SSE
    // SSE implementation for x86_64
    // Implementation will be in simd_x86.cpp
    // For now, use a simple loop
    for (size_t i = 0; i < m_data.size(); ++i)
    {
        float result = static_cast<float>(m_data[i]) * factor;
        m_data[i] = static_cast<T>(std::min(static_cast<float>(std::numeric_limits<T>::max()), result));
    }
#elif defined(HAVE_NEON)
    // NEON implementation for ARM64
    // Implementation will be in simd_arm.cpp
    // For now, use a simple loop
    for (size_t i = 0; i < m_data.size(); ++i)
    {
        float result = static_cast<float>(m_data[i]) * factor;
        m_data[i] = static_cast<T>(std::min(static_cast<float>(std::numeric_limits<T>::max()), result));
    }
#else
    // Generic implementation
    for (size_t i = 0; i < m_data.size(); ++i)
    {
        float result = static_cast<float>(m_data[i]) * factor;
        m_data[i] = static_cast<T>(std::min(static_cast<float>(std::numeric_limits<T>::max()), result));
    }
#endif
}

template <typename T>
void ImageBuffer<T>::divide(float divisor)
{
    if (divisor == 0.0f)
    {
        throw std::invalid_argument("Division by zero");
    }

    multiply(1.0f / divisor);
}

template <typename T>
std::shared_ptr<ImageBuffer<T>> ImageBuffer<T>::clone() const
{
    auto copy = std::make_shared<ImageBuffer<T>>(m_width, m_height, m_channels);
    std::copy(m_data.begin(), m_data.end(), copy->m_data.begin());
    return copy;
}

template <typename T>
void ImageBuffer<T>::resize(int width, int height, int channels)
{
    if (width <= 0 || height <= 0 || channels <= 0)
    {
        throw std::invalid_argument("Invalid dimensions for resize");
    }

    // Simple resize - just allocate new memory
    // A more sophisticated implementation would interpolate
    m_data.resize(width * height * channels);
    m_width = width;
    m_height = height;
    m_channels = channels;
}

template <typename T>
void ImageBuffer<T>::clear()
{
    std::fill(m_data.begin(), m_data.end(), 0);
}

template <typename T>
void ImageBuffer<T>::fill(T value)
{
    std::fill(m_data.begin(), m_data.end(), value);
}

template <typename T>
float ImageBuffer<T>::mean() const
{
    if (m_data.empty())
    {
        return 0.0f;
    }

    double sum = std::accumulate(m_data.begin(), m_data.end(), 0.0);
    return static_cast<float>(sum / m_data.size());
}

template <typename T>
T ImageBuffer<T>::min() const
{
    if (m_data.empty())
    {
        return 0;
    }

    return *std::min_element(m_data.begin(), m_data.end());
}

template <typename T>
T ImageBuffer<T>::max() const
{
    if (m_data.empty())
    {
        return 0;
    }

    return *std::max_element(m_data.begin(), m_data.end());
}

template <typename T>
void ImageBuffer<T>::normalize()
{
    if (m_data.empty())
    {
        return;
    }

    T minVal = min();
    T maxVal = max();

    if (minVal == maxVal)
    {
        return;  // Avoid division by zero
    }

    // Scale to full range
    for (size_t i = 0; i < m_data.size(); ++i)
    {
        m_data[i] = static_cast<T>((static_cast<float>(m_data[i] - minVal) / (maxVal - minVal)) * std::numeric_limits<T>::max());
    }
}

template <typename T>
bool ImageBuffer<T>::hasSameDimensions(const ImageBuffer<T> &other) const
{
    return m_width == other.m_width &&
           m_height == other.m_height &&
           m_channels == other.m_channels;
}

template <typename T>
template <typename U>
std::shared_ptr<ImageBuffer<U>> ImageBuffer<T>::convertTo() const
{
    auto result = std::make_shared<ImageBuffer<U>>(m_width, m_height, m_channels);

    if constexpr (std::is_same<T, uint8_t>::value && std::is_same<U, uint16_t>::value)
    {
        // 8-bit to 16-bit conversion
        for (size_t i = 0; i < m_data.size(); ++i)
        {
            // Scale from [0, 255] to [0, 65535]
            result->getData()[i] = static_cast<U>(m_data[i] * 257);
        }
    }
    else if constexpr (std::is_same<T, uint16_t>::value && std::is_same<U, uint8_t>::value)
    {
        // 16-bit to 8-bit conversion
        for (size_t i = 0; i < m_data.size(); ++i)
        {
            // Scale from [0, 65535] to [0, 255]
            result->getData()[i] = static_cast<U>(m_data[i] / 257);
        }
    }
    else
    {
        // Same type, just copy
        for (size_t i = 0; i < m_data.size(); ++i)
        {
            result->getData()[i] = static_cast<U>(m_data[i]);
        }
    }

    return result;
}

// Explicit template instantiations
template class ImageBuffer<uint8_t>;
template class ImageBuffer<uint16_t>;

// Explicit instantiation of convertTo methods
template std::shared_ptr<ImageBuffer<uint16_t>> ImageBuffer<uint8_t>::convertTo<uint16_t>() const;
template std::shared_ptr<ImageBuffer<uint8_t>> ImageBuffer<uint16_t>::convertTo<uint8_t>() const;
