using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;
using System.Xml.Serialization;

namespace VisiMath
{
    /// <summary>
    /// Matrix2x2, used for 2D transformations
    /// </summary>
    [Serializable]
    [StructLayout(LayoutKind.Sequential)]
    public struct Matrix2 : IEquatable<Matrix2>
    {

        /// <summary>
        /// Matrix fields
        /// </summary>        
        public float m00, m10, m01, m11;

        public float this[int index]
        {
            get
            {
                switch (index)
                {
                    case 0:
                        return m00;
                    case 1:
                        return m10;
                    case 2:
                        return m01;
                    case 3:
                        return m11;
                    default:
                        throw new ArgumentOutOfRangeException("Indexing out of the matrix");
                }                
            }

            set
            {
                switch (index)
                {
                    case 0:
                        m00 =value;break;
                    case 1:
                        m10 = value; break;
                    case 2:
                        m01 = value; break;
                    case 3:
                        m11 = value; break;
                    default:
                        throw new ArgumentOutOfRangeException("Indexing out of the matrix");
                }
            }
        }

        #region Constructors

        public Matrix2(float val)
        {
            m00 = m10 = m01 = m11 = val;
        }

        public Matrix2(float n00, float n01, float n10, float n11)
        {
            m00 = n00;
            m01 = n01;
            m10 = n10;
            m11 = n11;
        }


        #endregion

        #region Constants

        public static Matrix2 Identitity = new Matrix2(1, 0, 0, 1);

        #endregion

        #region Properties

        /// <summary>
        /// Determinant of the matrix
        /// </summary>
        [XmlIgnore]
        float Determinant
        {
            get
            {
                return m00 * m11 - m01 * m10;
            }
        }

        #endregion

        #region Methods

        /// <summary>
        /// Assigns new values to matrix
        /// </summary>
        public void Assign(float n00, float n01, float n10, float n11)
        {
            m00 = n00;
            m01 = n01;
            m10 = n10;
            m11 = n11;
        }

        /// <summary>
        /// Sets matrix to identity
        /// </summary>
        public void SetToIdentity()
        {
            Assign(1, 0, 0, 1);
        }

        /// <summary>
        /// Transposes Matrix
        /// </summary>
        public void Transpose()
        {
            MathTools.Swap(ref m01, ref m10);
        }

        /// <summary>
        /// Inverts matrix
        /// </summary>
        public void Invert()
        {
            this = Matrix2.Invert(ref this);
        }

        /// <summary>
        /// Adds another matrix to this one
        /// </summary>
        /// <param name="other">right operand</param>
        public void Add(ref Matrix2 other)
        {
            m00 += other.m00;
            m01 += other.m01;
            m10 += other.m10;
            m11 += other.m11;
        }

        /// <summary>
        /// Substracts another matrix from this one
        /// </summary>
        /// <param name="other">right operand</param>
        public void Substract(ref Matrix2 other)
        {
            m00 -= other.m00;
            m01 -= other.m01;
            m10 -= other.m10;
            m11 -= other.m11;
        }

        /// <summary>
        /// Scalar multiplication
        /// </summary>
        /// <param name="scale">Scalar operand</param>
        public void Multiply(float scale)
        {
            m00 *= scale;
            m01 *= scale;
            m10 *= scale;
            m11 *= scale;
        }

        /// <summary>
        /// Multiplies this matrix by another (this = this*other)
        /// </summary>
        /// <param name="other">right operand</param>
        public void MultiplyBy(ref Matrix2 other)
        {
            this = Matrix2.Multiply(ref this, ref other);
        }

        /// <summary>
        /// Multiplies this matrix by another and returns this*other)
        /// </summary>
        /// <param name="other">right operand</param>
        public Matrix2 Multiply(ref Matrix2 other)
        {
            return Matrix2.Multiply(ref this, ref other);
        }


        /// <summary>
        /// Multiplies vector with matrix
        /// </summary>
        /// <param name="vec">right operand</param>
        /// <returns>transformed vector</returns>
        public Vector2 Multiply(ref Vector2 vec)
        {
            return new Vector2(m00 * vec.X + m01 * vec.Y, m10 * vec.X + m11 * vec.Y);
        }

        /// <summary>
        /// Transforms vector by matrix
        /// </summary>
        /// <param name="vec">vector to be transformed</param>
        public void Transform(ref Vector2 vec)
        {
            vec.Assign(m00 * vec.X + m01 * vec.Y, m10 * vec.X + m11 * vec.Y);
        }

        
        #endregion


        #region Static methods

        #region math

        /// <summary>
        /// Calculates inverted matrix
        /// </summary>
        /// <param name="mat">matrix to be inverted</param>
        /// <returns>Inverted matrix</returns>
        /// <exception cref="System.ArgumentException">Thrown if matrix is singular</exception>
        public static Matrix2 Invert(ref Matrix2 mat)
        {
            float invdet = 1.0f / (mat.m00 * mat.m11 - mat.m01 * mat.m11);
            if (MathTools.IsAlmostZero(invdet))
            {
                throw new ArgumentException("Cannot invert singular matrix");
            }
            Matrix2 rval = new Matrix2(mat.m11, -mat.m01, -mat.m10, mat.m00);
            rval.Multiply(invdet);
            return rval;
        }

