/**
 * @file Matrix.h
 * @author Petr Egert (egertpet@fel.cvut.cz)
 * @brief Contains Matrix class declaration and implementation
 */

#ifndef MATRIX_H
#define MATRIX_H

#include <string.h>

namespace RD
{
    template<class T>
    class Vector2;

    template<class T>
    class Vector3;

    template<class T>
    class Vector4;

    /**
     * @brief Class representing a 4x4 matrix
     */
    template<class ValType>
    class Matrix
    {
        public:
            typedef ValType ValueType;

            /**
             * @brief Union representing the matrix values
             *
             * Allows to access members as a 1D array f[0-15] or a 2D array m[0-3][0-3] (m[y][x])
             */
            union
            {
                ValType m[4][4];    ///< Member access using 2D array m[y][x]
                ValType f[16];      ///< Member acces using 1D array
            };


            ///Default constructor - sets all values to 0 (NOT identity!)
            Matrix()
            {
                memset(f, 0, 16 * sizeof(ValType));
            }

            ///Copy constructor
            Matrix(ValType *M)
            {
                memcpy(m, M, 16 * sizeof(ValType));
            }

            ///Constructor using individual values
            Matrix(  ValType A1, ValType A2, ValType A3, ValType A4,
                        ValType B1, ValType B2, ValType B3, ValType B4,
                        ValType C1, ValType C2, ValType C3, ValType C4,
                        ValType D1, ValType D2, ValType D3, ValType D4)
            {
                m[0][0] = A1; m[0][1] = A2; m[0][2] = A3; m[0][3] = A4;
                m[1][0] = B1; m[1][1] = B2; m[1][2] = B3; m[1][3] = B4;
                m[2][0] = C1; m[2][1] = C2; m[2][2] = C3; m[2][3] = C4;
                m[3][0] = D1; m[3][1] = D2; m[3][2] = D3; m[3][3] = D4;
            }

            Matrix<ValType> operator + (const Matrix<ValType> &M)
            {
                Matrix out;
                for(unsigned int i = 0; i < 16; i++) out.f[i] = f[i] + M.f[i];
                return out;
            }

            Matrix<ValType> operator - (const Matrix<ValType> &M)
            {
                Matrix out;
                for(unsigned int i = 0; i < 16; i++) out.f[i] = f[i] - M.f[i];
                return out;
            }

            Matrix<ValType> operator * (const Matrix<ValType> &M)
            {
                Matrix out;
                for(unsigned int a = 0; a < 4; a++)
                {
                    for(unsigned int b = 0; b < 4; b++)
                    {
                        for(unsigned int c = 0; c < 4; c++) out.m[a][b] += m[c][b] * M.m[a][c];
                    }
                }
                return out;
            }

            Matrix<ValType> operator * (const ValType &F)
            {
                Matrix out;
                for(unsigned int i = 0; i < 16; i++) out.f[i] = f[i] * F;
                return out;
            }

            Matrix<ValType> operator / (const ValType &F)
            {
                Matrix out;
                for(unsigned int i = 0; i < 16; i++) out.f[i] = f[i] / F;
                return out;
            }

            void operator += (const Matrix<ValType> &M)
            {
                *this = *this + M;
            }

            void operator -= (const Matrix<ValType> &M)
            {
                *this = *this - M;
            }

            void operator *= (const Matrix<ValType> &M)
            {
                *this = *this * M;
            }

            void operator *= (const ValType &F)
            {
                *this = *this * F;
            }

            void operator /= (const ValType &F)
            {
                *this = *this / F;
            }

            Matrix<ValType> operator = (const Matrix<ValType> &M)
            {
                for(unsigned int i = 0; i < 16; i++) f[i] = M.f[i];
                return *this;
            }

            Matrix<ValType> operator - ()
            {
                return *this * -1.0;
            }

            operator ValType* ()
            {
                return f;
            }

            bool operator == (const Matrix<ValType> &M)
            {
                for(unsigned int i = 0; i < 16; i++) if (f[i] != M.f[i]) return false;
                return true;
            }

            bool operator != (const Matrix<ValType> &M)
            {
                return !(*this == M);
            }

            #include "MatrixFunc.h"
    };

    /// Typedef for a 4x4 matrix consisting of floats
    typedef Matrix<float> mat44;
}

#endif  //MATRIX_H
