#pragma once

#include "vec2.h"

#include <cstdint>
#include <algorithm>

template<typename T>
struct vec3
{
    union
    {
        struct{T x, y, z;};
        struct{T r, g, b;};
        struct{T h, s, v;};
        T data[3];
    };

    constexpr vec3():x(0),y(0),z(0){}

    template<typename A, typename B, typename C>
    constexpr vec3(const A _1, const B _2, const C _3):x(_1),y(_2),z(_3){}


    template<typename A>
    constexpr vec3(const vec2<A> &_1):x(_1.x),y(_1.y),z(0){}

    template<typename A, typename B>
    constexpr vec3(const vec2<A> &_1, const B _2 = 0):x(_1.x),y(_1.y),z(_2){}

    template<typename A, typename B>
    constexpr vec3(const A _1, const vec2<B> &_2):x(_1),y(_2.x),z(_2.y){}

    template<typename S>
    constexpr vec3(const vec3<S> &a):x(a.x),y(a.y),z(a.z){}

    template<typename S>
    constexpr vec3(const S &a):x(a),y(a),z(a){}

    template <typename S>
    constexpr vec3<T> &operator+=(const vec3<S> &a) noexcept
    {
        x += static_cast<T>(a.x);
        y += static_cast<T>(a.y);
        z += static_cast<T>(a.z);
        return *this;
    }

    template <typename S>
    constexpr vec3<T> &operator-=(const vec3<S> &a) noexcept
    {
        x -= static_cast<T>(a.x);
        y -= static_cast<T>(a.y);
        z -= static_cast<T>(a.z);
        return *this;
    }

    template <typename S>
    constexpr vec3<T> &operator*=(const vec3<S> &a) noexcept
    {
        x *= static_cast<T>(a.x);
        y *= static_cast<T>(a.y);
        z *= static_cast<T>(a.z);
        return *this;
    }

    template <typename S>
    constexpr vec3<T> &operator*=(const S &a) noexcept
    {
        x *= static_cast<T>(a);
        y *= static_cast<T>(a);
        z *= static_cast<T>(a);
        return *this;
    }

    template <typename S>
    constexpr vec3<T> &operator/=(const vec3<S> &a) noexcept
    {
        x /= static_cast<T>(a.x);
        y /= static_cast<T>(a.y);
        z /= static_cast<T>(a.z);
        return *this;
    }

    template <typename S>
    constexpr vec3<T> &operator/=(const S &a) noexcept
    {
        x /= static_cast<T>(a);
        y /= static_cast<T>(a);
        z /= static_cast<T>(a);
        return *this;
    }

    template <typename S>
    constexpr vec3<T> operator+(const vec3<S> &a) const noexcept
    {
        return vec3<T>(x+a.x, y+a.y, z+a.z);
    }

    template <typename S>
    constexpr vec3<T> operator-(const vec3<S> &a) const noexcept
    {
        return vec3<T>(x-static_cast<T>(a.x),
                       y-static_cast<T>(a.y),
                       z-static_cast<T>(a.z));
    }

    template <typename S>
    constexpr vec3<T> operator*(const S &a) const noexcept
    {
        return vec3<T>(x*static_cast<T>(a),
                       y*static_cast<T>(a),
                       z*static_cast<T>(a));
    }

    template <typename S>
    constexpr vec3<T> operator*(const vec3<S> &a) const noexcept
    {
        return vec3<T>(x*static_cast<T>(a.x),
                       y*static_cast<T>(a.y),
                       z*static_cast<T>(a.z));
    }

    template <typename S>
    constexpr vec3<T> operator/(const S &a) const noexcept
    {
        return vec3<T>(x/static_cast<T>(a),
                       y/static_cast<T>(a),
                       z/static_cast<T>(a));
    }

    template <typename S>
    constexpr vec3<T> operator/(const vec3<S> &a) const noexcept
    {
        return vec3<T>(x/static_cast<T>(a.x),
                       y/static_cast<T>(a.y),
                       z/static_cast<T>(a.z));
    }

    template <typename S>
    constexpr bool operator==(const vec3<S> &a) const noexcept
    {
        return x == static_cast<T>(a.x)
            && y == static_cast<T>(a.y)
            && z == static_cast<T>(a.z);
    }

    template <typename S>
    constexpr bool operator!=(const vec3<S> &a) const noexcept
    {
        return x != static_cast<T>(a.x)
            || y != static_cast<T>(a.y)
            || z != static_cast<T>(a.z);
    }

    template <typename S>
    constexpr bool operator>(const vec3<S> &a) const noexcept
    {
        return x*x+y*y+z*z > static_cast<T>(a.x*a.x+a.y*a.y+a.z*a.z);
    }

    template <typename S>
    constexpr bool operator>=(const vec3<S> &a) const noexcept
    {
        return x*x+y*y+z*z >= static_cast<T>(a.x*a.x+a.y*a.y+a.z*a.z);
    }

    template <typename S>
    constexpr bool operator<=(const vec3<S> &a) const noexcept
    {
        return !(operator>(a));
    }

    template <typename S>
    constexpr bool operator<(const vec3<S> &a) const noexcept
    {
        return !(operator>=(a));
    }

    constexpr const T &max()const noexcept{return std::max(x, std::max(y, z));}
    constexpr const T &min()const noexcept{return std::min(x, std::min(y, z));}
    constexpr T &max()noexcept{return std::max(x, std::max(y, z));}
    constexpr T &min()noexcept{return std::min(x, std::min(y, z));}

    constexpr T &operator[](uint32_t a){return *((&x)+a);}

    template <typename S>
    constexpr T dot(const vec3<S> &a) const noexcept
    {
        return T(x*a.x+y*a.y+z*a.z);
    }

    template <typename S>
    constexpr vec3<T> cross(const vec3<S> &a) const noexcept
    {
        return vec3<T>(y*a.z-a.y*z, z*a.x-a.z*x, x*a.y-y*a.x);
    }

    constexpr vec2<T> xy() const noexcept
    {
        return vec2<T>(x, y);
    }

    constexpr vec2<T> yx() const noexcept
    {
        return vec2<T>(y, x);
    }

    constexpr vec2<T> xz() const noexcept
    {
        return vec2<T>(x, z);
    }

    constexpr vec2<T> yz() const noexcept
    {
        return vec2<T>(y, z);
    }

    constexpr vec2<T> zy() const noexcept
    {
        return vec2<T>(z, y);
    }

    constexpr vec2<T> zx() const noexcept
    {
        return vec2<T>(z, x);
    }

    constexpr vec3<T> yzx() const noexcept
    {
        return vec3<T>(y, z, x);
    }

    constexpr vec3<T> zyx() const noexcept
    {
        return vec3<T>(z, y, x);
    }
};

typedef vec3<int8_t> vec3i8;
typedef vec3<int16_t> vec3i16;
typedef vec3<int32_t> vec3i32;

typedef vec3<uint8_t> vec3u8;
typedef vec3<uint16_t> vec3u16;
typedef vec3<uint32_t> vec3u32;

typedef vec3<float> vec3f;
typedef vec3<double> vec3d;

