#pragma once

constexpr float PI32 = 3.14159265359f;
constexpr float DEG2RAD = (PI32 / 180.f); // @Usage: degree * DEG2RAD = radians;
constexpr float RAD2DEG = (180.f / PI32);

union Vec2 {
    struct {
        float x, y;
    };
    struct {
        float w, h;
    };
    float e[2];
};

union Vec3 {
    struct {
        float x, y, z;
    };
    struct {
        float r, g, b;
    };
    struct {
        float h, s, l;
    };
    struct {
        Vec2 xy;
        float floor;
    };
    struct {
        float _x1;
        Vec2 yz;
    };
    float e[3];
};

union Vec4 {
    struct {
        float x, y, z, w;
    };
    struct {
        float r, g, b, a;
    };
    struct {
        float h, s, l, a;
    };
    struct {
        Vec3 rgb;
        float _i1;
    };
    struct {
        Vec3 xyz;
        float _i2;
    };
    struct {
        Vec2 xy;
        Vec2 zw;
    };


    float e[4];
};

union Vec2I {
    struct {
        int x, y;
    };
    int e[2];
};

union Vec3I {
    struct {
        int x;
        int y;
        int z;
    };
    struct {
        int r, g, b;
    };
    struct {
        int z_[2];
        int floor;
    };
    int e[3];
};

union Vec4I {
    struct {
        int x, y, z, w;
    };
    struct {
        int r, g, b, a;
    };
    int e[4];
};

struct Rect2 {
    union {
        struct {
            float x0;
            float y0;
            float x1;
            float y1;
        };
        struct {
            Vec2 min;
            Vec2 max;
        };
    };
};

struct Rect3 {
    Vec3 min;
    Vec3 max;
};

// Right handed coordinate system +
struct M4x4 {
    // Matrices are stored ROW MAJOR (But they follow notation and behaviour of column major math notation !)
    // Matrix.e[row][column]
    // Multiplication follows right to left = TRANSLATION * ROTATION * SCALE * V
    // When sending to OpenGL need to transpose to column major !!!
    //
    // We want matrices to look and act exactly like in a textbook.
    // When we store row major the C notation mimicks the textbook.
    float e[4][4];
};

#define COLOR_WHITE \
    Vec4 { 1.f, 1.f, 1.f, 1.f }
#define COLOR_BLACK \
    Vec4 { 0.f, 0.f, 0.f, 1.f }
#define COLOR_BLACK_NO_ALPHA \
    Vec4 { 0.f, 0.f, 0.f, 0.f }
#define COLOR_GREY \
    Vec4 { 0.5f, 0.5f, 0.5f, 1.f }
#define COLOR_RED \
    Vec4 { 1.0f, 0.f, 0.f, 1.f }
#define COLOR_GREEN \
    Vec4 { 0.0f, 1.f, 0.f, 1.f }
#define COLOR_BLUE \
    Vec4 { 0.0f, 0.f, 1.f, 1.f }


template<class T>
inline T Maximum(T a, T b) {
    if (a > b) return a;
    return b;
}

template<class T>
inline T Minimum(T a, T b) {
    if (a > b) return b;
    return a;
}

template<class T>
inline void Swap(T &a, T& b) {
    T temp = a;
    a = b;
    b = temp;
}

