using System;
using System.ComponentModel.DataAnnotations;
using System.Diagnostics;

namespace Quik
{
    /// <summary>
    /// A 2 dimensional Vector.
    /// </summary>
    [DebuggerDisplay("({X}, {Y})")]
    public struct QVec2
    {
        public float X;
        public float Y;

        public float Magnitude => MathF.Sqrt(X * X + Y * Y);

        public QVec2(float x, float y)
        {
            X = x;
            Y = y;
        }

        public QVec2 Normalize() => this * (1.0f / Magnitude);

        public float Atan2() => MathF.Atan2(Y, X);
        public static QVec2 operator +(QVec2 a, QVec2 b)
        {
            return new QVec2()
            {
                X = a.X + b.X,
                Y = a.Y + b.Y
            };
        }

        public static QVec2 operator -(QVec2 a)
        {
            return new QVec2()
            {
                X = -a.X,
                Y = -a.Y
            };
        }

        public static QVec2 operator -(QVec2 a, QVec2 b)
        {
            return new QVec2()
            {
                X = a.X - b.X,
                Y = a.Y - b.Y
            };
        }

        public static QVec2 operator *(float a, QVec2 b)
        {
            return new QVec2()
            {
                X = a * b.X,
                Y = a * b.Y
            };
        }

        public static QVec2 operator *(QVec2 a, float b) => b * a;

        public static bool operator ==(QVec2 a, QVec2 b) => a.X == b.X && a.Y == b.Y;

        public static bool operator !=(QVec2 a, QVec2 b) => a.X != b.X || a.Y != b.Y;

        public override bool Equals(object obj)
        {
            if (obj is QVec2)
            {
                return (QVec2) obj == this;
            }
            else
            {
                return false;
            }
        }

        public override int GetHashCode()
        {
            return 63671 * X.GetHashCode() ^ 81083 * Y.GetHashCode();
        }

        public static float Dot(QVec2 a, QVec2 b)
        {
            return a.X * b.X + a.Y * b.Y;
        }
    }

    /// <summary>
    /// A RGBA color value.
    /// </summary>
    [DebuggerDisplay("({R}, {G}, {B}, {A})")]
    public struct QColor
    {
        /// <summary>
        /// Red channel.
        /// </summary>
        public byte R;
        /// <summary>
        /// Green channel.
        /// </summary>
        public byte G;
        /// <summary>
        /// Blue channel.
        /// </summary>
        public byte B;
        /// <summary>
        /// Alpha channel.
        /// </summary>
        public byte A;

        public QColor(byte r, byte g, byte b, byte a)
        {
            R = r;
            G = g;
            B = b;
            A = a;
        }
        
        public QColor(byte r, byte g, byte b) : this(r, g, b, 1) { }

        public QColor(uint hexCode)
        {
            R = (byte)((hexCode >> 24) & 0xFF);
            G = (byte)((hexCode >> 16) & 0xFF);
            B = (byte)((hexCode >> 8 ) & 0xFF);
            A = (byte)((hexCode >> 0 ) & 0xFF);
        }
        public QColor(int hexCode) : this((uint)hexCode) { }

        public static readonly QColor Black = new QColor(0, 0, 0, 255);
        public static readonly QColor Red = new QColor(255, 0, 0, 255);
        public static readonly QColor Green = new QColor(0, 255, 0, 255);
        public static readonly QColor Blue = new QColor(0, 0, 255, 255);
        public static readonly QColor Yellow = new QColor(255, 255, 0, 255);
        public static readonly QColor Cyan = new QColor(0, 255, 255, 255);
        public static readonly QColor Magenta = new QColor(255, 0, 255, 255);
        public static readonly QColor White = new QColor(255, 255, 255, 255);

        public static explicit operator QColorF(QColor a)
        {
            return new QColorF(a.R/255.0f, a.G/255.0f, a.B/255.0f, a.A/255.0f);
        }
    }

    public struct QColorF
    {
        /// <summary>
        /// Red channel.
        /// </summary>
        public float R;
        /// <summary>
        /// Green channel.
        /// </summary>
        public float G;
        /// <summary>
        /// Blue channel.
        /// </summary>
        public float B;
        /// <summary>
        /// Alpha channel.
        /// </summary>
        public float A;

