#ifndef Mat3_H
#define Mat3_H

#include "Vec2.h"

namespace Framework
{
    template<typename T>
    //! A 3x3 Matrix
    class Mat3
    {
    public:
        T elements[3][3]; //! The elements of the matrix

        //! Copies the elements of another matrix into this one
        //! \param r The other matrix
        Mat3& operator=(const Mat3& r)
        {
            memcpy(elements, r.elements, sizeof(elements));
            return *this;
        }

        //! Multiplies all values with a scalar and stores the result
        //! in this matrix \param r The scalar
        Mat3& operator*=(const T r)
        {
            for (T& e : elements)
                e *= r;
            return *this;
        }

        //! Multiplies the matrix with another and stores the result
        //! in this matrix \param r The other matrix
        Mat3& operator*=(const Mat3& r)
        {
            return *this = *this * r;
        }

        //! Multiplies all values with a scalar without modifying this matrix
        //! \param r The scalar
        Mat3 operator*(const T r) const
        {
            Mat3 result = *this;
            return result *= r;
        }

        //! Multiplies the matrix with another without modifying this matrix
        //! \param r The other matrix
        Mat3 operator*(const Mat3& r) const
        {
            Mat3 result;
            for (int j = 0; j < 3; j++)
            {
                for (int k = 0; k < 3; k++)
                {
                    T sum = 0;
                    for (int i = 0; i < 3; i++)
                        sum += elements[j][i] * r.elements[i][k];
                    result.elements[j][k] = sum;
                }
            }
            return result;
        }

        //! Multiplies the matrix with a vector without modifying this matrix
        //! \param r The vector
        Vec2<T> operator*(const Vec2<T> r) const
        {
            Vec2<T> result;
            result.x
                = elements[0][0] * r.x + elements[0][1] * r.y + elements[0][2];
            result.y
                = elements[1][0] * r.x + elements[1][1] * r.y + elements[1][2];
            return result;
        }

        //! Creates a matrix that rotates a vector when multiplied with it
        //! \param radian The angle in radians by which the vector should
        //! be rotated
        static Mat3 rotation(T radian)
        {
            const T cosTheta = (T)lowPrecisionCos(radian);
            const T sinTheta = (T)lowPrecisionSin(radian);
            Mat3 r = {cosTheta, -sinTheta, 0, sinTheta, cosTheta, 0, 0, 0, 1};
            return r;
        }

        //! Creates a matrix that scales a vector when multiplied with it
        //! \param faktor The factor by which to scale
        static Mat3 scaling(T faktor)
        {
            Mat3 s = {faktor, 0, 0, 0, faktor, 0, 0, 0, 1};
            return s;
        }

        //! Creates a matrix that translates a vector when multiplied with it
        //! \param offset The x and y values to add to the vector
        static Mat3 translation(const Vec2<T> offset)
        {
            Mat3 t = {1, 0, offset.x, 0, 1, offset.y, 0, 0, 1};
            return t;
        }

        //! Creates a matrix that does not change a vector when multiplied with
        //! it
        static Mat3 identity()
        {
            Mat3 i = {1, 0, 0, 0, 1, 0, 0, 0, 1};
            return i;
        }
    };
} // namespace Framework

#endif