using System;
using System.Runtime.InteropServices;
using MTV3D65;

namespace OctreeRendering
{
    [StructLayout(LayoutKind.Sequential)]
    struct Vector3f : IEquatable<Vector3f>  
    {
        public float X, Y, Z;

        public Vector3f(float c)
        {
            X = Y = Z = c;
        }
        public Vector3f(Vector2f xy, float z)
        {
            X = xy.X;
            Y = xy.Y;
            Z = z;
        }
        public Vector3f(float x, float y, float z)
        {
            X = x;
            Y = y;
            Z = z;
        }

        #region Constants

        public static readonly Vector3f One = new Vector3f(1, 1, 1);
        public static readonly Vector3f Zero = new Vector3f(0, 0, 0);

        public static readonly Vector3f UnitX = new Vector3f(1, 0, 0);
        public static readonly Vector3f UnitY = new Vector3f(0, 1, 0);
        public static readonly Vector3f UnitZ = new Vector3f(0, 0, 1);

        public static readonly Vector3f Up = new Vector3f(0, 1, 0);
        public static readonly Vector3f Down = new Vector3f(0, -1, 0);
        public static readonly Vector3f Left = new Vector3f(-1, 0, 0);
        public static readonly Vector3f Right = new Vector3f(1, 0, 0);
        public static readonly Vector3f Forward = new Vector3f(0, 0, 1);
        public static readonly Vector3f Back = new Vector3f(0, 0, -1);

        #endregion

        #region Operators

        public static Vector3f operator+(Vector3f a, Vector3f b)
        {
            return new Vector3f(a.X + b.X, a.Y + b.Y, a.Z + b.Z);
        }
        public static Vector3f operator -(Vector3f a, Vector3f b)
        {
            return new Vector3f(a.X - b.X, a.Y - b.Y, a.Z - b.Z);
        }
        public static Vector3f operator -(Vector3f vector)
        {
            return new Vector3f(-vector.X, -vector.Y, -vector.Z);
        }
        public static Vector3f operator /(Vector3f a, Vector3f b)
        {
            return new Vector3f(a.X / b.X, a.Y / b.Y, a.Z / b.Z);
        }
        public static Vector3f operator /(Vector3f a, float b)
        {
            return new Vector3f(a.X / b, a.Y / b, a.Z / b);
        }
        public static Vector3f operator *(Vector3f a, Vector3f b)
        {
            return new Vector3f(a.X * b.X, a.Y * b.Y, a.Z * b.Z);
        }
        public static Vector3f operator *(Vector3f a, Vector3i b)
        {
            return new Vector3f(a.X * b.X, a.Y * b.Y, a.Z * b.Z);
        }
        public static Vector3f operator *(Vector3i a, Vector3f b)
        {
            return new Vector3f(a.X * b.X, a.Y * b.Y, a.Z * b.Z);
        }
        public static Vector3f operator *(Vector3f a, float b)
        {
            return new Vector3f(a.X * b, a.Y * b, a.Z * b);
        }
        public static Vector3f operator *(float a, Vector3f b)
        {
            return new Vector3f(a * b.X, a * b.Y, a * b.Z);
        }
        public static implicit operator TV_3DVECTOR(Vector3f vector)
        {
            return new TV_3DVECTOR(vector.X, vector.Y, vector.Z);
        }
        public static implicit operator Vector3f(TV_3DVECTOR vector)
        {
            return new Vector3f(vector.x, vector.y, vector.z);
        }
        public static bool operator ==(Vector3f a, Vector3f b) 
        {
            return a.X == b.X && a.Y == b.Y && a.Z == b.Z;
        }
        public static bool operator !=(Vector3f a, Vector3f b)
        {
            return a.X != b.X || a.Y != b.Y || a.Z != b.Z;
        }

        #endregion

        #region Object Overrides

        public override int GetHashCode()
        {
            return X.GetHashCode() + Y.GetHashCode() << 10 + Z.GetHashCode() << 20;
        }