inline Vec2 Vec2IToVec2(Vec2I a) { return { (float)a.x, (float)a.y,   }; }
inline Vec3 Vec3IToVec3(Vec3I a) { return { (float)a.x, (float)a.y, (float)a.z,  }; }
inline Vec4 Vec4IToVec4(Vec4I a) { return { (float)a.x, (float)a.y, (float)a.z, (float)a.w, }; }
inline Vec2 operator-(Vec2 a) { return { -a.x, -a.y   }; }
inline Vec3 operator-(Vec3 a) { return { -a.x, -a.y ,-a.z  }; }
inline Vec4 operator-(Vec4 a) { return { -a.x, -a.y ,-a.z ,-a.w }; }
inline Vec2 operator*(Vec2 a, Vec2 b) { return { a.x * b.x, a.y * b.y,   }; }
inline Vec2 &operator*=(Vec2 &a, Vec2 b) { a = a * b; return a; }
inline Vec2 operator*(Vec2 a, float b) { return { a.x * b, a.y * b,   }; }
inline Vec2 operator*(float b, Vec2 a) { return { a.x * b, a.y * b,   }; }
inline Vec2 &operator*=(Vec2 &a, float b) { a = a * b; return a; }
inline Vec3 operator*(Vec3 a, Vec3 b) { return { a.x * b.x, a.y * b.y, a.z * b.z,  }; }
inline Vec3 &operator*=(Vec3 &a, Vec3 b) { a = a * b; return a; }
inline Vec3 operator*(Vec3 a, float b) { return { a.x * b, a.y * b, a.z * b,  }; }
inline Vec3 operator*(float b, Vec3 a) { return { a.x * b, a.y * b, a.z * b,  }; }
inline Vec3 &operator*=(Vec3 &a, float b) { a = a * b; return a; }
inline Vec4 operator*(Vec4 a, Vec4 b) { return { a.x * b.x, a.y * b.y, a.z * b.z, a.w * b.w, }; }
inline Vec4 &operator*=(Vec4 &a, Vec4 b) { a = a * b; return a; }
inline Vec4 operator*(Vec4 a, float b) { return { a.x * b, a.y * b, a.z * b, a.w * b, }; }
inline Vec4 operator*(float b, Vec4 a) { return { a.x * b, a.y * b, a.z * b, a.w * b, }; }
inline Vec4 &operator*=(Vec4 &a, float b) { a = a * b; return a; }
inline Vec2 operator/(Vec2 a, Vec2 b) { return { a.x / b.x, a.y / b.y,   }; }
inline Vec2 &operator/=(Vec2 &a, Vec2 b) { a = a / b; return a; }
inline Vec2 operator/(Vec2 a, float b) { return { a.x / b, a.y / b,   }; }
inline Vec2 operator/(float b, Vec2 a) { return { b / a.x, b / a.y,   }; }
inline Vec2 &operator/=(Vec2 &a, float b) { a = a / b; return a; }
inline Vec3 operator/(Vec3 a, Vec3 b) { return { a.x / b.x, a.y / b.y, a.z / b.z,  }; }
inline Vec3 &operator/=(Vec3 &a, Vec3 b) { a = a / b; return a; }
inline Vec3 operator/(Vec3 a, float b) { return { a.x / b, a.y / b, a.z / b,  }; }
inline Vec3 operator/(float b, Vec3 a) { return { b / a.x, b / a.y, b / a.z,  }; }
inline Vec3 &operator/=(Vec3 &a, float b) { a = a / b; return a; }
inline Vec4 operator/(Vec4 a, Vec4 b) { return { a.x / b.x, a.y / b.y, a.z / b.z, a.w / b.w, }; }
inline Vec4 &operator/=(Vec4 &a, Vec4 b) { a = a / b; return a; }
inline Vec4 operator/(Vec4 a, float b) { return { a.x / b, a.y / b, a.z / b, a.w / b, }; }
inline Vec4 operator/(float b, Vec4 a) { return { b / a.x, b / a.y, b / a.z, b / a.w, }; }
inline Vec4 &operator/=(Vec4 &a, float b) { a = a / b; return a; }
inline Vec2 operator+(Vec2 a, Vec2 b) { return { a.x + b.x, a.y + b.y,   }; }
inline Vec2 &operator+=(Vec2 &a, Vec2 b) { a = a + b; return a; }
inline Vec2 operator+(Vec2 a, float b) { return { a.x + b, a.y + b,   }; }
inline Vec2 operator+(float b, Vec2 a) { return { a.x + b, a.y + b,   }; }
inline Vec2 &operator+=(Vec2 &a, float b) { a = a + b; return a; }
inline Vec3 operator+(Vec3 a, Vec3 b) { return { a.x + b.x, a.y + b.y, a.z + b.z,  }; }
inline Vec3 &operator+=(Vec3 &a, Vec3 b) { a = a + b; return a; }
inline Vec3 operator+(Vec3 a, float b) { return { a.x + b, a.y + b, a.z + b,  }; }
inline Vec3 operator+(float b, Vec3 a) { return { a.x + b, a.y + b, a.z + b,  }; }
inline Vec3 &operator+=(Vec3 &a, float b) { a = a + b; return a; }
inline Vec4 operator+(Vec4 a, Vec4 b) { return { a.x + b.x, a.y + b.y, a.z + b.z, a.w + b.w, }; }
inline Vec4 &operator+=(Vec4 &a, Vec4 b) { a = a + b; return a; }
inline Vec4 operator+(Vec4 a, float b) { return { a.x + b, a.y + b, a.z + b, a.w + b, }; }
inline Vec4 operator+(float b, Vec4 a) { return { a.x + b, a.y + b, a.z + b, a.w + b, }; }
inline Vec4 &operator+=(Vec4 &a, float b) { a = a + b; return a; }
inline Vec2 operator-(Vec2 a, Vec2 b) { return { a.x - b.x, a.y - b.y,   }; }
inline Vec2 &operator-=(Vec2 &a, Vec2 b) { a = a - b; return a; }
inline Vec2 operator-(Vec2 a, float b) { return { a.x - b, a.y - b,   }; }
inline Vec2 operator-(float b, Vec2 a) { return { b - a.x, b - a.y,   }; }
inline Vec2 &operator-=(Vec2 &a, float b) { a = a - b; return a; }
inline Vec3 operator-(Vec3 a, Vec3 b) { return { a.x - b.x, a.y - b.y, a.z - b.z,  }; }
inline Vec3 &operator-=(Vec3 &a, Vec3 b) { a = a - b; return a; }
inline Vec3 operator-(Vec3 a, float b) { return { a.x - b, a.y - b, a.z - b,  }; }
inline Vec3 operator-(float b, Vec3 a) { return { b - a.x, b - a.y, b - a.z,  }; }
inline Vec3 &operator-=(Vec3 &a, float b) { a = a - b; return a; }
inline Vec4 operator-(Vec4 a, Vec4 b) { return { a.x - b.x, a.y - b.y, a.z - b.z, a.w - b.w, }; }
inline Vec4 &operator-=(Vec4 &a, Vec4 b) { a = a - b; return a; }
inline Vec4 operator-(Vec4 a, float b) { return { a.x - b, a.y - b, a.z - b, a.w - b, }; }
inline Vec4 operator-(float b, Vec4 a) { return { b - a.x, b - a.y, b - a.z, b - a.w, }; }
inline Vec4 &operator-=(Vec4 &a, float b) { a = a - b; return a; }

