#pragma once
#include <stddef.h>

#undef min
#undef max

namespace M {
    template<typename T>
    constexpr inline T min(T a, T b) {
        return (a < b) ? a : b;
    }

    template<typename T>
    constexpr inline T max(T a, T b) {
        return (a > b) ? a : b;
    }

    template<typename T>
    constexpr inline T clamp(T min, T a, T max) {
        return M::min(M::max(min, a), max);
    }

    template<typename T>
    constexpr inline T clamp01(T a) {
        return clamp((T)0, a, (T)1);
    }

    float  square_root(float a);
    double square_root(double a);

    float  reciprocal_square_root(float a);
    double reciprocal_square_root(double a);

    float lerp (float a, float b, float t);
    float ilerp(float a, float b, float v);
    float remap(float in_a, float in_b, float out_a, float out_b, float v);

    float radians(float degrees);

    union V2 {
        struct {
            float x;
            float y;
        };

        struct {
            float u;
            float v;
        };

        struct {
            float width;
            float height;
        };

        struct {
            float E[2];
        };

        constexpr float &operator [](size_t index) {
            return E[index];
        }
    };

    V2 V2_(float x, float y);

    V2 operator - (V2  a);
    V2 operator + (V2  a, V2 b);
    V2 operator +=(V2 &a, V2 b);
    V2 operator - (V2  a, V2 b);
    V2 operator -=(V2 &a, V2 b);

    V2 operator  * (float a, V2    b);
    V2 operator  * (V2    a, float b);
    V2 &operator *=(V2   &a, float b);
    V2 operator  / (float a, V2    b);
    V2 operator  / (V2    a, float b);
    V2 &operator /=(V2   &a, float b);
    V2 operator  / (V2    a, V2    b);

    float dot     (V2 a, V2 b);
    V2    hadamard(V2 a, V2 b);

    float length_squared   (V2 a);
    float length           (V2 a);
    float reciprocal_length(V2 a);

    V2 normalize    (V2 a);
    V2 perpendicular(V2 a);
    V2 clamp01      (V2 a);

    V2    min(V2 a, V2 b);
    V2    max(V2 a, V2 b);
    float min(V2 a);
    float max(V2 a);

    V2 lerp (V2 a, V2 b, float t);
    V2 lerp (V2 a, V2 b, V2    t);
    V2 ilerp(V2 a, V2 b, V2    v);

    V2 remap(V2 in_a, V2 in_b, V2 out_a, V2 out_b, V2 v);

    union V3 {
        struct {
            float x;
            float y;
            float z;
        };

        struct {
            float r;
            float g;
            float b;
        };
        
        struct {
            float u;
            float v;
            float s;
        };

        struct {
            V2 xy;
            float _z;
        };

        struct {
            float _x;
            V2 yz;
        };

        struct {
            V2 uv;
            float _s;
        };

        struct {
            float _u;
            V2 vs;
        };

        struct {
            V2 rg;
            float _b;
        };

        struct {
            float _r;
            V2 gb;
        };

        struct {
            float E[3];
        };

        constexpr float &operator [](size_t index) {
            return E[index];
        }
    };

    V3 V3_(float x, float y, float z);
    V3 V3_(V2 xy, float z);
    V3 V3_(float x, V2 yz);

    V3 operator - (V3  a);
    V3 operator + (V3  a, V3 b);
    V3 operator +=(V3 &a, V3 b);
    V3 operator - (V3  a, V3 b);
    V3 operator -=(V3 &a, V3 b);

    V3 operator * (float a, V3    b);
    V3 operator * (V3    a, float b);
    V3 operator *=(V3   &a, float b);
    V3 operator / (float a, V3    b);
    V3 operator / (V3    a, float b);
    V3 operator /=(V3   &a, float b);
    V3 operator / (V3    a, V3    b);

    float dot     (V3 a, V3 b);
    V3    hadamard(V3 a, V3 b);
    V3    cross   (V3 a, V3 b);