        public QColorF(float r, float g, float b, float a)
        {
            R = r; G = g; B = b; A = a;
        }
        public QColorF(float r, float g, float b) : this(r, g, b, 1.0f) { }
        public QColorF(uint hexCode)
        {
            R = ((hexCode >> 24) & 0xFF)/255.0f;
            G = ((hexCode >> 16) & 0xFF)/255.0f;
            B = ((hexCode >> 8 ) & 0xFF)/255.0f;
            A = ((hexCode >> 0 ) & 0xFF)/255.0f;
        }
        public QColorF(int hexCode) : this((uint)hexCode) { }

        public static readonly QColorF Black = new QColorF(0, 0, 0, 1.0f);
        public static readonly QColorF Red = new QColorF(1.0f, 0, 0, 1.0f);
        public static readonly QColorF Green = new QColorF(0, 1, 0, 1);
        public static readonly QColorF Blue = new QColorF(0, 0, 1, 1);
        public static readonly QColorF Yellow = new QColorF(1, 1, 0, 1);
        public static readonly QColorF Cyan = new QColorF(0, 1, 1, 1);
        public static readonly QColorF Magenta = new QColorF(1, 0, 1, 1);
        public static readonly QColorF White = new QColorF(1, 1, 1, 1);

        public static explicit operator QColor(QColorF a)
        {
            return new QColor((byte)(a.R * 255), (byte)(a.G * 255), (byte)(a.B * 255), (byte)(a.A * 255));
        }
    }


    /// <summary>
    /// A bezier curve segment.
    /// </summary>
    [DebuggerDisplay("{Start} -- {ControlA} -- {ControlB} -- {End}")]
    public struct QBezier
    {
        /// <summary>
        /// Segment start point.
        /// </summary>
        public QVec2 Start;
        
        /// <summary>
        /// Start point control point.
        /// </summary>
        public QVec2 ControlA;
        
        /// <summary>
        /// End point control point.
        /// </summary>
        public QVec2 ControlB;
        
        /// <summary>
        /// Segment end point.
        /// </summary>
        public QVec2 End;

        /// <summary>
        /// An approximation of the arc length of the bezier curve, for calculating rasterization resolution.
        /// </summary>
        public float RasterizationArc => 
            0.5f * (End - Start).Magnitude + 
            0.5f * ((ControlA - Start).Magnitude + (ControlB - ControlA).Magnitude + (End - ControlB).Magnitude);

        public QBezier(QVec2 start, QVec2 controlA, QVec2 controlB, QVec2 end)
        {
            Start = start;
            ControlA = controlA;
            ControlB = controlB;
            End = end;
        }

        public QBezier(
            float startX,
            float startY,
            float controlAx,
            float controlAy,
            float controlBx,
            float controlBy,
            float endX,
            float endY)
        : this(
            new QVec2(startX, startY),
            new QVec2(controlAx, controlAy),
            new QVec2(controlBx, controlBy),
            new QVec2(endX, endY))
        {
        }

        /// <summary>
        /// Get a point in the curve segment.
        /// </summary>
        /// <param name="t">Control parameter (between 0 and 1)</param>
        /// <returns>The point on the curve.</returns>
        public QVec2 GetBezierPoint(float t)
        {
            float T = 1 - t;
            return
                T * T * T * Start +
                3 * T * T * t * ControlA +
                3 * T * t * t * ControlB +
                t * t * t * End;
        }

        /// <summary>
        /// Get the tangent on the curve.
        /// </summary>
        /// <param name="t">Control parameter (between 0 and 1)</param>
        /// <returns>The tangent curve.</returns>
        public QVec2 GetBezierTangent(float t)
        {
            float T = 1 - t;
            return
                (
                    3 * T * T * (ControlA - Start) +
                    6 * T * t * (ControlB - ControlA) +
                    3 * t * t * (End - ControlB)
                ).Normalize();
        }

        internal QVec2 GetBezierNormal(float t)
        {
            QVec2 tangent = GetBezierTangent(t);
            return new QVec2(-tangent.Y, tangent.X);
        }
    }

    /// <summary>
    /// A line segment.
    /// </summary>
    [DebuggerDisplay("{Start} -- {End}")]
    public struct QLine
    {
        /// <summary>
        /// Start point.
        /// </summary>
        public QVec2 Start;
        
        /// <summary>
        /// End point.
        /// </summary>
        public QVec2 End;

        public QLine(QVec2 start, QVec2 end)
        {
            Start = start;
            End = end;
        }

        public QLine(float startX, float startY, float endX, float endY)
        {
            Start.X = startX;
            Start.Y = startY;
            End.X = endX;
            End.Y = endY;
        }

        public QVec2 Normal()
        {
            QVec2 tangent = Tangent();
            return new QVec2(-tangent.Y, tangent.X);
        }
        public QVec2 Tangent()
        {
            return (End - Start).Normalize();
        }
    }