inline Vec2I operator-(Vec2I a) { return { -a.x, -a.y   }; }
inline Vec3I operator-(Vec3I a) { return { -a.x, -a.y ,-a.z  }; }
inline Vec4I operator-(Vec4I a) { return { -a.x, -a.y ,-a.z ,-a.w }; }
inline Vec2I operator*(Vec2I a, Vec2I b) { return { a.x * b.x, a.y * b.y,   }; }
inline Vec2I &operator*=(Vec2I &a, Vec2I b) { a = a * b; return a; }
inline Vec2I operator*(Vec2I a, int b) { return { a.x * b, a.y * b,   }; }
inline Vec2I operator*(int b, Vec2I a) { return { a.x * b, a.y * b,   }; }
inline Vec2I &operator*=(Vec2I &a, int b) { a = a * b; return a; }
inline Vec3I operator*(Vec3I a, Vec3I b) { return { a.x * b.x, a.y * b.y, a.z * b.z,  }; }
inline Vec3I &operator*=(Vec3I &a, Vec3I b) { a = a * b; return a; }
inline Vec3I operator*(Vec3I a, int b) { return { a.x * b, a.y * b, a.z * b,  }; }
inline Vec3I operator*(int b, Vec3I a) { return { a.x * b, a.y * b, a.z * b,  }; }
inline Vec3I &operator*=(Vec3I &a, int b) { a = a * b; return a; }
inline Vec4I operator*(Vec4I a, Vec4I b) { return { a.x * b.x, a.y * b.y, a.z * b.z, a.w * b.w, }; }
inline Vec4I &operator*=(Vec4I &a, Vec4I b) { a = a * b; return a; }
inline Vec4I operator*(Vec4I a, int b) { return { a.x * b, a.y * b, a.z * b, a.w * b, }; }
inline Vec4I operator*(int b, Vec4I a) { return { a.x * b, a.y * b, a.z * b, a.w * b, }; }
inline Vec4I &operator*=(Vec4I &a, int b) { a = a * b; return a; }
inline Vec2I operator/(Vec2I a, Vec2I b) { return { a.x / b.x, a.y / b.y,   }; }
inline Vec2I &operator/=(Vec2I &a, Vec2I b) { a = a / b; return a; }
inline Vec2I operator/(Vec2I a, int b) { return { a.x / b, a.y / b,   }; }
inline Vec2I operator/(int b, Vec2I a) { return { b / a.x, b / a.y,   }; }
inline Vec2I &operator/=(Vec2I &a, int b) { a = a / b; return a; }
inline Vec3I operator/(Vec3I a, Vec3I b) { return { a.x / b.x, a.y / b.y, a.z / b.z,  }; }
inline Vec3I &operator/=(Vec3I &a, Vec3I b) { a = a / b; return a; }
inline Vec3I operator/(Vec3I a, int b) { return { a.x / b, a.y / b, a.z / b,  }; }
inline Vec3I operator/(int b, Vec3I a) { return { b / a.x, b / a.y, b / a.z,  }; }
inline Vec3I &operator/=(Vec3I &a, int b) { a = a / b; return a; }
inline Vec4I operator/(Vec4I a, Vec4I b) { return { a.x / b.x, a.y / b.y, a.z / b.z, a.w / b.w, }; }
inline Vec4I &operator/=(Vec4I &a, Vec4I b) { a = a / b; return a; }
inline Vec4I operator/(Vec4I a, int b) { return { a.x / b, a.y / b, a.z / b, a.w / b, }; }
inline Vec4I operator/(int b, Vec4I a) { return { b / a.x, b / a.y, b / a.z, b / a.w, }; }
inline Vec4I &operator/=(Vec4I &a, int b) { a = a / b; return a; }
inline Vec2I operator+(Vec2I a, Vec2I b) { return { a.x + b.x, a.y + b.y,   }; }
inline Vec2I &operator+=(Vec2I &a, Vec2I b) { a = a + b; return a; }
inline Vec2I operator+(Vec2I a, int b) { return { a.x + b, a.y + b,   }; }
inline Vec2I operator+(int b, Vec2I a) { return { a.x + b, a.y + b,   }; }
inline Vec2I &operator+=(Vec2I &a, int b) { a = a + b; return a; }
inline Vec3I operator+(Vec3I a, Vec3I b) { return { a.x + b.x, a.y + b.y, a.z + b.z,  }; }
inline Vec3I &operator+=(Vec3I &a, Vec3I b) { a = a + b; return a; }
inline Vec3I operator+(Vec3I a, int b) { return { a.x + b, a.y + b, a.z + b,  }; }
inline Vec3I operator+(int b, Vec3I a) { return { a.x + b, a.y + b, a.z + b,  }; }
inline Vec3I &operator+=(Vec3I &a, int b) { a = a + b; return a; }
inline Vec4I operator+(Vec4I a, Vec4I b) { return { a.x + b.x, a.y + b.y, a.z + b.z, a.w + b.w, }; }
inline Vec4I &operator+=(Vec4I &a, Vec4I b) { a = a + b; return a; }
inline Vec4I operator+(Vec4I a, int b) { return { a.x + b, a.y + b, a.z + b, a.w + b, }; }
inline Vec4I operator+(int b, Vec4I a) { return { a.x + b, a.y + b, a.z + b, a.w + b, }; }
inline Vec4I &operator+=(Vec4I &a, int b) { a = a + b; return a; }
inline Vec2I operator-(Vec2I a, Vec2I b) { return { a.x - b.x, a.y - b.y,   }; }
inline Vec2I &operator-=(Vec2I &a, Vec2I b) { a = a - b; return a; }
inline Vec2I operator-(Vec2I a, int b) { return { a.x - b, a.y - b,   }; }
inline Vec2I operator-(int b, Vec2I a) { return { b - a.x, b - a.y,   }; }
inline Vec2I &operator-=(Vec2I &a, int b) { a = a - b; return a; }
inline Vec3I operator-(Vec3I a, Vec3I b) { return { a.x - b.x, a.y - b.y, a.z - b.z,  }; }
inline Vec3I &operator-=(Vec3I &a, Vec3I b) { a = a - b; return a; }
inline Vec3I operator-(Vec3I a, int b) { return { a.x - b, a.y - b, a.z - b,  }; }
inline Vec3I operator-(int b, Vec3I a) { return { b - a.x, b - a.y, b - a.z,  }; }
inline Vec3I &operator-=(Vec3I &a, int b) { a = a - b; return a; }
inline Vec4I operator-(Vec4I a, Vec4I b) { return { a.x - b.x, a.y - b.y, a.z - b.z, a.w - b.w, }; }
inline Vec4I &operator-=(Vec4I &a, Vec4I b) { a = a - b; return a; }
inline Vec4I operator-(Vec4I a, int b) { return { a.x - b, a.y - b, a.z - b, a.w - b, }; }
inline Vec4I operator-(int b, Vec4I a) { return { b - a.x, b - a.y, b - a.z, b - a.w, }; }
inline Vec4I &operator-=(Vec4I &a, int b) { a = a - b; return a; }

