/////////////////////////////////////////////////////////////////////////////////
// Paint.NET                                                                   //
// Copyright (C) Rick Brewster, Tom Jackson, and past contributors.            //
// Portions Copyright (C) Microsoft Corporation. All Rights Reserved.          //
// See license-pdn.txt for full licensing and attribution details.             //
/////////////////////////////////////////////////////////////////////////////////

using System;


namespace Pinta.Core
{
    /// <summary>
    /// Provides a set of standard UnaryPixelOps.
    /// </summary>
    public sealed class UnaryPixelOps
    {
        private UnaryPixelOps()
        {
        }

        /// <summary>
        /// Passes through the given color value.
        /// result(color) = color
        /// </summary>
        [Serializable]
        public class Identity
            : UnaryPixelOp
        {
            public override ColorBgra Apply(ColorBgra color)
            {
                return color;
            }

            public unsafe override void Apply(ColorBgra *dst, ColorBgra *src, int length)
            {
                //Memory.Copy(dst, src, (ulong)length * (ulong)sizeof(ColorBgra));
            }

            public unsafe override void Apply(ColorBgra* ptr, int length)
            {
                return;
            }
        }

        /// <summary>
        /// Always returns a constant color.
        /// </summary>
        [Serializable]
        public class Constant
            : UnaryPixelOp
        {
            private ColorBgra setColor;

            public override ColorBgra Apply(ColorBgra color)
            {
                return setColor;
            }

            public unsafe override void Apply(ColorBgra* dst, ColorBgra* src, int length)
            {
                while (length > 0)
                {
                    *dst = setColor;
                    ++dst;
                    --length;
                }
            }

            public unsafe override void Apply(ColorBgra* ptr, int length)
            {
                while (length > 0)
                {
                    *ptr = setColor;
                    ++ptr;
                    --length;
                }
            }

            public Constant(ColorBgra setColor)
            {
                this.setColor = setColor;
            }
        }

        /// <summary>
        /// Blends pixels with the specified constant color.
        /// </summary>
        [Serializable]
        public class BlendConstant
            : UnaryPixelOp
        {
            private ColorBgra blendColor;

            public override ColorBgra Apply(ColorBgra color)
            {
                int a = blendColor.A;
                int invA = 255 - a;

                int r = ((color.R * invA) + (blendColor.R * a)) / 256;
                int g = ((color.G * invA) + (blendColor.G * a)) / 256;
                int b = ((color.B * invA) + (blendColor.B * a)) / 256;
                byte a2 = ComputeAlpha(color.A, blendColor.A);

                return ColorBgra.FromBgra((byte)b, (byte)g, (byte)r, a2);
            }

            public BlendConstant(ColorBgra blendColor)
            {
                this.blendColor = blendColor;
            }
        }

        /// <summary>
        /// Used to set a given channel of a pixel to a given, predefined color.
        /// Useful if you want to set only the alpha value of a given region.
        /// </summary>
        [Serializable]
        public class SetChannel
            : UnaryPixelOp
        {
            private int channel;
            private byte setValue;

            public override ColorBgra Apply(ColorBgra color)
            {
                color[channel] = setValue;
                return color;
            }

            public override unsafe void Apply(ColorBgra* dst, ColorBgra* src, int length)
            {
                while (length > 0)
                {
                    *dst = *src;
                    (*dst)[channel] = setValue;
                    ++dst;
                    ++src;
                    --length;
                }
            }

            public override unsafe void Apply(ColorBgra* ptr, int length)
            {
                while (length > 0)
                {
                    (*ptr)[channel] = setValue;
                    ++ptr;
                    --length;
                }
            }


            public SetChannel(int channel, byte setValue)
            {
                this.channel = channel;
                this.setValue = setValue;
            }
        }

