#ifndef IMAGEBUFFER_H
#define IMAGEBUFFER_H

#include <vector>
#include <cstdint>
#include <memory>
#include <string>
#include <type_traits>

/**
 * @brief The ImageBuffer class represents an image buffer with templated pixel type.
 *
 * This class provides a container for image data with basic operations
 * for image manipulation. It supports SIMD-optimized operations where available.
 * The template parameter T can be uint8_t for 8-bit images or uint16_t for 16-bit images.
 */
template <typename T>
class ImageBuffer
{
    public:
        // Type checking to ensure only valid types are used
        static_assert(std::is_same<T, uint8_t>::value || std::is_same<T, uint16_t>::value,
                      "ImageBuffer only supports uint8_t and uint16_t pixel types");

        /**
         * @brief Construct an empty ImageBuffer
         */
        ImageBuffer();

        /**
         * @brief Construct an ImageBuffer with specified dimensions
         * @param width The width of the image
         * @param height The height of the image
         * @param channels The number of channels (1 for grayscale, 3 for RGB)
         */
        ImageBuffer(int width, int height, int channels = 1);

        /**
         * @brief Construct an ImageBuffer from existing data
         * @param data The image data
         * @param width The width of the image
         * @param height The height of the image
         * @param channels The number of channels
         * @param copy Whether to copy the data or take ownership
         */
        ImageBuffer(T* data, int width, int height, int channels = 1, bool copy = true);

        /**
         * @brief Destructor
         */
        ~ImageBuffer();

        /**
         * @brief Get the width of the image
         * @return The width in pixels
         */
        int getWidth() const
        {
            return m_width;
        }

        /**
         * @brief Get the height of the image
         * @return The height in pixels
         */
        int getHeight() const
        {
            return m_height;
        }

        /**
         * @brief Get the number of channels
         * @return The number of channels
         */
        int getChannels() const
        {
            return m_channels;
        }

        /**
         * @brief Get the total size of the buffer in bytes
         * @return The buffer size
         */
        size_t getBufferSize() const
        {
            return m_data.size();
        }

        /**
         * @brief Get a pointer to the raw data
         * @return Pointer to the data
         */
        T* getData()
        {
            return m_data.data();
        }

        /**
         * @brief Get a const pointer to the raw data
         * @return Const pointer to the data
         */
        const T* getData() const
        {
            return m_data.data();
        }

        /**
         * @brief Get the value of a pixel
         * @param x The x coordinate
         * @param y The y coordinate
         * @param channel The channel index
         * @return The pixel value
         */
        T getPixel(int x, int y, int channel = 0) const;

        /**
         * @brief Set the value of a pixel
         * @param x The x coordinate
         * @param y The y coordinate
         * @param value The new value
         * @param channel The channel index
         */
        void setPixel(int x, int y, T value, int channel = 0);

        /**
         * @brief Add another buffer to this one
         * @param other The buffer to add
         */
        void add(const ImageBuffer<T> &other);

        /**
         * @brief Subtract another buffer from this one
         * @param other The buffer to subtract
         */
        void subtract(const ImageBuffer<T> &other);

        /**
         * @brief Multiply this buffer by a scalar
         * @param factor The multiplication factor
         */
        void multiply(float factor);

        /**
         * @brief Divide this buffer by a scalar
         * @param divisor The division factor
         */
        void divide(float divisor);

        /**
         * @brief Create a deep copy of this buffer
         * @return A new buffer with the same data
         */
        std::shared_ptr<ImageBuffer<T>> clone() const;

        /**
         * @brief Resize the buffer
         * @param width The new width
         * @param height The new height
         * @param channels The new number of channels
         */
        void resize(int width, int height, int channels = 1);

        /**
         * @brief Clear the buffer (set all values to 0)
         */
        void clear();

        /**
         * @brief Fill the buffer with a specific value
         * @param value The value to fill with
         */
        void fill(T value);

        /**
         * @brief Calculate the mean value of the buffer
         * @return The mean value
         */
        float mean() const;

        /**
         * @brief Calculate the minimum value in the buffer
         * @return The minimum value
         */
        T min() const;

        /**
         * @brief Calculate the maximum value in the buffer
         * @return The maximum value
         */
        T max() const;

        /**
         * @brief Normalize the buffer to use the full range
         */
        void normalize();

        /**
         * @brief Check if this buffer has the same dimensions as another
         * @param other The other buffer
         * @return True if dimensions match
         */
        bool hasSameDimensions(const ImageBuffer<T> &other) const;

        /**
         * @brief Check if the data is stored contiguously in memory
         * @return True if the data is contiguous
         */
        bool isContiguous() const
        {
            // The data is always contiguous in our implementation
            // since we're using a std::vector
            return true;
        }

        /**
         * @brief Convert this buffer to another bit depth
         * @return A new buffer with the converted bit depth
         */
        template <typename U>
        std::shared_ptr<ImageBuffer<U>> convertTo() const;

    private:
        std::vector<T> m_data;
        int m_width;
        int m_height;
        int m_channels;
        bool m_ownData;

        /**
         * @brief Calculate the index in the data array for a given pixel
         * @param x The x coordinate
         * @param y The y coordinate
         * @param channel The channel index
         * @return The index in the data array
         */
        size_t getIndex(int x, int y, int channel = 0) const;
};

// Common type aliases for convenience
using ImageBuffer8bit = ImageBuffer<uint8_t>;
using ImageBuffer16bit = ImageBuffer<uint16_t>;

#endif // IMAGEBUFFER_H
