using System;
using System.Numerics;

namespace UniGLTF
{
    public static class NumericsExtensions
    {
        const float EPSILON = 1e-5f;

        public static bool NearlyEqual(this Vector3 lhs, Vector3 rhs)
        {
            if (Math.Abs(lhs.X - rhs.X) > EPSILON) return false;
            if (Math.Abs(lhs.Y - rhs.Y) > EPSILON) return false;
            if (Math.Abs(lhs.Z - rhs.Z) > EPSILON) return false;
            return true;
        }

        public static bool NearlyEqual(this Quaternion lhs, Quaternion rhs)
        {
            if (Math.Abs(lhs.X - rhs.X) > EPSILON) return false;
            if (Math.Abs(lhs.Y - rhs.Y) > EPSILON) return false;
            if (Math.Abs(lhs.Z - rhs.Z) > EPSILON) return false;
            if (Math.Abs(lhs.W - rhs.W) > EPSILON) return false;
            return true;
        }

        public const float TO_RAD = (float)(Math.PI / 180.0);


        public static Vector3 ReverseX(this Vector3 src)
        {
            return new Vector3(-src.X, src.Y, src.Z);
        }

        public static Vector3 ReverseZ(this Vector3 src)
        {
            return new Vector3(src.X, src.Y, -src.Z);
        }

        public static (Vector3, float) GetAxisAngle(this Quaternion q)
        {
            var qw = q.W;
            if (qw == 1)
            {
                return (Vector3.UnitX, 0);
            }
            var angle = 2 * Math.Acos(qw);
            var x = q.X / Math.Sqrt(1 - qw * qw);
            var y = q.Y / Math.Sqrt(1 - qw * qw);
            var z = q.Z / Math.Sqrt(1 - qw * qw);
            return (new Vector3((float)x, (float)y, (float)z), (float)angle);
        }

        public static Quaternion ReverseX(this Quaternion src)
        {
            var (axis, angle) = src.GetAxisAngle();
            return Quaternion.CreateFromAxisAngle(axis.ReverseX(), -angle);
        }

        public static Quaternion ReverseZ(this Quaternion src)
        {
            var (axis, angle) = src.GetAxisAngle();
            return Quaternion.CreateFromAxisAngle(axis.ReverseZ(), -angle);
        }

        public static Vector3 ExtractPosition(this Matrix4x4 matrix)
        {
            Vector3 position;
            position.X = matrix.M41;
            position.Y = matrix.M42;
            position.Z = matrix.M43;
            return position;
        }

        public static Quaternion ExtractRotation(this Matrix4x4 matrix)
        {
            return Quaternion.CreateFromRotationMatrix(matrix);
        }

        public static Vector3 ExtractScale(this Matrix4x4 matrix)
        {
            Vector3 scale;
            scale.X = new Vector4(matrix.M11, matrix.M12, matrix.M13, matrix.M14).Length();
            scale.Y = new Vector4(matrix.M21, matrix.M22, matrix.M23, matrix.M24).Length();
            scale.Z = new Vector4(matrix.M31, matrix.M32, matrix.M33, matrix.M34).Length();
            return scale;
        }

        public static Matrix4x4 FromTRS(Vector3 t, Quaternion r, Vector3 s)
        {
            var tt = Matrix4x4.CreateTranslation(t);
            var rr = Matrix4x4.CreateFromQuaternion(r);
            var ss = Matrix4x4.CreateScale(s);
            // return tt * rr * ss;
            return ss * rr * tt;
        }

        public static bool IsOnlyTranslation(this Matrix4x4 m)
        {
            if (m.M11 != 1.0f) return false;
            if (m.M22 != 1.0f) return false;
            if (m.M33 != 1.0f) return false;
            if (m.M12 != 0) return false;
            if (m.M13 != 0) return false;
            if (m.M23 != 0) return false;
            if (m.M21 != 0) return false;
            if (m.M31 != 0) return false;
            if (m.M32 != 0) return false;
            return true;
        }
    }
}