    float length_squared   (V3 a);
    float length           (V3 a);
    float reciprocal_length(V3 a);

    V3 normalize(V3 a);
    V3 clamp01  (V3 a);

    V3    min(V3 a, V3 b);
    V3    max(V3 a, V3 b);
    float min(V3 a);
    float max(V3 a);

    V3 lerp (V3 a, V3 b, float t);
    V3 lerp (V3 a, V3 b, V3    t);
    V3 ilerp(V3 a, V3 b, V3    v);

    V3 remap(V3 in_a, V3 in_b, V3 out_a, V3 out_b, V3 v);

    union V4 {
        struct {
            float x;
            float y;
            float z;
            float w;
        };

        struct {
            float r;
            float g;
            float b;
            float a;
        };

        struct {
            float u;
            float v;
            float s;
            float t;
        };

        struct {
            V2 xy;
            V2 zw;
        };

        struct {
            float _x;
            V2 yz;
            float _w;
        };

        struct {
            V2 uv0;
            V2 uv1;
        };

        struct {
            V2 uv;
            V2 st;
        };
        
        struct {
            float _u;
            V2 vs;
            float _t;
        };
        
        struct {
            V2 rg;
            V2 ba;
        };
        
        struct {
            float _r;
            V2 gb;
            float _a;
        };

        struct {
            V3 xyz;
            float _w2;
        };

        struct {
            float _x2;
            V3 yzw;
        };

        struct {
            V3 rgb;
            float _a2;
        };

        struct {
            float _r2;
            V3 gba;
        };

        struct {
            V3 uvs;
            float _t2;
        };

        struct {
            float _u2;
            V3 vst;
        };

        struct {
            float E[4];
        };

        constexpr float &operator [](size_t index) {
            return E[index];
        }
    };

    V4 V4_(float x, float y, float z, float w);
    V4 V4_(V2 xy, V2 zw);
    V4 V4_(V2 xy, float z, float w);
    V4 V4_(float x, V2 yz, float w);
    V4 V4_(float x, float y, V2 zw);
    V4 V4_(V3 xyz, float w);
    V4 V4_(float x, V3 yzw);

    V4 operator - (V4  a);
    V4 operator + (V4  a, V4 b);
    V4 operator +=(V4 &a, V4 b);
    V4 operator - (V4  a, V4 b);
    V4 operator -=(V4 &a, V4 b);

    V4 operator * (float a, V4    b);
    V4 operator * (V4    a, float b);
    V4 operator *=(V4   &a, float b);
    V4 operator / (float a, V4    b);
    V4 operator / (V4    a, float b);
    V4 operator /=(V4   &a, float b);
    V4 operator / (V4    a, V4    b);

    float dot     (V4 a, V4 b);
    V4    hadamard(V4 a, V4 b);

    float length_squared   (V4 a);
    float length           (V4 a);
    float reciprocal_length(V4 a);

    V4 normalize(V4 a);
    V4 clamp01  (V4 a);

    V4    min(V4 a, V4 b);
    V4    max(V4 a, V4 b);
    float min(V4 a);
    float max(V4 a);

    V4 lerp (V4 a, V4 b, float t);
    V4 lerp (V4 a, V4 b, V4    t);
    V4 ilerp(V4 a, V4 b, V4    v);

    V4 remap(V4 in_a, V4 in_b, V4 out_a, V4 out_b, V4 v);

    union M2x2 {
        struct {
            float _00; float _01;
            float _10; float _11;
        };

        struct {
            float E[2][2];
        };

        struct {
            V2 V[2];
        };

        constexpr V2 &operator [](size_t row) {
            return V[row];
        }

        constexpr static M2x2 identity() {
            return {
                1.0f, 0.0f,
                0.0f, 1.0f
            };
        }
    };

    M2x2 M2x2_(float _00, float _01, 
               float _10, float _11);
    M2x2 M2x2_(V2 row0, V2 row1);