        public override bool Equals(object obj)
        {
            return obj != null && obj is Vector3f && this == (Vector3f)obj;
        }
        public bool Equals(Vector3f other)
        {
            return this == other;
        }

        public override string ToString()
        {
            return string.Format("{{X:{0} Y:{1} Z:{2}}}", X, Y, Z);
        }

        #endregion

        #region Static Methods

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

        public static Vector3f Normalize(Vector3f vector)
        {
            float length = vector.Length();
            return length == 0 ? Zero : vector / length;
        }

        public static Vector3f Clamp(Vector3f vector, Vector3f minimum, Vector3f maximum)
        {
            return new Vector3f(
                MathHelper.Clamp(vector.X, minimum.X, maximum.X),
                MathHelper.Clamp(vector.Y, minimum.Y, maximum.Y),
                MathHelper.Clamp(vector.Z, minimum.Z, maximum.Z));
        }

        public static Vector3f Lerp(Vector3f from, Vector3f to, float step)
        {
            return new Vector3f(MathHelper.Lerp(from.X, to.X, step),
                MathHelper.Lerp(from.Y, to.Y, step),
                MathHelper.Lerp(from.Z, to.Z, step));
        }

        public static Vector3f Abs(Vector3f vector)
        {
            return new Vector3f(Math.Abs(vector.X), Math.Abs(vector.Y), Math.Abs(vector.Z));
        }

        public static Vector3f Saturate(Vector3f vector)
        {
            return Clamp(vector, Zero, One);
        }

        public static Vector3i Sign(Vector3f vector)
        {
            return new Vector3i(Math.Sign(vector.X), Math.Sign(vector.Y), Math.Sign(vector.Z));
        }

        public static Vector3f Min(Vector3f a, Vector3f b)
        {
            return new Vector3f(Math.Min(a.X, b.X), Math.Min(a.Y, b.Y), Math.Min(a.Z, b.Z));
        }

        public static Vector3f Max(Vector3f a, Vector3f b)
        {
            return new Vector3f(Math.Max(a.X, b.X), Math.Max(a.Y, b.Y), Math.Max(a.Z, b.Z));
        }

        #endregion

        #region Instance Methods

        public float Dot(Vector3f other)
        {
            return X * other.X + Y * other.Y + Z * other.Z;
        }

        public void Normalize()
        {
            float length = Length();
            if (length == 0)
                X = Y = Z = 0;
            else
            {
                X /= length;
                Y /= length;
                Z /= length;
            }
        }

        public float Length()
        {
            return (float)Math.Sqrt(X * X + Y * Y + Z * Z);
        }

        #endregion

        #region Swizzlers

        public Vector3f XZY
        {
            get { return new Vector3f(X, Z, Y); }
        }
        public Vector3f ZXY
        {
            get { return new Vector3f(Z, X, Y); }
        }
        public Vector3f ZYX
        {
            get { return new Vector3f(Z, Y, X); }
        }
        public Vector3f XXX
        {
            get { return new Vector3f(X); }
        }
        public Vector3f YYY
        {
            get { return new Vector3f(Y); }
        }
        public Vector3f ZZZ
        {
            get { return new Vector3f(Z); }
        }

        public Vector2f XY
        {
            get { return new Vector2f(X, Y); }
        }
        public Vector2f ZY
        {
            get { return new Vector2f(Z, Y); }
        }
        public Vector2f XZ
        {
            get { return new Vector2f(X, Z); }
        }
        public Vector2f XX
        {
            get { return new Vector2f(X); }
        }
        public Vector2f YY
        {
            get { return new Vector2f(Y); }
        }
        public Vector2f ZZ
        {
            get { return new Vector2f(Z); }
        }
        public Vector2f YX
        {
            get { return new Vector2f(Y, X); }
        }
        public Vector2f YZ
        {
            get { return new Vector2f(Y, Z); }
        }
        
        #endregion
    }
}