        /// <summary>
        /// Specialization of SetChannel that sets the alpha channel.
        /// </summary>
        /// <remarks>This class depends on the system being litte-endian with the alpha channel 
        /// occupying the 8 most-significant-bits of a ColorBgra instance.
        /// By the way, we use addition instead of bitwise-OR because an addition can be
        /// perform very fast (0.5 cycles) on a Pentium 4.</remarks>
        [Serializable]
        public class SetAlphaChannel
            : UnaryPixelOp
        {
            private UInt32 addValue;

            public override ColorBgra Apply(ColorBgra color)
            {
                return ColorBgra.FromUInt32((color.Bgra & 0x00ffffff) + addValue);
            }

            public override unsafe void Apply(ColorBgra* dst, ColorBgra* src, int length)
            {
                while (length > 0)
                {
                    dst->Bgra = (src->Bgra & 0x00ffffff) + addValue;
                    ++dst;
                    ++src;
                    --length;
                }
            }

            public override unsafe void Apply(ColorBgra* ptr, int length)
            {
                while (length > 0)
                {
                    ptr->Bgra = (ptr->Bgra & 0x00ffffff) + addValue;
                    ++ptr;
                    --length;
                }
            }

            public SetAlphaChannel(byte alphaValue)
            {
                addValue = (uint)alphaValue << 24;
            }
        }

        /// <summary>
        /// Specialization of SetAlphaChannel that always sets alpha to 255.
        /// </summary>
        [Serializable]
        public class SetAlphaChannelTo255
            : UnaryPixelOp
        {
            public override ColorBgra Apply(ColorBgra color)
            {
                return ColorBgra.FromUInt32(color.Bgra | 0xff000000);
            }

            public override unsafe void Apply(ColorBgra* dst, ColorBgra* src, int length)
            {
                while (length > 0)
                {
                    dst->Bgra = src->Bgra | 0xff000000;
                    ++dst;
                    ++src;
                    --length;
                }
            }

            public override unsafe void Apply(ColorBgra* ptr, int length)
            {
                while (length > 0)
                {
                    ptr->Bgra |= 0xff000000;
                    ++ptr;
                    --length;
                }
            }
        }

        /// <summary>
        /// Inverts a pixel's color, and passes through the alpha component.
        /// </summary>
        [Serializable]
        public class Invert
            : UnaryPixelOp
        {
            public override ColorBgra Apply(ColorBgra color)
            {
                return ColorBgra.FromBgra((byte)(255 - color.B), (byte)(255 - color.G), (byte)(255 - color.R), color.A);
            }
        }

        /// <summary>
        /// If the color is within the red tolerance, remove it
        /// </summary>
        [Serializable]
        public class RedEyeRemove
            : UnaryPixelOp
        {
            private int tolerence;
            private double setSaturation;

            public RedEyeRemove(int tol, int sat)
            {
                tolerence = tol;
                setSaturation = (double)sat / 100;
            }

            public override ColorBgra Apply(ColorBgra color)
            {
                // The higher the saturation, the more red it is
                int saturation = GetSaturation(color);

                // The higher the difference between the other colors, the more red it is
                int difference = color.R - Math.Max(color.B,color.G);

                // If it is within tolerence, and the saturation is high
                if ((difference > tolerence) && (saturation > 100)) 
                {
                    double i = 255.0 * color.GetIntensity();
                    byte ib = (byte)(i * setSaturation); // adjust the red color for user inputted saturation
                    return ColorBgra.FromBgra((byte)color.B,(byte)color.G, ib, color.A);
                }
                else
                {
                    return color;
                }
            }

            //Saturation formula from RgbColor.cs, public HsvColor ToHsv()
            private int GetSaturation(ColorBgra color)
            {
                double min;
                double max;
                double delta;

                double r = (double) color.R / 255;
                double g = (double) color.G / 255;
                double b = (double) color.B / 255;

                double s;

                min = Math.Min(Math.Min(r, g), b);
                max = Math.Max(Math.Max(r, g), b);
                delta = max - min;

                if (max == 0 || delta == 0) 
                {
                    // R, G, and B must be 0, or all the same.
                    // In this case, S is 0, and H is undefined.
                    // Using H = 0 is as good as any...
                    s = 0;
                } 
                else
                {
                    s = delta / max;
                }

                return (int)(s * 255);
            }               
        }

