using System;
using System.Numerics;
namespace Engine.Core;
// TODO Comments
/// 
/// Represents a 4D left handed space matrix.
/// 
/// 
/// Initializes a new instance of the  struct with the specified values.
/// 
[System.Diagnostics.DebuggerDisplay("{ToString(),nq}")]
public readonly struct Matrix4x4(
    float m11, float m12, float m13, float m14,
    float m21, float m22, float m23, float m24,
    float m31, float m32, float m33, float m34,
    float m41, float m42, float m43, float m44
) : IEquatable
{
    public readonly float M11 = m11, M12 = m12, M13 = m13, M14 = m14;
    public readonly float M21 = m21, M22 = m22, M23 = m23, M24 = m24;
    public readonly float M31 = m31, M32 = m32, M33 = m33, M34 = m34;
    public readonly float M41 = m41, M42 = m42, M43 = m43, M44 = m44;
    /// 
    /// Extracts the position (translation) from the .
    /// 
    public readonly Vector3D Position => new(M41, M42, M43);
    /// 
    /// Extracts the scale from the .
    /// 
    public readonly Vector3D Scale
    {
        get
        {
            float scaleX = new Vector3D(M11, M12, M13).Length();
            float scaleY = new Vector3D(M21, M22, M23).Length();
            float scaleZ = new Vector3D(M31, M32, M33).Length();
            if (Determinant(this) < 0)
                scaleX *= -1;
            return new(scaleX, scaleY, scaleZ);
        }
    }
    /// 
    /// Extracts the rotation from the .
    /// 
    public readonly Quaternion Rotation => Quaternion.FromRotationMatrix4x4(this).Normalized;
    /// 
    /// Represents the identity .
    /// 
    public static Matrix4x4 Identity => new(
        1f, 0f, 0f, 0f,
        0f, 1f, 0f, 0f,
        0f, 0f, 1f, 0f,
        0f, 0f, 0f, 1f
    );
    public static Matrix4x4 operator *(Matrix4x4 a, Matrix4x4 b) => new(
        a.M11 * b.M11 + a.M12 * b.M21 + a.M13 * b.M31 + a.M14 * b.M41,
        a.M11 * b.M12 + a.M12 * b.M22 + a.M13 * b.M32 + a.M14 * b.M42,
        a.M11 * b.M13 + a.M12 * b.M23 + a.M13 * b.M33 + a.M14 * b.M43,
        a.M11 * b.M14 + a.M12 * b.M24 + a.M13 * b.M34 + a.M14 * b.M44,
        a.M21 * b.M11 + a.M22 * b.M21 + a.M23 * b.M31 + a.M24 * b.M41,
        a.M21 * b.M12 + a.M22 * b.M22 + a.M23 * b.M32 + a.M24 * b.M42,
        a.M21 * b.M13 + a.M22 * b.M23 + a.M23 * b.M33 + a.M24 * b.M43,
        a.M21 * b.M14 + a.M22 * b.M24 + a.M23 * b.M34 + a.M24 * b.M44,
        a.M31 * b.M11 + a.M32 * b.M21 + a.M33 * b.M31 + a.M34 * b.M41,
        a.M31 * b.M12 + a.M32 * b.M22 + a.M33 * b.M32 + a.M34 * b.M42,
        a.M31 * b.M13 + a.M32 * b.M23 + a.M33 * b.M33 + a.M34 * b.M43,
        a.M31 * b.M14 + a.M32 * b.M24 + a.M33 * b.M34 + a.M34 * b.M44,
        a.M41 * b.M11 + a.M42 * b.M21 + a.M43 * b.M31 + a.M44 * b.M41,
        a.M41 * b.M12 + a.M42 * b.M22 + a.M43 * b.M32 + a.M44 * b.M42,
        a.M41 * b.M13 + a.M42 * b.M23 + a.M43 * b.M33 + a.M44 * b.M43,
        a.M41 * b.M14 + a.M42 * b.M24 + a.M43 * b.M34 + a.M44 * b.M44
    );
    public static bool operator ==(Matrix4x4 left, Matrix4x4 right) =>
        left.M11 == right.M11 && left.M12 == right.M12 && left.M13 == right.M13 && left.M14 == right.M14 &&
        left.M21 == right.M21 && left.M22 == right.M22 && left.M23 == right.M23 && left.M24 == right.M24 &&
        left.M31 == right.M31 && left.M32 == right.M32 && left.M33 == right.M33 && left.M34 == right.M34 &&
        left.M41 == right.M41 && left.M42 == right.M42 && left.M43 == right.M43 && left.M44 == right.M44;
    public static bool operator !=(Matrix4x4 left, Matrix4x4 right) =>
        left.M11 != right.M11 || left.M12 != right.M12 || left.M13 != right.M13 || left.M14 != right.M14 ||
        left.M21 != right.M21 || left.M22 != right.M22 || left.M23 != right.M23 || left.M24 != right.M24 ||
        left.M31 != right.M31 || left.M32 != right.M32 || left.M33 != right.M33 || left.M34 != right.M34 ||
        left.M41 != right.M41 || left.M42 != right.M42 || left.M43 != right.M43 || left.M44 != right.M44;
    public static implicit operator System.Numerics.Matrix4x4(Matrix4x4 m) => new(
        m.M11, m.M12, m.M13, m.M14,
        m.M21, m.M22, m.M23, m.M24,
        m.M31, m.M32, m.M33, m.M34,
        m.M41, m.M42, m.M43, m.M44
    );
    public static implicit operator Matrix4x4(System.Numerics.Matrix4x4 m) => new(
        m.M11, m.M12, m.M13, m.M14,
        m.M21, m.M22, m.M23, m.M24,
        m.M31, m.M32, m.M33, m.M34,
        m.M41, m.M42, m.M43, m.M44
    );
    /// 
    /// Calculates the determinant of the .
    /// 
    /// The .
    /// The determinant of the .
    public static float Determinant(Matrix4x4 m) => // https://www.euclideanspace.com/maths/algebra/matrix/functions/determinant/fourD/index.htm
         m.M14 * m.M23 * m.M32 * m.M41 - m.M13 * m.M24 * m.M32 * m.M41 -
         m.M14 * m.M22 * m.M33 * m.M41 + m.M12 * m.M24 * m.M33 * m.M41 +
         m.M13 * m.M22 * m.M34 * m.M41 - m.M12 * m.M23 * m.M34 * m.M41 -
         m.M14 * m.M23 * m.M31 * m.M42 + m.M13 * m.M24 * m.M31 * m.M42 +
         m.M14 * m.M21 * m.M33 * m.M42 - m.M11 * m.M24 * m.M33 * m.M42 -
         m.M13 * m.M21 * m.M34 * m.M42 + m.M11 * m.M23 * m.M34 * m.M42 +
         m.M14 * m.M22 * m.M31 * m.M43 - m.M12 * m.M24 * m.M31 * m.M43 -
         m.M14 * m.M21 * m.M32 * m.M43 + m.M11 * m.M24 * m.M32 * m.M43 +
         m.M12 * m.M21 * m.M34 * m.M43 - m.M11 * m.M22 * m.M34 * m.M43 -
         m.M13 * m.M22 * m.M31 * m.M44 + m.M12 * m.M23 * m.M31 * m.M44 +
         m.M13 * m.M21 * m.M32 * m.M44 - m.M11 * m.M23 * m.M32 * m.M44 -
         m.M12 * m.M21 * m.M33 * m.M44 + m.M11 * m.M22 * m.M33 * m.M44;
    public static Matrix4x4 CreateTranslation(Vector3D position) => new(
        1f, 0f, 0f, 0f,
        0f, 1f, 0f, 0f,
        0f, 0f, 1f, 0f,
        position.X, position.Y, position.Z, 1
    );
    public static Matrix4x4 CreateScale(Vector3D scale) => new(
        scale.X, 0f, 0f, 0f,
        0f, scale.Y, 0f, 0f,
        0f, 0f, scale.Z, 0f,
        0f, 0f, 0f, 1f
    );
    public static Matrix4x4 CreateRotationX(float radians)
    {
        float c = Math.Cos(radians);
        float s = Math.Sin(radians);
        return new Matrix4x4(
            1f, 0f, 0f, 0f,
            0f, c, s, 0f,
            0f, -s, c, 0f,
            0f, 0f, 0f, 1f
        );
    }
    public static Matrix4x4 CreateRotationY(float radians)
    {
        float c = Math.Cos(radians);
        float s = Math.Sin(radians);
        return new Matrix4x4(
            c, 0f, -s, 0f,
            0f, 1f, 0f, 0f,
            s, 0f, c, 0f,
            0f, 0f, 0f, 1f
        );
    }
    public static Matrix4x4 CreateRotationZ(float radians)
    {
        float c = Math.Cos(radians);
        float s = Math.Sin(radians);
        return new Matrix4x4(
            c, s, 0f, 0f,
            -s, c, 0f, 0f,
            0f, 0f, 1f, 0f,
            0f, 0f, 0f, 1f
        );
    }
    // TODO Find a better calculation for this 
    public static Matrix4x4 CreateRotation(Quaternion quaternion)
    {
        Vector3D angles = quaternion.ToAngles();
        return Identity * CreateRotationX(angles.X) * CreateRotationY(angles.Y) * CreateRotationZ(angles.Z);
    }
    public static Matrix4x4 CreateLookMatrix(Vector3D forward, Vector3D up)
    {
        Vector3D z = forward.Normalized;
        Vector3D x = up.Cross(z).Normalized;
        Vector3D y = z.Cross(x);
        return new Matrix4x4(
            x.X, y.X, z.X, 0f,
            x.Y, y.Y, z.Y, 0f,
            x.Z, y.Z, z.Z, 0f,
            0f, 0f, 0f, 1f
        );
    }
    public static Matrix4x4 CreateLookMatrix(Vector3D position, Vector3D target, Vector3D up)
    {
        Vector3D z = position.FromTo(target).Normalized;
        Vector3D x = up.Cross(z).Normalized;
        Vector3D y = z.Cross(x);
        return new Matrix4x4(
            x.X, y.X, z.X, 0f,
            x.Y, y.Y, z.Y, 0f,
            x.Z, y.Z, z.Z, 0f,
            -x.Dot(position), -y.Dot(position), -z.Dot(position), 1f
        );
    }
    public static Matrix4x4 CreatePerspectiveFieldOfView(float fieldOfViewInRadians, float aspectRatio, float nearPlane, float farPlane)
    {
        float yScale = 1f / Math.Tan(fieldOfViewInRadians / 2f);
        float xScale = yScale / aspectRatio;
        return new Matrix4x4(
            xScale, 0f, 0f, 0f,
            0f, yScale, 0f, 0f,
            0f, 0f, farPlane / (farPlane - nearPlane), 1f,
            0f, 0f, -nearPlane * farPlane / (farPlane - nearPlane), 0f
        );
    }
    public static Matrix4x4 ToRightHanded(Matrix4x4 m) => new(
        m.M11, m.M12, m.M13, m.M14,
        m.M31, m.M32, m.M33, m.M34,
        m.M21, m.M22, m.M23, m.M24,
        m.M41, m.M42, m.M43, m.M44
    );
    public override bool Equals(object? obj) => obj is Matrix4x4 matrix && this == matrix;
    public bool Equals(Matrix4x4 other) => this == other;
    public override int GetHashCode() => HashCode.Combine(
        HashCode.Combine(M11, M12, M13, M14, M21, M22, M23, M24),
        HashCode.Combine(M31, M32, M33, M34, M41, M42, M43, M44)
    );
    public override string ToString() => $"Matrix4x4({M11}, {M12}, {M13}, {M14},{M21}, {M22}, {M23}, {M24},{M31}, {M32}, {M33}, {M34},{M41}, {M42}, {M43}, {M44})";
}
/// 
/// Provides extension methods for  type.
/// 
public static class Matrix4x4Extensions
{
    /// 
    public static float Determinant(this Matrix4x4 matrix) => Matrix4x4.Determinant(matrix);
    /// 
    public static Matrix4x4 ApplyTranslation(this Matrix4x4 matrix, Vector3D translation) => matrix * Matrix4x4.CreateTranslation(translation);
    /// 
    public static Matrix4x4 ApplyScale(this Matrix4x4 matrix, Vector3 scale) => matrix * Matrix4x4.CreateScale(scale);
    /// 
    public static Matrix4x4 ApplyRotationX(this Matrix4x4 matrix, float radians) => matrix * Matrix4x4.CreateRotationX(radians);
    /// 
    public static Matrix4x4 ApplyRotationY(this Matrix4x4 matrix, float radians) => matrix * Matrix4x4.CreateRotationY(radians);
    /// 
    public static Matrix4x4 ApplyRotationZ(this Matrix4x4 matrix, float radians) => matrix * Matrix4x4.CreateRotationZ(radians);
    /// 
    public static Matrix4x4 ApplyRotation(this Matrix4x4 matrix, Quaternion quaternion) => matrix * Matrix4x4.CreateRotation(quaternion);
    /// 
    public static Matrix4x4 ApplyLookRotationTo(this Matrix4x4 matrix, Vector3D forward, Vector3 up) => matrix * Matrix4x4.CreateLookMatrix(forward, up);
    /// 
    public static Matrix4x4 CreateLookMatrixTo(this Vector3D from, Vector3D to, Vector3 up) => Matrix4x4.CreateLookMatrix(from, to, up);
    /// 
    public static Matrix4x4 ApplyPerspectiveFieldOfView(this Matrix4x4 matrix, float fieldOfViewInRadians, float aspectRatio, float nearPlane, float farPlane)
        => matrix * Matrix4x4.CreatePerspectiveFieldOfView(fieldOfViewInRadians, aspectRatio, nearPlane, farPlane);
    ///