inline Rect2 Rect2_Center(Vec2 center, Vec2 halfdim) { return {center.x - halfdim.x, center.y - halfdim.y, center.x + halfdim.x, center.y + halfdim.y}; }
inline Vec2 CalcSize(Rect2 r) { return { r.max.x - r.min.x, r.max.y - r.min.y }; }
inline Rect2 Rect2_Size(float x, float y, float w, float h) { return { x, y, x + w, y + h }; }
inline Rect2 Rect2_Size(Vec2 pos, Vec2 size) { return { pos.x, pos.y, pos.x + size.w, pos.y + size.h }; }
inline Rect2 Rect2_MinMax(Vec2 p0, Vec2 p1) { return {p0.x, p0.y, p1.x, p1.y }; }
inline Rect2 ShrinkByHalfSize(Rect2 rect, Vec2 half_size) { return {rect.min.x + half_size.x, rect.min.y + half_size.y, rect.max.x - half_size.x, rect.max.y - half_size.y}; }
inline Rect2 ExpandByHalfSize(Rect2 rect, Vec2 half_size) { return {rect.min.x - half_size.x, rect.min.y - half_size.y, rect.max.x + half_size.x, rect.max.y + half_size.y}; }

inline Rect2 Rect2_AdjustMinMax(Vec2 p0, Vec2 p1) {
    Rect2 result = {
        Minimum(p0.x, p1.x), Minimum(p0.y, p1.y),
        Maximum(p0.x, p1.x), Maximum(p0.y, p1.y),
    };
    return result;
}