        /// <summary>
        /// Inverts a pixel's color and its alpha component.
        /// </summary>
        [Serializable]
        public class InvertWithAlpha
            : UnaryPixelOp
        {
            public override ColorBgra Apply(ColorBgra color)
            {
                return ColorBgra.FromBgra((byte)(255 - color.B), (byte)(255 - color.G), (byte)(255 - color.R), (byte)(255 - color.A));
            }
        }

        /// <summary>
        /// Averages the input color's red, green, and blue channels. The alpha component
        /// is unaffected.
        /// </summary>
        [Serializable]
        public class AverageChannels
            : UnaryPixelOp
        {
            public override ColorBgra Apply(ColorBgra color)
            {
                byte average = (byte)(((int)color.R + (int)color.G + (int)color.B) / 3);
                return ColorBgra.FromBgra(average, average, average, color.A);
            }
        }

        [Serializable]
        public class Desaturate
            : UnaryPixelOp
        {
            public override ColorBgra Apply(ColorBgra color)
            {
                byte i = color.GetIntensityByte();
                return ColorBgra.FromBgra(i, i, i, color.A);
            }

            public unsafe override void Apply(ColorBgra* ptr, int length)
            {
                while (length > 0)
                {
                    byte i = ptr->GetIntensityByte();

                    ptr->R = i;
                    ptr->G = i;
                    ptr->B = i;

                    ++ptr;
                    --length;
                }
            }

            public unsafe override void Apply(ColorBgra* dst, ColorBgra* src, int length)
            {
                while (length > 0)
                {
                    byte i = src->GetIntensityByte();

                    dst->B = i;
                    dst->G = i;
                    dst->R = i;
                    dst->A = src->A;

                    ++dst;
                    ++src;
                    --length;
                }
            }
        }

        [Serializable]
        public class LuminosityCurve
            : UnaryPixelOp
        {
            public byte[] Curve = new byte[256];

            public LuminosityCurve()
            {    
                for (int i = 0; i < 256; ++i)
                {
                    Curve[i] = (byte)i;
                }
            }

            public override ColorBgra Apply(ColorBgra color)
            {
                byte lumi = color.GetIntensityByte();
                int diff = Curve[lumi] - lumi;

                return ColorBgra.FromBgraClamped(
                    color.B + diff,
                    color.G + diff,
                    color.R + diff,
                    color.A);
            }
        }

        [Serializable]
        public class ChannelCurve
            : UnaryPixelOp
        {
            public byte[] CurveB = new byte[256];
            public byte[] CurveG = new byte[256];
            public byte[] CurveR = new byte[256];

            public ChannelCurve()
            {
                for (int i = 0; i < 256; ++i)
                {
                    CurveB[i] = (byte)i;
                    CurveG[i] = (byte)i;
                    CurveR[i] = (byte)i;
                }
            }

            public override unsafe void Apply(ColorBgra* dst, ColorBgra* src, int length)
            {
                while (--length >= 0)
                {
                    dst->B = CurveB[src->B];
                    dst->G = CurveG[src->G];
                    dst->R = CurveR[src->R];
                    dst->A = src->A;

                    ++dst;
                    ++src;
                }
            }

            public override unsafe void Apply(ColorBgra* ptr, int length)
            {
                while (--length >= 0)
                {
                    ptr->B = CurveB[ptr->B];
                    ptr->G = CurveG[ptr->G];
                    ptr->R = CurveR[ptr->R];

                    ++ptr;
                }
            }

            public override ColorBgra Apply(ColorBgra color)
            {
                return ColorBgra.FromBgra(CurveB[color.B], CurveG[color.G], CurveR[color.R], color.A);
            }

//            public override void Apply(Surface dst, Point dstOffset, Surface src, Point srcOffset, int scanLength)
//            {
//                base.Apply (dst, dstOffset, src, srcOffset, scanLength);
//            }
        }