        /// <summary>
        /// Multiplies two matrices and returns result
        /// </summary>
        /// <param name="left">left operand</param>
        /// <param name="right">right operand</param>
        /// <returns>left*right</returns>
        private static Matrix2 Multiply(ref Matrix2 left, ref Matrix2 right)
        {
            return new Matrix2(
                    left.m00 * right.m00 + left.m01 * right.m10,
                    left.m00 * right.m01 + left.m01 * right.m11,
                    left.m10 * right.m00 + left.m11 * right.m10,
                    left.m10 * right.m01 + left.m11 * right.m11
                );
        }


        #endregion


        #region matrix creation

        /// <summary>
        /// Creates 2D scale matrix
        /// </summary>
        public static Matrix2 CreateScale(float scale)
        {
            return CreateScale(scale, scale);
        }

        /// <summary>
        /// Creates 2D scale matrix
        /// </summary>
        public static Matrix2 CreateScale(ref Vector2 scale)
        {
            return CreateScale(scale.X, scale.Y);
        }

        /// <summary>
        /// Creates 2D scale matrix
        /// </summary>
        public static Matrix2 CreateScale(float scalex, float scaley)
        {
            return new Matrix2(scalex, 0, 0, scaley);
        }

        /// <summary>
        /// Creates 2d rotation transformation
        /// </summary>
        /// <param name="angle">angle in radians</param>
        /// <returns>New 2d transformation</returns>
        public static Matrix2 CreateRotation(float angle)
        {
            float cos = (float)Math.Cos(angle);
            float sin = (float)Math.Sin(angle);
            return new Matrix2(cos, -sin, sin, cos);
        }


        #endregion

        #endregion

        #region Operators

        /// <summary>
        /// Matrix multiplication
        /// </summary>
        /// <param name="left">left operand</param>
        /// <param name="right">right operand</param>
        /// <returns>New Matrix24 with result</returns>
        public static Matrix2 operator *(Matrix2 left, Matrix2 right)
        {
            return Matrix2.Multiply(ref left, ref right);
        }

        /// <summary>
        /// Scalar multiplication
        /// </summary>
        /// <param name="left">Matrix</param>
        /// <param name="right">scalar</param>
        /// <returns>Matrix*scalar</returns>
        public static Matrix2 operator *(Matrix2 left, float right)
        {
            left.Multiply(right);
            return left;
        }

        /// <summary>
        /// Scalar multiplication
        /// </summary>
        /// <param name="left">scalar</param>
        /// <param name="right">Matrix</param>
        /// <returns>Matrix*scalar</returns>
        public static Matrix2 operator *(float left, Matrix2 right)
        {
            right.Multiply(left);
            return right;
        }

        /// <summary>
        /// Substraction of two matrices
        /// </summary>
        /// <param name="left">left operand</param>
        /// <param name="right">right operand</param>
        /// <returns>left - right</returns>
        public static Matrix2 operator -(Matrix2 left, Matrix2 right)
        {
            left.Substract(ref right);
            return left;
        }

        /// <summary>
        /// Addition of two matrices
        /// </summary>
        /// <param name="left">left operand</param>
        /// <param name="right">right operand</param>
        /// <returns>left + right</returns>
        public static Matrix2 operator +(Matrix2 left, Matrix2 right)
        {
            left.Add(ref right);
            return left;
        }


        /// <summary>
        /// Multiplication of vector by matrix
        /// </summary>
        /// <param name="left">Matrix</param>
        /// <param name="right">vector</param>
        /// <returns>Transformed vector</returns>
        public static Vector2 operator *(Matrix2 left, Vector2 right)
        {
            left.Multiply(ref right);
            return right;
        }


        /// <summary>
        /// Compares two instances
        /// </summary>
        /// <param name="left">first operand.</param>
        /// <param name="right">second operand.</param>
        /// <returns>True if left  == right, otherwise false.</returns>
        public static bool operator ==(Matrix2 left, Matrix2 right)
        {
            return left.Equals(right);
        }

        /// <summary>
        /// Compares two instances
        /// </summary>
        /// <param name="left">first operand.</param>
        /// <param name="right">second operand.</param>
        /// <returns>True if left  != right, otherwise false.</returns>
        public static bool operator !=(Matrix2 left, Matrix2 right)
        {
            return !left.Equals(right);
        }


        #endregion

        #region Overrides

        public override int GetHashCode()
        {
            return m00.GetHashCode() ^ m01.GetHashCode() ^ m10.GetHashCode() ^ m11.GetHashCode();
        }

        public override string ToString()
        {
            return String.Format("({0},{1})\n({0},{1})", m00, m01, m10, m11);
        }

        public override bool Equals(object obj)
        {
            if (obj is Matrix2)
            {
                return this.Equals((Matrix2)obj);
            }
            return false;
        }

        public bool Equals(Matrix2 other)
        {
            for (int i = 0; i < 4; i++)
            {
                if (this[i] != other[i]) return false;
            }
            return true;
        }        
        
        #endregion


    }
}