    /// <summary>
    /// A rectangle.
    /// </summary>
    [DebuggerDisplay("({Right}, {Top}, {Left}, {Bottom})")]
    public struct QRectangle
    {
        /// <summary>
        /// Rectangle maximum point.
        /// </summary>
        public QVec2 Max;
        
        /// <summary>
        /// Rectangle minimum point.
        /// </summary>
        public QVec2 Min;

        public float Left
        {
            get => Min.X;
            set => Min.X = value;
        }

        public float Right
        {
            get => Max.X;
            set => Max.X = value;
        }

        public float Top
        {
            get => Max.Y;
            set => Max.Y = value;
        }

        public float Bottom
        {
            get => Min.Y;
            set => Min.Y = value;
        }

        public QVec2 Size
        {
            get => Max - Min;
            set => Max = Min + value;
        }

        public QRectangle(QVec2 max, QVec2 min)
        {
            Max = max;
            Min = min;
        }

        public QRectangle(float r, float t, float l, float b)
        {
            Max = new QVec2() {X = r, Y = t};
            Min = new QVec2() {X = l, Y = b};
        }

        public bool Contains(QVec2 point)
        {
            return
                point.X > Left   && point.X < Right &&
                point.Y > Bottom && point.Y < Top;
        }

        internal void Translate(in QVec2 offset)
        {
            Min += offset;
            Max += offset;
        }

        public static QRectangle Intersect(in QRectangle a, in QRectangle b) =>
            new QRectangle(
                Math.Min(a.Right, b.Right),
                Math.Min(a.Top, b.Top),
                Math.Max(a.Left, b.Left),
                Math.Max(a.Bottom, b.Bottom)
            );
    }

    /// <summary>
    /// An ellipse.
    /// </summary>
    /// <remarks>It is undefined to have an ellipse with non-orthogonal axes.</remarks>
    [DebuggerDisplay("{Center} ellipse {AxisA}; {AxisB}")]
    public struct QEllipse
    {
        /// <summary>
        /// Ellipse center point.
        /// </summary>
        public QVec2 Center;
        
        /// <summary>
        /// First ellipse axis.
        /// </summary>
        public QVec2 AxisA;
        
        /// <summary>
        /// Second ellipse axis.
        /// </summary>
        public QVec2 AxisB;
    }

    /// <summary>
    /// A triangle.
    /// </summary>
    [DebuggerDisplay("{A} -- {B} -- {C}")]
    public struct QTriangle
    {
        /// <summary>
        /// First vertex.
        /// </summary>
        public QVec2 A;
        
        /// <summary>
        /// Second vertex.
        /// </summary>
        public QVec2 B;
        
        /// <summary>
        /// Third vertex.
        /// </summary>
        public QVec2 C;
    }

    [DebuggerDisplay("[{M11} {M12} {M13} {M14}; {M21} {M22} {M23} {M24}; {M31} {M32} {M33} {M34}; {M41} {M42} {M43} {M44}]")]
    public struct QMat4
    {
        public float M11, M21, M31, M41;
        public float M12, M22, M32, M42;
        public float M13, M23, M33, M43;
        public float M14, M24, M34, M44;

        public static QMat4 Identity { get; } = new QMat4()
        {
            M11 = 1.0f,
            M22 = 1.0f,
            M33 = 1.0f,
            M44 = 1.0f
        };

        public static void Translation(out QMat4 mat, float x, float y, float z)
        {
            mat = Identity;
            mat.M41 = x;
            mat.M42 = y;
            mat.M43 = z;
        }

        public static void Scale(out QMat4 mat, float x, float y, float z)
        {
            mat = default;
            mat.M11 = x;
            mat.M22 = y;
            mat.M33 = z;
            mat.M44 = 1.0f;
        }

        public static void Orthographic(out QMat4 mat, QRectangle bounds, float near = 1, float far = -1)
        {
            float a, b, c;
            mat = default;

            a = 1.0f/(bounds.Right - bounds.Left);
            b = 1.0f/(bounds.Top - bounds.Bottom);
            c = 1.0f/(near - far);

            mat.M11 = 2 * a;
            mat.M22 = 2 * b;
            mat.M33 = 2 * c;

            mat.M41 = -a * (bounds.Left + bounds.Right);
            mat.M42 = -b * (bounds.Top + bounds.Bottom);
            mat.M43 = -c * (near + far);
            mat.M44 = 1.0f;
        }
    }
}