#ifndef CORE_VECTOR_H
#define CORE_VECTOR_H

#include "math/Math.h"
#include "utils/ArrayString.h"

namespace Core {
    template<int N, typename T>
    class Vector final {
        static_assert(N > 0, "Vector size must be positive");
        T values[static_cast<unsigned int>(N)];

    public:
        Vector() {
            for(int i = 0; i < N; i++) {
                values[i] = static_cast<T>(0);
            }
        }

        template<typename OT, typename... Args>
        Vector(OT a, Args&&... args) {
            init<0>(a, args...);
        }

    private:
        template<int I>
        void init() {
            static_assert(I == N, "vector parameters do not match its size");
        }

        template<int I, typename OT, typename... Args>
        void init(OT a, Args&&... args) {
            values[I] = static_cast<T>(a);
            init<I + 1>(args...);
        }

    public:
        Vector& setAngles(float, float) = delete;
        Vector cross(const Vector&) const = delete;

        Vector& operator+=(const Vector& other) {
            for(int i = 0; i < N; i++) {
                values[i] += other.values[i];
            }
            return *this;
        }

        Vector operator+(const Vector& other) const {
            Vector v = *this;
            v += other;
            return v;
        }

        Vector& operator-=(const Vector& other) {
            for(int i = 0; i < N; i++) {
                values[i] -= other.values[i];
            }
            return *this;
        }

        Vector operator-() const {
            Vector v = *this;
            for(int i = 0; i < N; i++) {
                v.values[i] = -v.values[i];
            }
            return v;
        }

        Vector operator-(const Vector& other) const {
            Vector v = *this;
            v -= other;
            return v;
        }

        Vector& operator*=(T factor) {
            for(int i = 0; i < N; i++) {
                values[i] *= factor;
            }
            return *this;
        }

        Vector& operator*=(const Vector& other) {
            for(int i = 0; i < N; i++) {
                values[i] *= other.values[i];
            }
            return *this;
        }

        Vector operator*(T factor) const {
            Vector v = *this;
            v *= factor;
            return v;
        }

        Vector operator*(const Vector& other) const {
            Vector v = *this;
            v *= other;
            return v;
        }

        Vector& operator/=(T factor) {
            for(int i = 0; i < N; i++) {
                values[i] /= factor;
            }
            return *this;
        }

        Vector& operator/=(const Vector& other) {
            for(int i = 0; i < N; i++) {
                values[i] /= other.values[i];
            }
            return *this;
        }

        Vector operator/(T factor) const {
            Vector v = *this;
            v /= factor;
            return v;
        }

        Vector operator/(const Vector& other) const {
            Vector v = *this;
            v /= other;
            return v;
        }

        T dot(const Vector& v) const {
            T length = 0.0f;
            for(int i = 0; i < N; i++) {
                length += values[i] * v.values[i];
            }
            return length;
        }

        T squareLength() const {
            return dot(*this);
        }

        float length() const {
            return Math::squareRoot(squareLength());
        }

        Vector& normalize() {
            *this *= 1.0f / length();
            return *this;
        }

        T& operator[](int index) {
            return values[index];
        }

        const T& operator[](int index) const {
            return values[index];
        }

        Vector<N, int> toInt() const {
            Vector<N, int> cast;
            for(int i = 0; i < N; i++) {
                cast[i] = static_cast<int>(values[i]);
            }
            return cast;
        }

        Vector<N, float> toFloat() const {
            Vector<N, float> cast;
            for(int i = 0; i < N; i++) {
                cast[i] = static_cast<float>(values[i]);
            }
            return cast;
        }

        // returns true on error and calls the error callback
        template<int L>
        check_return bool toString(ArrayString<L>& s) const {
            if(s.append("[")) {
                return true;
            }
            for(int i = 0; i < N - 1; i++) {
                if(s.append(values[i]) || s.append(", ")) {
                    return true;
                }
            }
            if(N > 0 && s.append(values[N - 1])) {
                return true;
            }
            return s.append("]");
        }
    };

    using Vector4 = Vector<4, float>;
    using Vector3 = Vector<3, float>;
    using Vector2 = Vector<2, float>;

    using IntVector4 = Vector<4, int>;
    using IntVector3 = Vector<3, int>;
    using IntVector2 = Vector<2, int>;

    template<>
    Vector3& Vector3::setAngles(float lengthAngle, float widthAngle);

    template<>
    Vector3 Vector3::cross(const Vector3& other) const;
}

template<int N, typename T>
Core::Vector<N, T> operator*(T factor, const Core::Vector<N, T>& v) {
    return v * factor;
}

#endif