    M2x2 operator - (M2x2  a);
    M2x2 operator + (M2x2  a, M2x2 b);
    M2x2 operator +=(M2x2 &a, M2x2 b);
    M2x2 operator - (M2x2  a, M2x2 b);
    M2x2 operator -=(M2x2 &a, M2x2 b);

    M2x2 operator * (float a, M2x2  b);
    M2x2 operator * (M2x2  a, float b);
    M2x2 operator *=(M2x2 &a, float b);
    M2x2 operator * (M2x2  a, M2x2  b);
    V2   operator * (M2x2  a, V2    b);

    M2x2 transpose(M2x2 a);

    union M3x3 {
        struct {
            float _00; float _01; float _02;
            float _10; float _11; float _12;
            float _20; float _21; float _22;
        };

        struct {
            float E[3][3];
        };

        struct {
            V3 V[3];
        };

        constexpr V3 &operator [](size_t row) {
            return V[row];
        }

        constexpr static M3x3 identity() {
            return {
                1.0f, 0.0f, 0.0f,
                0.0f, 1.0f, 0.0f,
                0.0f, 0.0f, 1.0f
            };
        }
    };

    M3x3 M3x3_(float _00, float _01, float _02,
               float _10, float _11, float _12,
               float _20, float _21, float _22);
    M3x3 M3x3_(V3 row0, V3 row1, V3 row2);

    M3x3 operator - (M3x3  a);
    M3x3 operator + (M3x3  a, M3x3 b);
    M3x3 operator +=(M3x3 &a, M3x3 b);
    M3x3 operator - (M3x3  a, M3x3 b);
    M3x3 operator -=(M3x3 &a, M3x3 b);

    M3x3 operator * (float a, M3x3  b);
    M3x3 operator * (M3x3  a, float b);
    M3x3 operator *=(M3x3 &a, float b);
    M3x3 operator * (M3x3  a, M3x3  b);
    V3   operator * (M3x3  a, V3    b);

    M3x3 transpose(M3x3 a);

    union M4x4 {
        struct {
            float _00; float _01; float _02; float _03;
            float _10; float _11; float _12; float _13;
            float _20; float _21; float _22; float _23;
            float _30; float _31; float _32; float _33;
        };

        struct {
            float E[4][4];
        };

        struct {
            V4 V[4];
        };

        constexpr V4 &operator [](size_t row) {
            return V[row];
        }

        constexpr static M4x4 identity() {
            return {
                1.0f, 0.0f, 0.0f, 0.0f,
                0.0f, 1.0f, 0.0f, 0.0f,
                0.0f, 0.0f, 1.0f, 0.0f,
                0.0f, 0.0f, 0.0f, 1.0f
            };
        }
    };

    M4x4 M4x4_(float _00, float _01, float _02, float _03,
               float _10, float _11, float _12, float _13,
               float _20, float _21, float _22, float _23,
               float _30, float _31, float _32, float _33);
    M4x4 M4x4_(V4 row0, V4 row1, V4 row2, V4 row3);

    M4x4 operator - (M4x4  a);
    M4x4 operator + (M4x4  a, M4x4 b);
    M4x4 operator +=(M4x4 &a, M4x4 b);
    M4x4 operator - (M4x4  a, M4x4 b);
    M4x4 operator -=(M4x4 &a, M4x4 b);

    M4x4 operator * (float a, M4x4  b);
    M4x4 operator * (M4x4  a, float b);
    M4x4 operator *=(M4x4 &a, float b);
    M4x4 operator * (M4x4  a, M4x4  b);
    V4   operator * (M4x4  a, V4    b);

    M4x4 transpose(M4x4 a);

    M4x4 view        (V3 player_pos, float tilt, float camera_distance);
    M4x4 inverse_view(V3 player_pos, float tilt, float camera_distance);

    M4x4 projection        (float fovy, float aspect, float near);
    M4x4 inverse_projection(float fovy, float aspect, float near);
}
