#ifndef STACKER_H
#define STACKER_H

#include <memory>
#include <vector>
#include <type_traits>
#include "../buffer/imagebuffer.h"
#include <opencv2/opencv.hpp>
#include "stacker_settings.h"
#include "../io/logger.h"

/**
 * @brief The Stacker class handles image stacking operations.
 *
 * This class provides functionality for stacking multiple images to improve
 * signal-to-noise ratio. It supports different bit depths through templates.
 * The template parameter T can be uint8_t for 8-bit images or uint16_t for 16-bit images.
 */
template <typename T>
class Stacker
{
    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,
                      "Stacker only supports uint8_t and uint16_t pixel types");

        /**
         * @brief Construct a new Stacker object
         */
        Stacker();

        /**
         * @brief Destroy the Stacker object
         */
        ~Stacker();

        /**
         * @brief Set the reference frame
         * @param referenceFrame The reference frame
         */
        void setReferenceFrame(const std::shared_ptr<ImageBuffer<T>> &referenceFrame);

        /**
         * @brief Add a frame to the stack
         * @param frame The frame to add
         */
        void addFrame(const std::shared_ptr<ImageBuffer<T>> &frame);

        /**
         * @brief Set the dark frame for dark subtraction
         * @param darkFrame The dark frame
         */
        void setDarkFrame(const std::shared_ptr<ImageBuffer<T>> &darkFrame);

        /**
         * @brief Set whether to align frames
         * @param align Whether to align frames
         */
        void setAlignFrames(bool align);

        /**
         * @brief Set the maximum number of stars to use for alignment
         * @param maxStars The maximum number of stars
         */
        void setMaxAlignmentStars(int maxStars);

        /**
         * @brief Set the background adjustment percentage
         * @param percentage The background adjustment percentage (0-100)
         */
        void setBackgroundAdjustment(float percentage);

        /**
         * @brief Set the contrast adjustment percentage
         * @param percentage The contrast adjustment percentage (0-200)
         */
        void setContrastAdjustment(float percentage);

        /**
         * @brief Set the saturation adjustment percentage
         * @param percentage The saturation adjustment percentage (0-100)
         */
        void setSaturationAdjustment(float percentage);

        /**
         * @brief Set the compression percentage
         * @param percentage The compression percentage (0-100)
         */
        void setCompressionPercentage(float percentage);

        /**
         * @brief Set the median filter size
         * @param size The median filter size (0 to disable)
         */
        void setMedianFilterSize(int size);

        /**
         * @brief Set the local histogram equalization parameters
         * @param windowSize The size of the local window (0 to disable)
         * @param clipLimit The contrast limit to prevent over-amplification of noise (0.0-4.0)
         */
        void setLocalHistogramEqualizationParams(int windowSize, float clipLimit);

        /**
         * @brief Set the denoising parameters
         * @param method The denoising method to use
         * @param strength The strength of the denoising effect (1-30)
         */
        void setDenoising(StackerSettings::DenoisingMethod method, int strength);

        /**
         * @brief Set the sigma clipping parameters
         * @param frames The number of frames to use for sigma clipping
         * @param lower The lower bound for sigma clipping
         * @param upper The upper bound for sigma clipping
         */
        void setSigmaClippingParams(int frames, float lower, float upper);

        /**
         * @brief Stack images using mean stacking
         * @return The stacked image
         */
        std::shared_ptr<ImageBuffer<T>> stackMean();

        /**
         * @brief Stack images using median stacking
         * @return The stacked image
         */
        std::shared_ptr<ImageBuffer<T>> stackMedian();

        /**
         * @brief Stack images using sigma clipping
         * @return The stacked image
         */
        std::shared_ptr<ImageBuffer<T>> stackSigmaClip();

        /**
         * @brief Apply post-processing to a frame
         * @param frame The frame to process
         * @return The processed frame
         */
        std::shared_ptr<ImageBuffer<T>> applyPostProcessing(const std::shared_ptr<ImageBuffer<T>> &frame);

        /**
         * @brief Enable diagnostic mode to save intermediate results
         * @param enable Whether to enable diagnostic mode
         * @param outputPrefix The prefix for output files
         */
        void setDiagnosticMode(bool enable, const std::string &outputPrefix = "frame_");

        /**
         * @brief Set the frame quality threshold
         * @param threshold The threshold for frame quality (0.0-1.0)
         */
        void setQualityThreshold(float threshold);

        /**
         * @brief Enable benchmark mode to time operations
         * @param enable Whether to enable benchmark mode
         */
        void setBenchmarkMode(bool enable);

        /**
         * @brief Get the settings object
         * @return A shared pointer to the settings object
         */
        std::shared_ptr<StackerSettings> getSettings() const
        {
            return m_settings;
        }

    private:
        /**
         * @brief Perform pure background subtraction without additional processing
         * @param image The input image
         * @param backgroundModel The background model to subtract
         * @return The result of pure background subtraction
         */
        cv::Mat pureBackgroundSubtraction(const cv::Mat &image, const cv::Mat &backgroundModel);

        /**
         * @brief Align a frame to the reference frame
         * @param frame The frame to align
         * @return The aligned frame
         */
        std::shared_ptr<ImageBuffer<T>> alignFrame(const std::shared_ptr<ImageBuffer<T>> &frame);

        /**
         * @brief Apply dark subtraction to a frame
         * @param frame The frame to process
         * @return The processed frame
         */
        std::shared_ptr<ImageBuffer<T>> applyDarkSubtraction(const std::shared_ptr<ImageBuffer<T>> &frame);

        /**
         * @brief Apply background adjustment to a frame
         * @param frame The frame to process
         * @param percentage The background adjustment percentage
         * @return The processed frame
         */
        std::shared_ptr<ImageBuffer<T>> applyBackgroundAdjustment(const std::shared_ptr<ImageBuffer<T>> &frame, float percentage);

        /**
         * @brief Apply contrast adjustment to a frame
         * @param frame The frame to process
         * @param percentage The contrast adjustment percentage
         * @return The processed frame
         */
        std::shared_ptr<ImageBuffer<T>> applyContrastAdjustment(const std::shared_ptr<ImageBuffer<T>> &frame, float percentage);

        /**
         * @brief Apply saturation adjustment to a frame
         * @param frame The frame to process
         * @param percentage The saturation adjustment percentage
         * @return The processed frame
         */
        std::shared_ptr<ImageBuffer<T>> applySaturationAdjustment(const std::shared_ptr<ImageBuffer<T>> &frame, float percentage);

        /**
         * @brief Apply compression to a frame
         * @param frame The frame to process
         * @param percentage The compression percentage
         * @return The processed frame
         */
        std::shared_ptr<ImageBuffer<T>> applyCompression(const std::shared_ptr<ImageBuffer<T>> &frame, float percentage);

        /**
         * @brief Apply median filter to a frame
         * @param frame The frame to process
         * @param size The median filter size
         * @return The processed frame
         */
        std::shared_ptr<ImageBuffer<T>> applyMedianFilter(const std::shared_ptr<ImageBuffer<T>> &frame, int size);

        /**
         * @brief Apply local histogram equalization to a frame
         * @param frame The frame to process
         * @param windowSize The size of the local window (should be odd)
         * @param clipLimit The contrast limit to prevent over-amplification of noise (0.0-4.0)
         * @return The processed frame
         */
        std::shared_ptr<ImageBuffer<T>> applyLocalHistogramEqualization(
                                         const std::shared_ptr<ImageBuffer<T>> &frame,
                                         int windowSize,
                                         float clipLimit);

        /**
         * @brief Apply denoising to a frame
         * @param frame The frame to process
         * @param method The denoising method to use
         * @param strength The strength of the denoising effect (1-30)
         * @return The processed frame
         */
        std::shared_ptr<ImageBuffer<T>> applyDenoising(
                                         const std::shared_ptr<ImageBuffer<T>> &frame,
                                         StackerSettings::DenoisingMethod method,
                                         int strength);

        /**
         * @brief Calculate the quality score for a frame
         * @param frame The frame to evaluate
         * @return The quality score (0.0-1.0)
         */
        float calculateFrameQuality(const std::shared_ptr<ImageBuffer<T>> &frame);

        /**
         * @brief Detect stars in two images using SEP and match them
         * @param refImage The reference image
         * @param frameImage The frame image to align
         * @param refPoints Output vector of points in the reference image
         * @param framePoints Output vector of corresponding points in the frame image
         * @return True if stars were successfully detected and matched, false otherwise
         */
        bool detectStarsWithSEP(const cv::Mat &refImage, const cv::Mat &frameImage,
                                std::vector<cv::Point2f> &refPoints,
                                std::vector<cv::Point2f> &framePoints);

        /**
         * @brief Detect stars in two images using StellarSolver and match them
         * @param refImage The reference image
         * @param frameImage The frame image to align
         * @param refPoints Output vector of points in the reference image
         * @param framePoints Output vector of corresponding points in the frame image
         * @return True if stars were successfully detected and matched, false otherwise
         */
        bool detectStarsWithStellarSolver(const cv::Mat &refImage, const cv::Mat &frameImage,
                                          std::vector<cv::Point2f> &refPoints,
                                          std::vector<cv::Point2f> &framePoints);

    private:
        /**
         * @brief Save an intermediate result during stacking
         * @param result The current stacked image
         * @param frameIndex The index of the last added frame
         */
        void saveIntermediateResult(const std::shared_ptr<ImageBuffer<T>> &result, int frameIndex);

        // Settings
        std::shared_ptr<StackerSettings> m_settings;

        // Frames
        std::shared_ptr<ImageBuffer<T>> m_referenceFrame;
        std::shared_ptr<ImageBuffer<T>> m_darkFrame;
        std::vector<std::shared_ptr<ImageBuffer<T>>> m_frames;
};

// Common type aliases for convenience
using Stacker8bit = Stacker<uint8_t>;
using Stacker16bit = Stacker<uint16_t>;

// Include the template implementation
#include "stacker.tpp"

#endif // STACKER_H
