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

namespace Dashboard
{
    /// <summary>
    /// Enumeration of the kinds of gradients available.
    /// </summary>
    public enum GradientType
    {
        /// <summary>
        /// A gradient which transitions over a set axis.
        /// </summary>
        Axial,
        /// <summary>
        /// A gradient which transitions along elliptical curves.
        /// </summary>
        Radial,
    }

    /// <summary>
    /// A single gradient stop.
    /// </summary>
    /// <param name="Position">The position of the gradient stop. Must be [0,1].</param>
    /// <param name="Color">The color value for the stop.</param>
    public record struct GradientStop(float Position, Color Color);

    /// <summary>
    /// Represents a linear gradient.
    /// </summary>
    public struct Gradient : ICollection<GradientStop>, ICloneable, IEquatable<Gradient>
    {
        private readonly List<GradientStop> _stops = new List<GradientStop>();

        /// <summary>
        /// Gradient type.
        /// </summary>
        public GradientType Type { get; set; } = GradientType.Axial;

        /// <summary>
        /// First gradient control point.
        /// </summary>
        public Vector2 C0 { get; set; } = Vector2.Zero;

        /// <summary>
        /// Second gradient control point.
        /// </summary>
        public Vector2 C1 { get; set; } = Vector2.One;

        /// <summary>
        /// Number of stops in a gradient.
        /// </summary>
        public int Count => _stops.Count;
        public bool IsReadOnly => false;

        /// <summary>
        /// Get a gradient control point.
        /// </summary>
        /// <param name="index">The index to get the control point for.</param>
        public GradientStop this[int index]
        {
            get => _stops[index];
            set
            {
                RemoveAt(index);
                Add(value);
            }
        }

        public Gradient()
        {
        }

        public Gradient(Color a, Color b)
        {
            Add(new GradientStop(0, a));
            Add(new GradientStop(1, b));
        }

        public Gradient(IEnumerable<GradientStop> stops)
        {
            _stops.AddRange(stops);

            if (_stops.Any(x => x.Position < 0 || x.Position > 1))
                throw new Exception("Gradient stop positions must be in the range [0, 1].");

            _stops.Sort((a, b) => a.Position.CompareTo(b.Position));
        }

        public Color GetColor(float position)
        {
            if (Count == 0)
                return Color.Black;
            else if (Count == 1)
                return _stops[0].Color;

            int pivot = _stops.FindIndex(x => x.Position < position);

            GradientStop left, right;
            if (pivot == -1)
            {
                left = right = _stops[^1];
            }
            else if (pivot == 0)
            {
                left = right = _stops[0];
            }
            else
            {
                left = _stops[pivot-1];
                right = _stops[pivot];
            }

            float weight = (position - left.Position) / (right.Position - left.Position);

            Vector4 lcolor = new Vector4(left.Color.R, left.Color.G, left.Color.B, left.Color.A) * (1-weight);
            Vector4 rcolor = new Vector4(right.Color.R, right.Color.G, right.Color.B, right.Color.A) * weight;
            Vector4 color = lcolor + rcolor;

            return Color.FromArgb((byte)color.W, (byte)color.X, (byte)color.Y, (byte)color.Z);
        }

        public Gradient Clone()
        {
            Gradient gradient = new Gradient()
            {
                Type = Type,
                C0 = C0,
                C1 = C1,
            };

            foreach (GradientStop stop in _stops)
            {
                gradient.Add(stop);
            }

            return gradient;
        }

        object ICloneable.Clone()
        {
            return Clone();
        }

        public IEnumerator<GradientStop> GetEnumerator()
        {
            return _stops.GetEnumerator();
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            return ((IEnumerable)_stops).GetEnumerator();
        }

        public void Add(GradientStop item)
        {
            if (item.Position < 0 || item.Position > 1)
                throw new Exception("Gradient stop positions must be in the range [0, 1].");

            int index = _stops.FindIndex(x => x.Position > item.Position);
            if (index == -1)
                index = _stops.Count;

            _stops.Insert(index, item);
        }

        public void Clear()
        {
            _stops.Clear();
        }

        public bool Contains(GradientStop item)
        {
            return _stops.Contains(item);
        }

        public void CopyTo(GradientStop[] array, int arrayIndex)
        {
            _stops.CopyTo(array, arrayIndex);
        }

        public bool Remove(GradientStop item)
        {
            return _stops.Remove(item);
        }

        public void RemoveAt(int index)
        {
            _stops.RemoveAt(index);
        }

        public override int GetHashCode()
        {
            HashCode code = new HashCode();
            code.Add(Count);
            foreach (GradientStop item in this)
                code.Add(item.GetHashCode());
            return code.ToHashCode();
        }

        public bool Equals(Gradient other)
        {
            return
                Type == other.Type &&
                C0 == other.C0 &&
                C1 == other.C1 &&
                _stops.Equals(other._stops);
        }

        public override bool Equals(object? obj)
        {
            return obj is Gradient other && Equals(other);
        }

        public static bool operator ==(Gradient left, Gradient right)
        {
            return left.Equals(right);
        }

        public static bool operator !=(Gradient left, Gradient right)
        {
            return !left.Equals(right);
        }
    }
}