        [Serializable]
        public class Level
            : ChannelCurve,
              ICloneable
        {
            private ColorBgra colorInLow;
            public ColorBgra ColorInLow 
            {
                get 
                {
                    return colorInLow; 
                }

                set 
                {
                    if (value.R == 255) 
                    {
                        value.R = 254;
                    }

                    if (value.G == 255)
                    {
                        value.G = 254;
                    }

                    if (value.B == 255)
                    {
                        value.B = 254;
                    }

                    if (colorInHigh.R < value.R + 1) 
                    {
                        colorInHigh.R = (byte)(value.R + 1);
                    }

                    if (colorInHigh.G < value.G + 1) 
                    {
                        colorInHigh.G = (byte)(value.R + 1);
                    }

                    if (colorInHigh.B < value.B + 1) 
                    {
                        colorInHigh.B = (byte)(value.R + 1);
                    }

                    colorInLow = value;
                    UpdateLookupTable();
                }
            }

            private ColorBgra colorInHigh;
            public ColorBgra ColorInHigh 
            {
                get 
                {
                    return colorInHigh;
                }

                set 
                {
                    if (value.R == 0) 
                    {
                        value.R = 1;
                    }

                    if (value.G == 0)
                    { 
                        value.G = 1;
                    }

                    if (value.B == 0)
                    {
                        value.B = 1;
                    }

                    if (colorInLow.R > value.R - 1) 
                    {
                        colorInLow.R = (byte)(value.R - 1);
                    }

                    if (colorInLow.G > value.G - 1) 
                    {
                        colorInLow.G = (byte)(value.R - 1);
                    }

                    if (colorInLow.B > value.B - 1) 
                    {
                        colorInLow.B = (byte)(value.R - 1);
                    }

                    colorInHigh = value;
                    UpdateLookupTable();
                }
            }

            private ColorBgra colorOutLow;
            public ColorBgra ColorOutLow 
            {
                get 
                {
                    return colorOutLow;
                }

                set 
                {
                    if (value.R == 255) 
                    {
                        value.R = 254;
                    }

                    if (value.G == 255)
                    {
                        value.G = 254;
                    }

                    if (value.B == 255)
                    {
                        value.B = 254;
                    }

                    if (colorOutHigh.R < value.R + 1) 
                    {
                        colorOutHigh.R = (byte)(value.R + 1);
                    }

                    if (colorOutHigh.G < value.G + 1) 
                    {
                        colorOutHigh.G = (byte)(value.G + 1);
                    }

                    if (colorOutHigh.B < value.B + 1) 
                    {
                        colorOutHigh.B = (byte)(value.B + 1);
                    }

                    colorOutLow = value;
                    UpdateLookupTable();
                }
            }

            private ColorBgra colorOutHigh;
            public ColorBgra ColorOutHigh 
            {
                get 
                {
                    return colorOutHigh;
                }

                set 
                {
                    if (value.R == 0) 
                    {
                        value.R = 1;
                    }

                    if (value.G == 0)
                    { 
                        value.G = 1;
                    }

                    if (value.B == 0)
                    {
                        value.B = 1;
                    }

                    if (colorOutLow.R > value.R - 1) 
                    {
                        colorOutLow.R = (byte)(value.R - 1);
                    }

                    if (colorOutLow.G > value.G - 1) 
                    {
                        colorOutLow.G = (byte)(value.G - 1);
                    }

                    if (colorOutLow.B > value.B - 1) 
                    {
                        colorOutLow.B = (byte)(value.B - 1);
                    }

                    colorOutHigh = value;
                    UpdateLookupTable();
                }       
            }               
                        
            private float[] gamma = new float[3];
            public float GetGamma(int index) 
            {               
                if (index < 0 || index >= 3) 
                {
                    throw new ArgumentOutOfRangeException("index", index, "Index must be between 0 and 2");
                }

                return gamma[index];
            }

            public void SetGamma(int index, float val) 
            {
                if (index < 0 || index >= 3) 
                {
                    throw new ArgumentOutOfRangeException("index", index, "Index must be between 0 and 2");
                }

                gamma[index] = Utility.Clamp(val, 0.1f, 10.0f);
                UpdateLookupTable();
            }

            public bool isValid = true;

            public static Level AutoFromLoMdHi(ColorBgra lo, ColorBgra md, ColorBgra hi) 
            {
                float[] gamma = new float[3];

                for (int i = 0; i < 3; i++)
                {
                    if (lo[i] < md[i] && md[i] < hi[i])
                    {
                        gamma[i] = (float)Utility.Clamp(Math.Log(0.5, (float)(md[i] - lo[i]) / (float)(hi[i] - lo[i])), 0.1, 10.0);
                    }
                    else
                    {
                        gamma[i] = 1.0f;
                    }
                }

                return new Level(lo, hi, gamma, ColorBgra.Black, ColorBgra.White);
            }

            private void UpdateLookupTable() 
            {
                for (int i = 0; i < 3; i++) 
                {
                    if (colorOutHigh[i] < colorOutLow[i] ||
                        colorInHigh[i] <= colorInLow[i] ||
                        gamma[i] < 0)
                    {
                        isValid = false;
                        return;
                    }

                    for (int j = 0; j < 256; j++) 
                    {
                        ColorBgra col = Apply(j, j, j);
                        CurveB[j] = col.B;
                        CurveG[j] = col.G;
                        CurveR[j] = col.R;
                    }
                }
            }

            public Level() 
                : this(ColorBgra.Black,
                       ColorBgra.White,
                       new float[] { 1, 1, 1 },
                       ColorBgra.Black,
                       ColorBgra.White)
            {
            }

            public Level(ColorBgra in_lo, ColorBgra in_hi, float[] gamma, ColorBgra out_lo, ColorBgra out_hi)
            {
                colorInLow = in_lo;
                colorInHigh = in_hi;
                colorOutLow = out_lo;
                colorOutHigh = out_hi;

                if (gamma.Length != 3) 
                {
                    throw new ArgumentException("gamma", "gamma must be a float[3]");
                }

                this.gamma = gamma;
                UpdateLookupTable();
            }

            public ColorBgra Apply(float r, float g, float b) 
            {
                ColorBgra ret = new ColorBgra();
                float[] input = new float[] { b, g, r };

                for (int i = 0; i < 3; i++) 
                {
                    float v = (input[i] - colorInLow[i]);

                    if (v < 0)
                    {
                        ret[i] = colorOutLow[i];
                    }
                    else if (v + colorInLow[i] >= colorInHigh[i])
                    {
                        ret[i] = colorOutHigh[i];
                    }
                    else
                    {
                        ret[i] = (byte)Utility.Clamp(
                            colorOutLow[i] + (colorOutHigh[i] - colorOutLow[i]) * Math.Pow(v / (colorInHigh[i] - colorInLow[i]), gamma[i]),
                            0.0f,
                            255.0f);
                    }
                }

                return ret;
            }

            public void UnApply(ColorBgra after, float[] beforeOut, float[] slopesOut) 
            {
                if (beforeOut.Length != 3) 
                {
                    throw new ArgumentException("before must be a float[3]", "before");
                }

                if (slopesOut.Length != 3) 
                {
                    throw new ArgumentException("slopes must be a float[3]", "slopes");
                }

                for (int i = 0; i < 3; i++) 
                {
                    beforeOut[i] = colorInLow[i] + (colorInHigh[i] - colorInLow[i]) *
                        (float)Math.Pow((float)(after[i] - colorOutLow[i]) / (colorOutHigh[i] - colorOutLow[i]), 1 / gamma[i]);

                    slopesOut[i] = (float)(colorInHigh[i] - colorInLow[i]) / ((colorOutHigh[i] - colorOutLow[i]) * gamma[i]) *
                        (float)Math.Pow((float)(after[i] - colorOutLow[i]) / (colorOutHigh[i] - colorOutLow[i]), 1 / gamma[i] - 1);

                    if (float.IsInfinity(slopesOut[i]) || float.IsNaN(slopesOut[i])) 
                    {
                        slopesOut[i] = 0;
                    }
                }
            }

            public object Clone()
            {
                Level copy = new Level(colorInLow, colorInHigh, (float[])gamma.Clone(), colorOutLow, colorOutHigh);

                copy.CurveB = (byte[])this.CurveB.Clone();
                copy.CurveG = (byte[])this.CurveG.Clone();
                copy.CurveR = (byte[])this.CurveR.Clone();

                return copy;
            }
        }

