using System;
using System.Collections.Generic;
using System.Drawing;
using System.Text;

namespace StarEngine.Core
{
    /// <summary>
    /// アフィン変換のための行列です。
    /// </summary>
    public struct AffineMatrix
    {
        /// <summary>
        /// 零行列を取得します。
        /// </summary>
        public static AffineMatrix O
        {
            get { return new AffineMatrix(0, 0, 0, 0, 0, 0); }
        }

        /// <summary>
        /// 単位行列を取得します。
        /// </summary>
        public static AffineMatrix I
        {
            get { return new AffineMatrix(1, 0, 0, 1, 0, 0); }
        }

        /// <summary>
        /// 2 つの AffineMatrix が同値かどうかを判断します。
        /// </summary>
        /// <param name="m1">AffineMatrix オブジェクト。</param>
        /// <param name="m2">AffineMatrix オブジェクト。</param>
        /// <returns>m1 と m2 が同値の場合は true。それ以外の場合は false。</returns>
        public static bool operator ==(AffineMatrix m1, AffineMatrix m2)
        {
            return m1.A == m2.A && m1.B == m2.B && m1.C == m2.C && m1.D == m2.D &&
                m1.TX == m2.TX && m1.TY == m2.TY;
        }

        /// <summary>
        /// 2 つの AffineMatrix が異なるかどうかを判断します。
        /// </summary>
        /// <param name="m1">AffineMatrix オブジェクト。</param>
        /// <param name="m2">AffineMatrix オブジェクト。</param>
        /// <returns>m1 と m2 が異なる場合は true。それ以外の場合は false。</returns>
        public static bool operator !=(AffineMatrix m1, AffineMatrix m2)
        {
            return !(m1 == m2);
        }

        /// <summary>
        /// 二つのオブジェクトが同値かどうかを判断します。
        /// </summary>
        /// <param name="obj">オブジェクト。</param>
        /// <returns>このインスタンスと obj が同値の場合は true。それ以外の場合は false。</returns>
        public override bool Equals(object obj)
        {
            return obj is AffineMatrix && this == (AffineMatrix)obj;
        }

        /// <summary>
        /// この AffineMatrix のハッシュコードを返します。
        /// </summary>
        /// <returns>32 ビット符号付き整数ハッシュコード。</returns>
        public override int GetHashCode()
        {
            return this.A.GetHashCode() ^ this.B.GetHashCode() ^
                this.C.GetHashCode() ^ this.D.GetHashCode() ^
                this.TX.GetHashCode() ^ this.TY.GetHashCode();
        }

        /// <summary>
        /// この AffineMatrix の文字列表現を返します。
        /// </summary>
        /// <returns>AffineMatrix の文字列表現。</returns>
        public override string ToString()
        {
            return string.Format("[[{0},{1},{4}],[{2},{3},{5}],[0,0,1]]",
                this.A, this.B, this.C, this.D, this.TX, this.TY);
        }

        /// <summary>
        /// A 成分。
        /// </summary>
        public double A;

        /// <summary>
        /// B 成分。
        /// </summary>
        public double B;

        /// <summary>
        /// C 成分。
        /// </summary>
        public double C;

        /// <summary>
        /// D 成分。
        /// </summary>
        public double D;

        /// <summary>
        /// X 軸方向の平行移動成分。
        /// </summary>
        public double TX;

        /// <summary>
        /// Y 軸方向の平行移動成分。
        /// </summary>
        public double TY;

        /// <summary>
        /// 新しい AffineMatrix オブジェクトを初期化します。
        /// </summary>
        /// <param name="a">A 成分。</param>
        /// <param name="b">B 成分。</param>
        /// <param name="c">C 成分。</param>
        /// <param name="d">D 成分。</param>
        /// <param name="tx">X 軸方向の平行移動成分。</param>
        /// <param name="ty">Y 軸方向の平行移動成分。</param>
        public AffineMatrix(double a, double b, double c, double d, double tx, double ty)
        {
            this = new AffineMatrix();
            this.A = a;
            this.B = b;
            this.C = c;
            this.D = d;
            this.TX = tx;
            this.TY = ty;
        }

        /// <summary>
        /// 正則か否かを示す値を取得します。
        /// </summary>
        public bool IsRegular
        {
            get
            {
                return this.A * this.D - this.B * this.C != 0;
            }
        }

        /// <summary>
        /// アフィン変換を合成します。
        /// </summary>
        /// <param name="m">合成するアフィン変換行列。</param>
        /// <returns>この行列。</returns>
        public AffineMatrix Concat(AffineMatrix m)
        {
            return this = new AffineMatrix(
                m.A * this.A + m.B * this.C,
                m.A * this.B + m.B * this.D,
                m.C * this.A + m.D * this.C,
                m.C * this.B + m.D * this.D,
                m.TX + m.A * this.TX + m.B * this.TY,
                m.TY + m.C * this.TX + m.D * this.TY);
        }

        /// <summary>
        /// 逆行列にします。
        /// </summary>
        /// <returns>この行列。</returns>
        public AffineMatrix Invert()
        {
            double det = this.A * this.D - this.B * this.C;
            if (det == 0)
                throw new StarEngineException("not regular exception");
            double newA = this.D / det;
            double newB = -this.B / det;
            double newC = -this.C / det;
            double newD = this.A / det;

            return this = new AffineMatrix(
                newA, newB, newC, newD,
                newA * -this.TX + newB * -this.TY,
                newC * -this.TX + newD * -this.TY);
        }

        /// <summary>
        /// 点に対して変換処理を行います。
        /// </summary>
        /// <param name="point">変換前の座標。</param>
        /// <returns>変換後の座標。</returns>
        public PointF Transform(PointF point)
        {
            return new PointF(
                (float)(this.A * point.X + this.B * point.Y + this.TX),
                (float)(this.C * point.X + this.D * point.Y + this.TY)
            );
        }
    }
}