inline float GetXSize(Rect2 r) { return r.max.x - r.min.x; }
inline float GetYSize(Rect2 r) { return r.max.y - r.min.y; }
inline Vec2 GetSize(Rect2 r) { return r.max - r.min; }

inline float Lerp(float from, float to, float t) {return (1-t)*from + t*to;}
inline Vec2 Lerp(Vec2 from, Vec2 to, float t) {return (1-t)*from + t*to;}
inline Vec3 Lerp(Vec3 from, Vec3 to, float t) {return (1-t)*from + t*to;}
inline Vec4 Lerp(Vec4 from, Vec4 to, float t) {return (1-t)*from + t*to;}

#include <math.h>
inline float SquareRoot(float a) { return sqrtf(a); }
inline Vec2 SquareRoot(Vec2 a) { return {SquareRoot(a.x), SquareRoot(a.y)}; }
inline Vec3 SquareRoot(Vec3 a) { return {SquareRoot(a.x), SquareRoot(a.y), SquareRoot(a.z)}; }
inline Vec4 SquareRoot(Vec4 a) { return {SquareRoot(a.x), SquareRoot(a.y), SquareRoot(a.z), SquareRoot(a.w)}; }
inline float Dot(Vec2 a, Vec2 b) { return a.x * b.x + a.y * b.y; }
inline float Dot(Vec3 a, Vec3 b) { return a.x * b.x + a.y * b.y + a.z * b.z; }
inline float Dot(Vec4 a, Vec4 b) { return a.x * b.x + a.y * b.y + a.z * b.z + a.w * b.w; }
inline float Length(Vec2 a) { return SquareRoot(Dot(a,a)); }
inline float Length(Vec3 a) { return SquareRoot(Dot(a,a)); }
inline float Length(Vec4 a) { return SquareRoot(Dot(a,a)); }

