/////////////////////////////////////////////////////////////////////////////////
// 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;
using Cairo;

namespace Pinta.Core
{
    /// <summary>
    /// Histogram is used to calculate a histogram for a surface (in a selection,
    /// if desired). This can then be used to retrieve percentile, average, peak,
    /// and distribution information.
    /// </summary>
    public sealed class HistogramRgb 
        : Histogram
    {
        public HistogramRgb()
            : base(3, 256)
        {
            visualColors = new ColorBgra[]{     
                                              ColorBgra.Blue,
                                              ColorBgra.Green,
                                              ColorBgra.Red
                                          };
        }

        public override ColorBgra GetMeanColor() 
        {
            float[] mean = GetMean();
            return ColorBgra.FromBgr((byte)(mean[0] + 0.5f), (byte)(mean[1] + 0.5f), (byte)(mean[2] + 0.5f));
        }

        public override ColorBgra GetPercentileColor(float fraction) 
        {
            int[] perc = GetPercentile(fraction);

            return ColorBgra.FromBgr((byte)(perc[0]), (byte)(perc[1]), (byte)(perc[2]));
        }

        protected override unsafe void AddSurfaceRectangleToHistogram(ImageSurface surface, Gdk.Rectangle rect)
        {
            long[] histogramB = histogram[0];
            long[] histogramG = histogram[1];
            long[] histogramR = histogram[2];
            
            int rect_right = rect.Right;
            
            for (int y = rect.Y; y < rect.Bottom; ++y)
            {
                ColorBgra* ptr = surface.GetPointAddressUnchecked(rect.X, y);
				for (int x = rect.X; x < rect_right; ++x)
                {
                    ++histogramB[ptr->B];
                    ++histogramG[ptr->G];
                    ++histogramR[ptr->R];
                    ++ptr;
                }
            }
        }

        public void SetFromLeveledHistogram(HistogramRgb inputHistogram, UnaryPixelOps.Level upo)
        {
            if (inputHistogram == null || upo == null) 
            {
                return;
            }

            Clear();

            float[] before = new float[3];
            float[] slopes = new float[3];

            for (int c = 0; c < 3; c++)
            {
                long[] channelHistogramOutput = histogram[c];
                long[] channelHistogramInput = inputHistogram.histogram[c];

                for (int v = 0; v <= 255; v++)
                {
                    ColorBgra after = ColorBgra.FromBgr((byte)v, (byte)v, (byte)v);

                    upo.UnApply(after, before, slopes);

                    if (after[c] > upo.ColorOutHigh[c]
                        || after[c] < upo.ColorOutLow[c]
                        || (int)Math.Floor(before[c]) < 0
                        || (int)Math.Ceiling(before[c]) > 255
                        || float.IsNaN(before[c])) 
                    {
                        channelHistogramOutput[v] = 0;
                    }
                    else if (before[c] <= upo.ColorInLow[c]) 
                    {
                        channelHistogramOutput[v] = 0;

                        for (int i = 0; i <= upo.ColorInLow[c]; i++)
                        {
                            channelHistogramOutput[v] += channelHistogramInput[i];
                        }
                    } 
                    else if (before[c] >= upo.ColorInHigh[c])
                    {
                        channelHistogramOutput[v] = 0;

                        for (int i = upo.ColorInHigh[c]; i < 256; i++)
                        {
                            channelHistogramOutput[v] += channelHistogramInput[i];
                        }
                    }
                    else
                    {
                        channelHistogramOutput[v] = (int)(slopes[c] * Utility.Lerp(
                            channelHistogramInput[(int)Math.Floor(before[c])],
                            channelHistogramInput[(int)Math.Ceiling(before[c])],
                            before[c] - Math.Floor(before[c])));
                    }
                }
            }

            OnHistogramUpdated();
        }

        public UnaryPixelOps.Level MakeLevelsAuto() 
        {
            ColorBgra lo = GetPercentileColor(0.005f);
            ColorBgra md = GetMeanColor();
            ColorBgra hi = GetPercentileColor(0.995f);

            return UnaryPixelOps.Level.AutoFromLoMdHi(lo, md, hi);
        }
    }
}
