using System;
using System.Drawing;
using System.Collections.Generic;

namespace Volpe.Cafe.EspressoUI.Charts
{
    /// <summary>
    /// Provides a color palette for painting series on a chart.
    /// </summary>
    [Serializable]
    public class ColorPalette
    {
        #region <<< Constructors >>>

        static ColorPalette()
        {
            // init themes
            AllPalettes = new ColorPalette[]
            {
                GetLightPalette(),
                GetDarkPalette(),
                GetLavenderPalette(),
                GetSoftBluePalette(),
                GetYellowHuesPalette(),
                GetFadedOrangePalette()
            };

            // add to dictionary to search by name
            Values = new Dictionary<string, ColorPalette>();
            foreach (var value in AllPalettes)
            {
                Values.Add(value.Name, value);
            }
        }

        private ColorPalette(string name, Color[] fillColor, Color[] borderColor)
        {
            if (fillColor == null || borderColor == null) {
                throw new ArgumentNullException(
                    "fillColor|borderColor", "fill and border colors cannot be null");
            }
            if (fillColor.Length != borderColor.Length) {
                throw new ArgumentException(
                    "fill and border colors must contain the same number of elements", "fillColor|borderColor");
            }
            if (fillColor.Length == 0) {
                throw new ArgumentException(
                    "fill and border colors must contain at least one element", "fillColor|borderColor");
            }

            this.Name        = name;
            this.ColorCount  = fillColor.Length;
            this.FillColor   = fillColor;
            this.BorderColor = borderColor;
        }

        #endregion

        #region <<< Properties >>>

        //----- static properties (for predefined palettes) -----
        /// <summary>Gets an array containing a copy of all predefined color palettes.</summary>
        public static ColorPalette[] AllPalettes { get; }

        /// <summary>Gets the predefined Light color palette.</summary>
        public static ColorPalette Light => Values["Light"];
        /// <summary>Gets the predefined Dark color palette.</summary>
        public static ColorPalette Dark => Values["Dark"];

        // all palettes
        private static Dictionary<string, ColorPalette> Values { get; }

        //----- instance properties -----
        /// <summary>Gets the name of the color palette.</summary>
        public string Name { get; private set; }
        /// <summary>Gets the number of colors defined in the color palette.</summary>
        public int ColorCount { get; private set; }

        private Color[] FillColor { get; set; }
        private Color[] BorderColor { get; set; }

        #endregion

        #region <<< Methods >>>

        //----- static methods (for initializing predefined palettes) -----
        /// <summary>
        /// Searches for and returns a color palette with the specified name.
        /// </summary>
        /// <param name="name">The name of the color palette to search for.</param>
        /// <returns>
        /// A color palette with the specified name, or a <see cref="Light"/> color palette, if the palette with the
        /// specified name is not found.
        /// </returns>
        public static ColorPalette GetPalette(string name)
        {
            return Values.TryGetValue(name, out ColorPalette value) ? value : Light;
        }

        private static ColorPalette GetLightPalette()
        {
            return new ColorPalette("Light",
                new Color[]{Color.FromArgb(255, 128, 128, 255),
                            Color.FromArgb(255, 128, 255, 128),
                            Color.FromArgb(255, 255, 128, 128),
                            Color.FromArgb(255, 255, 255, 128),
                            Color.FromArgb(255, 255, 128, 255),
                            Color.FromArgb(255, 128, 255, 255),
                            Color.FromArgb(255, 255, 192, 128),
                            Color.FromArgb(255, 192, 128, 255),
                            Color.FromArgb(255, 128, 255, 192),
                            Color.FromArgb(255, 128, 192, 255),
                            Color.FromArgb(255, 192, 255, 128),
                            Color.FromArgb(255, 255, 128, 192)},
                new Color[]{Color.FromArgb(255,  96,  96, 255),
                            Color.FromArgb(255,  96, 255,  96),
                            Color.FromArgb(255, 255,  96,  96),
                            Color.FromArgb(255, 255, 255,  96),
                            Color.FromArgb(255, 255,  96, 255),
                            Color.FromArgb(255,  96, 255, 255),
                            Color.FromArgb(255, 255, 160,  96),
                            Color.FromArgb(255, 160,  96, 255),
                            Color.FromArgb(255,  96, 255, 160),
                            Color.FromArgb(255,  96, 160, 255),
                            Color.FromArgb(255, 160, 255,  96),
                            Color.FromArgb(255, 255,  96, 160)});
        }
        private static ColorPalette GetDarkPalette()
        {
            Color c = Color.FromArgb(255, 64, 64, 64);
            return CreateLigthBasedColorPalette("Dark", c, c);
        }
        private static ColorPalette GetLavenderPalette()
        {
            return new ColorPalette("Lavender",
                new Color[]{Color.FromArgb(255, 168, 168, 248),
                            Color.FromArgb(255, 168, 208, 208),
                            Color.FromArgb(255, 188, 168, 208),
                            Color.FromArgb(255, 168, 208, 228),
                            Color.FromArgb(255, 196, 196, 252),
                            Color.FromArgb(255, 188, 232, 220),
                            Color.FromArgb(255, 188, 168, 248),
                            Color.FromArgb(255, 168, 208, 248)},
                new Color[]{Color.FromArgb(255, 136, 136, 248),
                            Color.FromArgb(255, 136, 192, 192),
                            Color.FromArgb(255, 164, 136, 192),
                            Color.FromArgb(255, 136, 192, 220),
                            Color.FromArgb(255, 172, 172, 252),
                            Color.FromArgb(255, 164, 232, 220),
                            Color.FromArgb(255, 164, 136, 248),
                            Color.FromArgb(255, 136, 192, 248)});
        }
        private static ColorPalette GetSoftBluePalette()
        {
            Color c = Color.FromArgb(255, 140, 140, 219);
            return CreateLigthBasedColorPalette("Soft Blue", c, c);
        }
        private static ColorPalette GetYellowHuesPalette()
        {
            Color c = ChartColors.AverageColor(Color.LightGreen, Color.Black);
            return CreateLigthBasedColorPalette("Yellow Hues", c, c);
        }
        private static ColorPalette GetFadedOrangePalette()
        {
            Color fc = Color.Orange;
            Color bc = Color.DarkOrange;
            return CreateLigthBasedColorPalette("Faded Orange", fc, bc);
        }

        // helper for light-based palettes
        private static ColorPalette CreateLigthBasedColorPalette(string name, Color fillColor, Color borderColor)
        {
            ColorPalette cp = GetLightPalette();
            cp.Name = name;
            for (int i = 0; i < cp.FillColor.Length; i++)
            {
                cp.FillColor  [i] = ChartColors.AverageColor(cp.FillColor  [i], fillColor);
                cp.BorderColor[i] = ChartColors.AverageColor(cp.BorderColor[i], borderColor);
            }
            return cp;
        }

        //----- instance methods -----
        /// <summary>
        /// Gets the fill color for the specified series index.
        /// </summary>
        /// <param name="index">The series index for which to obtain the fill color.</param>
        /// <returns>The fill color for the specified series index.</returns>
        public Color GetFillColor(int index)
        {
            return this.FillColor[index % this.FillColor.Length];
        }
        /// <summary>
        /// Gets the border color for the specified series index.
        /// </summary>
        /// <param name="index">The series index for which to obtain the border color.</param>
        /// <returns>The border color for the specified series index.</returns>
        public Color GetBorderColor(int index)
        {
            return this.BorderColor[index % this.BorderColor.Length];
        }

        #endregion
    }
}