        [Serializable]
        public class HueSaturationLightness
            : UnaryPixelOp
        {
            private int hueDelta;
            private int satFactor;
            private UnaryPixelOp blendOp;

            public HueSaturationLightness(int hueDelta, int satDelta, int lightness)
            {
                this.hueDelta = hueDelta;
                this.satFactor = (satDelta * 1024) / 100;

                if (lightness == 0)
                {
                    blendOp = new UnaryPixelOps.Identity();
                }
                else if (lightness > 0)
                {
                    blendOp = new UnaryPixelOps.BlendConstant(ColorBgra.FromBgra(255, 255, 255, (byte)((lightness * 255) / 100)));
                }
                else // if (lightness < 0)
                {
                    blendOp = new UnaryPixelOps.BlendConstant(ColorBgra.FromBgra(0, 0, 0, (byte)((-lightness * 255) / 100)));
                }
            }
            
			public override ColorBgra Apply (ColorBgra color)
            {
            	//adjust saturation
	            byte intensity = color.GetIntensityByte();
	            color.R = Utility.ClampToByte((intensity * 1024 + (color.R - intensity) * satFactor) >> 10);
	            color.G = Utility.ClampToByte((intensity * 1024 + (color.G - intensity) * satFactor) >> 10);
	            color.B = Utility.ClampToByte((intensity * 1024 + (color.B - intensity) * satFactor) >> 10);
	
	            HsvColor  hsvColor = (new RgbColor(color.R, color.G, color.B)).ToHsv();
				int hue = hsvColor.Hue;
	
	            hue += hueDelta;
	
	            while (hue < 0)
                {
                	hue += 360;
                }
                       
                while (hue > 360)
                {
                	hue -= 360;
                }
	
	            hsvColor.Hue = hue;
	
				RgbColor rgbColor=hsvColor.ToRgb();
				ColorBgra newColor = ColorBgra.FromBgr((byte)rgbColor.Blue, (byte)rgbColor.Green, (byte)rgbColor.Red);
	            newColor = blendOp.Apply(newColor);
	            newColor.A = color.A;
	              
	            return newColor;
            }
            
        }
		
