#include "hue_saturation.h"
#include <limits>

namespace image
{
    template <typename T>
    T clamp(T value, T max, T min)
    {
        if (value > max)
            value = max;
        else if (value < min)
            value = min;
        return value;
    };

    HueSaturation::HueSaturation()
    {
        overlap = 0.0f;

        for (int i = HUE_RANGE_ALL; i <= HUE_RANGE_MAGENTA; i++)
        {
            HueRange partition = (HueRange)i;
            hue[partition] = 0.0;
            lightness[partition] = 0.0;
            saturation[partition] = 0.0;
        }
    }

    double map_hue(HueSaturation config, HueRange range, double value)
    {
        value += (config.hue[HUE_RANGE_ALL] + config.hue[range]) / 2.0;

        if (value < 0)
            return value + 1.0;
        else if (value > 1.0)
            return value - 1.0;
        else
            return value;
    }

    double map_hue_overlap(HueSaturation config, HueRange primary_range, HueRange secondary_range, double value, double primary_intensity, double secondary_intensity)
    {
        double v = config.hue[primary_range] * primary_intensity +
                   config.hue[secondary_range] * secondary_intensity;

        value += (config.hue[HUE_RANGE_ALL] + v) / 2.0;

        if (value < 0)
            return value + 1.0;
        else if (value > 1.0)
            return value - 1.0;
        else
            return value;
    }

    double map_saturation(HueSaturation config,
                          HueRange range,
                          double value)
    {
        double v = config.saturation[HUE_RANGE_ALL] + config.saturation[range];
        value *= (v + 1.0);
        return clamp<double>(value, 1.0, 0.0);
    }

    double map_lightness(HueSaturation config, HueRange range, double value)
    {
        double v = (config.lightness[HUE_RANGE_ALL] + config.lightness[range]);
        if (v < 0)
            return value * (v + 1.0);
        else
            return value + (v * (1.0 - value));
    }

    void hue_saturation(Image &image, HueSaturation config)
    {
        float scale = image.maxval() - 1;

        double rgb_r, rgb_g, rgb_b;
        double hsl_h, hsl_s, hsl_l;

        float overlap = config.overlap / 2.0;

        for (size_t pixel = 0; pixel < image.width() * image.height(); pixel++)
        {
            rgb_r = image.get(image.width() * image.height() * 0 + pixel) / scale;
            rgb_g = image.get(image.width() * image.height() * 1 + pixel) / scale;
            rgb_b = image.get(image.width() * image.height() * 2 + pixel) / scale;

            rgb_to_hsl(rgb_r, rgb_g, rgb_b, hsl_h, hsl_s, hsl_l);
            {
                double h;
                int hue_counter;
                int hue = 0;
                int secondary_hue = 0;
                bool use_secondary_hue = false;
                float primary_intensity = 0.0;
                float secondary_intensity = 0.0;

                h = hsl_h * 6.0;

                for (hue_counter = 0; hue_counter < 7; hue_counter++)
                {
                    double hue_threshold = (double)hue_counter + 0.5;

                    if (h < ((double)hue_threshold + overlap))
                    {
                        hue = hue_counter;

                        if (overlap > 0.0 && h > ((double)hue_threshold - overlap))
                        {
                            use_secondary_hue = true;

                            secondary_hue = hue_counter + 1;

                            secondary_intensity = (h - (double)hue_threshold + overlap) / (2.0 * overlap);

                            primary_intensity = 1.0 - secondary_intensity;
                        }
                        else
                        {
                            use_secondary_hue = false;
                        }

                        break;
                    }
                }

                if (hue >= 6)
                {
                    hue = 0;
                    use_secondary_hue = false;
                }

                if (secondary_hue >= 6)
                {
                    secondary_hue = 0;
                }

                hue++;
                secondary_hue++;

                if (use_secondary_hue)
                {
                    hsl_h = map_hue_overlap(config, (HueRange)hue, (HueRange)secondary_hue, hsl_h, primary_intensity, secondary_intensity);
                    hsl_s = (map_saturation(config, (HueRange)hue, hsl_s) * primary_intensity + map_saturation(config, (HueRange)secondary_hue, hsl_s) * secondary_intensity);
                    hsl_l = (map_lightness(config, (HueRange)hue, hsl_l) * primary_intensity + map_lightness(config, (HueRange)secondary_hue, hsl_l) * secondary_intensity);
                }
                else
                {
                    hsl_h = map_hue(config, (HueRange)hue, hsl_h);
                    hsl_s = map_saturation(config, (HueRange)hue, hsl_s);
                    hsl_l = map_lightness(config, (HueRange)hue, hsl_l);
                }
            }
            hsl_to_rgb(hsl_h, hsl_s, hsl_l, rgb_r, rgb_g, rgb_b);

            image.set(image.width() * image.height() * 0 + pixel, rgb_r * scale);
            image.set(image.width() * image.height() * 1 + pixel, rgb_g * scale);
            image.set(image.width() * image.height() * 2 + pixel, rgb_b * scale);
        }
    }

    void rgb_to_hsl(double r, double g, double b, double &h, double &s, double &l)
    {
        double max, min, delta;

        max = std::max<double>(r, std::max<double>(g, b));
        min = std::min<double>(r, std::min<double>(g, b));

        l = (max + min) / 2.0;

        if (max == min)
        {
            s = 0.0;
            h = -1.0;
        }
        else
        {
            if (l <= 0.5)
                s = (max - min) / (max + min);
            else
                s = (max - min) / (2.0 - max - min);

            delta = max - min;

            if (delta == 0.0)
                delta = 1.0;

            if (r == max)
            {
                h = (g - b) / delta;
            }
            else if (g == max)
            {
                h = 2.0 + (b - r) / delta;
            }
            else
            {
                h = 4.0 + (r - g) / delta;
            }

            h /= 6.0;

            if (h < 0.0)
                h += 1.0;
        }
    }

    double hsl_value(double n1, double n2, double hue)
    {
        double val;

        if (hue > 6.0)
            hue -= 6.0;
        else if (hue < 0.0)
            hue += 6.0;

        if (hue < 1.0)
            val = n1 + (n2 - n1) * hue;
        else if (hue < 3.0)
            val = n2;
        else if (hue < 4.0)
            val = n1 + (n2 - n1) * (4.0 - hue);
        else
            val = n1;

        return val;
    }

    void hsl_to_rgb(double h, double s, double l, double &r, double &g, double &b)
    {
        if (s == 0)
        {
            r = l;
            g = l;
            b = l;
        }
        else
        {
            double m1, m2;

            if (l <= 0.5)
                m2 = l * (1.0 + s);
            else
                m2 = l + s - l * s;

            m1 = 2.0 * l - m2;

            r = hsl_value(m1, m2, h * 6.0 + 2.0);
            g = hsl_value(m1, m2, h * 6.0);
            b = hsl_value(m1, m2, h * 6.0 - 2.0);
        }
    }
}