﻿using System;

namespace Oni
{
    internal struct Vector4 : IEquatable<Vector4>
    {
        public float X;
        public float Y;
        public float Z;
        public float W;

        public Vector4(float all)
        {
            X = all;
            Y = all;
            Z = all;
            W = all;
        }

        public Vector4(Vector3 v, float w)
        {
            X = v.X;
            Y = v.Y;
            Z = v.Z;
            W = w;
        }

        public Vector4(float x, float y, float z, float w)
        {
            X = x;
            Y = y;
            Z = z;
            W = w;
        }

        public Vector3 XYZ
        {
            get
            {
                return new Vector3(X, Y, Z);
            }
            set
            {
                X = value.X;
                Y = value.Y;
                Z = value.Z;
            }
        }

        public static Vector4 operator +(Vector4 v1, Vector4 v2)
        {
            v1.X += v2.X;
            v1.Y += v2.Y;
            v1.Z += v2.Z;
            v1.W += v2.W;

            return v1;
        }

        public static Vector4 operator -(Vector4 v1, Vector4 v2)
        {
            v1.X -= v2.X;
            v1.Y -= v2.Y;
            v1.Z -= v2.Z;
            v1.W -= v2.W;

            return v1;
        }

        public static Vector4 operator *(Vector4 v, float s)
        {
            v.X *= s;
            v.Y *= s;
            v.Z *= s;
            v.W *= s;

            return v;
        }

        public static Vector4 operator *(float s, Vector4 v) => v * s;

        public static Vector4 operator /(Vector4 v, float s) => v * (1.0f / s);

        public static float Dot(Vector4 v1, Vector4 v2) => v1.X * v2.X + v1.Y * v2.Y + v1.Z * v2.Z + v1.W * v2.W;

        public static Vector4 Min(Vector4 v1, Vector4 v2)
        {
            v1.X = (v1.X < v2.X) ? v1.X : v2.X;
            v1.Y = (v1.Y < v2.Y) ? v1.Y : v2.Y;
            v1.Z = (v1.Z < v2.Z) ? v1.Z : v2.Z;
            v1.W = (v1.W < v2.W) ? v1.W : v2.W;

            return v1;
        }

        public static Vector4 Max(Vector4 v1, Vector4 v2)
        {
            v1.X = (v1.X > v2.X) ? v1.X : v2.X;
            v1.Y = (v1.Y > v2.Y) ? v1.Y : v2.Y;
            v1.Z = (v1.Z > v2.Z) ? v1.Z : v2.Z;
            v1.W = (v1.W > v2.W) ? v1.W : v2.W;

            return v1;
        }

        public static Vector4 Normalize(Vector4 v) => v * (1.0f / v.Length());

        public float LengthSquared() => X * X + Y * Y + Z * Z + W * W;
        public float Length() => FMath.Sqrt(LengthSquared());

        public static bool EqualsEps(Vector4 v1, Vector4 v2)
        {
            Vector4 d = v2 - v1;

            float dx = Math.Abs(d.X);
            float dy = Math.Abs(d.Y);
            float dz = Math.Abs(d.Z);
            float dw = Math.Abs(d.W);

            return (dx < 0.0001f && dy < 0.0001f && dz < 0.0001f && dw < 0.0001f);
        }

        public static bool operator ==(Vector4 v1, Vector4 v2) => v1.X == v2.X && v1.Y == v2.Y && v1.Z == v2.Z && v1.W == v2.W;
        public static bool operator !=(Vector4 v1, Vector4 v2) => v1.X != v2.X || v1.Y != v2.Y || v1.Z != v2.Z || v1.W != v2.W;

        public bool Equals(Vector4 other) => X == other.X && Y == other.Y && Z == other.Z && W == other.W;
        public override bool Equals(object obj) => obj is Vector4 && Equals((Vector4)obj);
        public override int GetHashCode() => X.GetHashCode() ^ Y.GetHashCode() ^ Z.GetHashCode() ^ W.GetHashCode();

        public override string ToString() => $"{{{X} {Y} {Z} {W}}}";

        private static Vector4 zero = new Vector4();
        private static Vector4 one = new Vector4(1.0f);
        private static Vector4 unitX = new Vector4(1.0f, 0.0f, 0.0f, 0.0f);
        private static Vector4 unitY = new Vector4(0.0f, 1.0f, 0.0f, 0.0f);
        private static Vector4 unitZ = new Vector4(0.0f, 0.0f, 1.0f, 0.0f);
        private static Vector4 unitW = new Vector4(0.0f, 0.0f, 0.0f, 1.0f);

        public static Vector4 Zero => zero;
        public static Vector4 One => one;
        public static Vector4 UnitX => unitX;
        public static Vector4 UnitY => unitY;
        public static Vector4 UnitZ => unitZ;
        public static Vector4 UnitW => unitW;
    }
}