inline Vec2 Normalize(Vec2 a) {
    Vec2 result = {};
    float len = Length(a);
    if (len != 0.f) {
        float inv_len = 1.0f / len;
        result.x = a.x * inv_len;
        result.y = a.y * inv_len;
    }
    return result;
}

inline Vec3 Normalize(Vec3 a) {
    Vec3 result = {};
    float len = Length(a);
    if (len != 0.f) {
        float inv_len = 1.0f / len;
        result.x = a.x * inv_len;
        result.y = a.y * inv_len;
        result.z = a.z * inv_len;
    }
    return result;
}

inline Vec4 Normalize(Vec4 a) {
    Vec4 result = {};
    float len = Length(a);
    if (len != 0.f) {
        float inv_len = 1.0f / len;
        result.x = a.x * inv_len;
        result.y = a.y * inv_len;
        result.z = a.z * inv_len;
        result.w = a.w * inv_len;
    }
    return result;
}

inline M4x4 operator*(M4x4 a, M4x4 b) {
    M4x4 result;
    for (int y = 0; y < 4; y++) {
        for (int x = 0; x < 4; x++) {
            result.e[y][x] =
                a.e[y][0] * b.e[0][x] + a.e[y][1] * b.e[1][x] + a.e[y][2] * b.e[2][x] + a.e[y][3] * b.e[3][x];
        }
    }
    return result;
}

// Matrix is stored ROW MAJOR in memory so that it mimicks the COLUMN MAJOR MATH NOTATION from textbooks.
// AMAZIN!
inline Vec4 operator*(M4x4 a, Vec4 b) {
    Vec4 result;
    for (int y = 0; y < 4; y++) {
        result.e[y] = a.e[y][0] * b.e[0] + a.e[y][1] * b.e[1] + a.e[y][2] * b.e[2] + a.e[y][3] * b.e[3];
    }
    return result;
}

// Matrix is stored ROW MAJOR in memory so that it mimicks the COLUMN MAJOR MATH NOTATION from textbooks.
// AMAZIN!
inline Vec3 operator*(M4x4 a, Vec3 b) {
    Vec4 result;
    for (int y = 0; y < 4; y++)
        result.e[y] = a.e[y][0] * b.e[0] + a.e[y][1] * b.e[1] + a.e[y][2] * b.e[2] + a.e[y][3] * 1;
    return {result.x, result.y, result.z};
}

inline Vec3 GetBasisX(M4x4 a) {
    Vec3 result = {a.e[0][0], a.e[0][1], a.e[0][2]};
    return result;
}

inline Vec3 GetBasisY(M4x4 a) {
    Vec3 result = {a.e[1][0], a.e[1][1], a.e[1][2]};
    return result;
}

inline Vec3 GetBasisZ(M4x4 a) {
    Vec3 result = {a.e[2][0], a.e[2][1], a.e[2][2]};
    return result;
}

inline Vec3 GetTranslation(M4x4 a) {
    Vec3 result = {a.e[0][3], a.e[1][3], a.e[2][3]};
    return result;
}

// Row major or column major: Read struct M4x4
inline M4x4 M4_Perspective(float fov, float window_x, float window_y, float znear, float zfar) {
    float a = window_x / window_y;
    float c = 1.0f / tanf(DEG2RAD*fov/2.0f);
    float f = zfar;
    float n = znear;
    M4x4 result = {
        c/a        , 0   , 0            , 0              ,
        0          , c   , 0            , 0              ,
        0          , 0   , -(f+n)/(f-n) , -(2*f*n)/(f-n) ,
        0          , 0   , -1           , 0              ,
    };
    return result;
}

