#pragma once

#include <cmath>

#include "tiny_vec.h"

template <class T>
class Matrix4 {
public:
    Matrix4(
        T c0r0, T c0r1, T c0r2, T c0r3,
        T c1r0, T c1r1, T c1r2, T c1r3,
        T c2r0, T c2r1, T c2r2, T c2r3,
        T c3r0, T c3r1, T c3r2, T c3r3
    ) {
        x = tiny_vec<T, 4>(c0r0, c0r1, c0r2, c0r3);
        y = tiny_vec<T, 4>(c1r0, c1r1, c1r2, c1r3);
        z = tiny_vec<T, 4>(c2r0, c2r1, c2r2, c2r3);
        w = tiny_vec<T, 4>(c3r0, c3r1, c3r2, c3r3);
    }

    ~Matrix4() { }

    static Matrix4 identity() 
    {
        T zero = T::value_type(0);
        T one = T::value_type(1);

        return Matrix4(
            one,  zero, zero, zero,
            zero, one,  zero, zero,
            zero, zero, one,  zero,
            zero, zero, zero,  one
        );
    }

    static Matrix4 from_translation(tiny_vec<T, 3> v)
    {
        T zero = T::value_type(0);
        T one = T::value_type(1);

        return Matrix4(
            one,  zero, zero, zero,
            zero,  one, zero, zero,
            zero, zero,  one, zero,
            v.x,   v.y,  v.z,  one
        );
    }

    static Matrix4 from_scale(T s)
    {
        return from_non_uniform_scale(s, s, s);
    }

    static Matrix4 from_non_uniform_scale(T x, T y, T z) 
    {
        T zero = T::value_type(0);
        T one = T::value_type(1);

        return Matrix4(
            x,    zero, zero, zero,
            zero, y,    zero, zero,
            zero, zero, z,    zero,
            zero, zero, zero,  one
        );
    }

    static Matrix4 from_angle_x(T a)
    {
        T s = std::sin(a);
        T c = std::cos(a);

        T zero = T::value_type(0);
        T one = T::value_type(1);

        return Matrix4(
            one,  zero, zero, zero,
            zero,    c,    s, zero,
            zero,   -s,    c, zero,
            zero, zero, zero,  one
        );
    }
    
    static Matrix4 from_angle_y(T a)
    {
        T s = std::sin(a);
        T c = std::cos(a);

        T zero = T::value_type(0);
        T one = T::value_type(1);

        return Matrix4(
            c,    zero,   -s, zero,
            zero, one,  zero, zero,
            s,    zero,    c, zero,
            zero, zero, zero,  one
        );
    }
    
    static Matrix4 from_angle_z(T a)
    {
        T s = std::sin(a);
        T c = std::cos(a);

        T zero = T::value_type(0);
        T one = T::value_type(1);

        return Matrix4(
            c,       s, zero, zero,
            -s,      c, zero, zero,
            zero, zero,  one, zero,
            zero, zero, zero,  one
        );
    }
    

private:
    // first column
    tiny_vec<T, 4> x;
    // second column
    tiny_vec<T, 4> y;
    // third column
    tiny_vec<T, 4> z;
    // fourth column
    tiny_vec<T, 4> w;
};