		[Serializable]
		public class PosterizePixel
            : UnaryPixelOp
        {
            private byte[] redLevels;
            private byte[] greenLevels;
            private byte[] blueLevels;

            public PosterizePixel(int red, int green, int blue)
            {
                this.redLevels = CalcLevels(red);
                this.greenLevels = CalcLevels(green);
                this.blueLevels = CalcLevels(blue);
            }

            private static byte[] CalcLevels(int levelCount)
            {
                byte[] t1 = new byte[levelCount];

                for (int i = 1; i < levelCount; i++)
                {
                    t1[i] = (byte)((255 * i) / (levelCount - 1));
                }

                byte[] levels = new byte[256];

                int j = 0;
                int k = 0;

                for (int i = 0; i < 256; i++)
                {
                    levels[i] = t1[j];

                    k += levelCount;

                    if (k > 255)
                    {
                        k -= 255;
                        j++;
                    }
                }

                return levels;
            }

            public override ColorBgra Apply(ColorBgra color)
            {
                return ColorBgra.FromBgra(blueLevels[color.B], greenLevels[color.G], redLevels[color.R], color.A);
            }

            public unsafe override void Apply(ColorBgra* ptr, int length)
            {
                while (length > 0)
                {
                    ptr->B = this.blueLevels[ptr->B];
                    ptr->G = this.greenLevels[ptr->G];
                    ptr->R = this.redLevels[ptr->R];

                    ++ptr;
                    --length;
                }
            }

            public unsafe override void Apply(ColorBgra* dst, ColorBgra* src, int length)
            {
                while (length > 0)
                {
                    dst->B = this.blueLevels[src->B];
                    dst->G = this.greenLevels[src->G];
                    dst->R = this.redLevels[src->R];
                    dst->A = src->A;

                    ++dst;
                    ++src;
                    --length;
                }
            }
        }

    }
}