// Row major or column major: Read struct M4x4
inline M4x4 M4_RotationX(float rotation) {
    float s = sinf(rotation);
    float c = cosf(rotation);
    M4x4 result = {
        1, 0,   0, 0,
        0, c, - s, 0,
        0, s,   c, 0,
        0, 0,   0, 1,
    };
    return result;
}

// Row major or column major: Read struct M4x4
inline M4x4 M4_RotationY(float rotation) {
    float s = sinf(rotation);
    float c = cosf(rotation);
    M4x4 result = {
        c, 0,  s, 0,
        0, 1,  0, 0,
     -s, 0,  c, 0,
        0, 0,  0, 1,
    };
    return result;
}

// Row major or column major: Read struct M4x4
inline M4x4 M4_RotationZ(float rotation) {
    float s = sinf(rotation);
    float c = cosf(rotation);
    M4x4 result = {
        c, -s, 0, 0,
        s,  c, 0, 0,
        0,  0, 1, 0,
        0,  0, 0, 1,
    };
    return result;
}

#if 0
inline Vec3 Cross(Vec3 a, Vec3 b) {
    Vec3 result;
    result.x = a.y * b.z - a.z * b.y;
    result.y = a.z * b.x - a.x * b.z;
    result.z = a.x * b.y - a.y * b.x;
    return result;
}

#endif
inline M4x4 M4_Identity() {
    M4x4 result = {
        1, 0, 0, 0,
        0, 1, 0, 0,
        0, 0, 1, 0,
        0, 0, 0, 1,
    };
    return result;
}

// Row major
inline M4x4 M4_Scale(Vec3 a) {
    M4x4 result = {
        a.x, 0,   0,   0,
        0,   a.y, 0,   0,
        0,   0,   a.z, 0,
        0,   0,   0,   1,
    };
    return result;
}

// Row major
inline M4x4 M4_Translation(Vec3 a) {
    return {
        1, 0, 0, a.x,
        0, 1, 0, a.y,
        0, 0, 1, a.z,
        0, 0, 0, 1
    };
}

// ?
// Why in the instance of changing the basis you need the vectors setup in this way
// but in camera transforms you have those vectors transposed??
inline M4x4 M4_ChangeOfBasis(Vec3 side, Vec3 up, Vec3 forward) {
    M4x4 result = {
        side.x , up.x , forward.x , 0 ,
        side.y , up.y , forward.y , 0 ,
        side.z , up.z , forward.z , 0 ,
        0      , 0    , 0         , 1 ,
    };
    return result;
}

inline M4x4 Transpose(M4x4 a) {
    M4x4 result = a;
    result.e[0][1] = result.e[1][0];
    result.e[0][2] = result.e[2][0];
    result.e[0][3] = result.e[3][0];
    result.e[2][1] = result.e[1][2];
    result.e[3][1] = result.e[1][3];
    result.e[3][2] = result.e[2][3];
    return result;
}

template<class T>
inline T ClampTop(T t, T max) {
    if (t > max) return max;
    return t;
}

template<class T>
inline T ClampBot(T t, T min) {
    if (t < min) return min;
    return t;
}


template<class T>
inline T Clamp(T t, T min, T max) {
    if (t > max) return max;
    else if (t < min) return min;
    return t;
}

inline float Clamp01(float t) {
    return Clamp(t, 0.f, 1.f);
}

inline float Clamp01MapToRange(float t, float min, float max) {
    float range = max - min;
    float value = (t - min) / range;
    float result = Clamp(value, 0.f, 1.f);
    return result;
}

inline bool AreColliding(Rect2 rect, Vec2 point) {
    bool result = point.x > rect.min.x && point.x < rect.max.x && point.y > rect.min.y && point.y < rect.max.y;
    return result;
}

inline bool AreColliding(Rect2 a, Rect2 b) {
    bool result = a.min.x < b.max.x && a.max.x > b.min.x &&a.min.y < b.max.y && a.max.y > b.min.y;
    return result;
}

inline bool AreColliding(Vec2 circle_pos, float radius, Vec2 point) {
    Vec2 d = point - circle_pos;
    float distance = Length(d);
    bool result = distance <= radius;
    return result;
}

/*
 * Converts an RGB color value to HSL. Conversion formula
 * adapted from http://en.wikipedia.org/wiki/HSL_color_space.
 * also https://gist.github.com/ciembor/1494530
 * alpha is ignored
 */
inline Vec3 RGBToHSL(Vec3 color) {

  Vec3 result;
  float max = Maximum(Maximum(color.r,color.g),color.b);
  float min = Minimum(Minimum(color.r,color.g),color.b);

  result.h = result.s = result.l = (max + min) / 2.f;

  if (max == min) {
    result.h = result.s = 0.f; // achromatic
  }
  else {
    float d = max - min;
    result.s = (result.l > 0.5f) ? d / (2.f - max - min) : d / (max + min);

    if (max == color.r) {
      result.h = (color.g - color.b) / d + (color.g < color.b ? 6.f : 0.f);
    }
    else if (max == color.g) {
      result.h = (color.b - color.r) / d + 2.f;
    }
    else if (max == color.b) {
      result.h = (color.r - color.g) / d + 4.f;
    }

    result.h /= 6.f;
  }

  return result;

}

inline Vec4 RGBToHSL(Vec4 color) {
    Vec3 result = RGBToHSL(color.rgb);
    return {result.r, result.g, result.b, color.a};
}

/*
 * Converts an HUE to r, g or b.
 * returns float in the set [0, 1].
 */
inline float HueToRGB(float p, float q, float t) {
  if (t < 0.f) t += 1.f;
  if (t > 1.f) t -= 1.f;
  if (t < 1.f/6.f) return p + (q - p) * 6.f * t;
  if (t < 1.f/2.f) return q;
  if (t < 2.f/3.f) return p + (q - p) * (2.f/3.f - t) * 6.f;
  return p;
}

inline Vec3 HSLToRGB(Vec3 color) {
  Vec3 result;

  if(0 == color.s) {
    result.r = result.g = result.b = color.l; // achromatic
  }
  else {
    float q = color.l < 0.5f ? color.l * (1.f + color.s) : color.l + color.s - color.l * color.s;
    float p = 2.f * color.l - q;
    result.r = HueToRGB(p, q, color.h + 1.f/3.f);
    result.g = HueToRGB(p, q, color.h);
    result.b = HueToRGB(p, q, color.h - 1.f/3.f);
  }
  return result;
}


inline Vec4 HSLToRGB(Vec4 color) {
  Vec3 result = HSLToRGB(color.rgb);
  return {result.r, result.g, result.b, color.a};
}

inline float PingPong(float x) {
	return 1.0f - fabsf(1.f - fmodf(x,2));
}

inline float Absolute(float x) {return fabsf(x);}
inline Vec2 Absolute(Vec2 x) {return {fabsf(x.x), fabsf(x.y)};}
inline Vec4 SetAlpha(Vec4 color, float x) {
    Vec4 result = color;
    result.a = x;
    return result;
}

inline float SmoothStep(float t) {
    float result = t*t*(3-2*t);
    return result;
}

inline float EaseOutBounce(float x) {
    float n1 = 7.5625f;
    float d1 = 2.75f;

    if (x < 1.f / d1) {
        return n1 * x * x;
    } else if (x < 2.f / d1) {
        return n1 * (x -= 1.5f / d1) * x + 0.75f;
    } else if (x < 2.5f / d1) {
        return n1 * (x -= 2.25f / d1) * x + 0.9375f;
    } else {
        return n1 * (x -= 2.625f / d1) * x + 0.984375f;
    }
}

inline float EaseOutElastic(float x) {
    float c4 = (2 * PI32) / 3;

    return x == 0.f
        ? 0
        : x == 1.f
        ? 1
        : powf(2.f, -10.f * x) * sinf((x * 10.f - 0.75f) * c4) + 1.f;
}

inline float EaseInQuint(float x) {
    return x * x * x * x * x;
}
inline float EaseInCubic(float x) {
    return x * x * x;
}
