#ifndef VECTOR_H
#define VECTOR_H

#include <cmath>

#include "utils/StringBuffer.h"

template<int N>
class Vector final {
    float values[N];

public:
    Vector() {
        for(int i = 0; i < N; i++) {
            values[i] = 0.0f;
        }
    }

    template<typename... Args>
    Vector(float a, Args&&... args) {
        const int size = sizeof...(args) + 1;
        float init[size] = {a, args...};
        static_assert(N == size,
                      "vector size and amount of float arguments do not match");
        for(int i = 0; i < N; i++) {
            values[i] = init[i];
        }
    }

    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*=(float factor) {
        for(int i = 0; i < N; i++) {
            values[i] *= factor;
        }
        return *this;
    }

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

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

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

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

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

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

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

    float length() const {
        return sqrtf(squareLength());
    }

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

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

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

    template<int L>
    void toString(StringBuffer<L>& s) const {
        s.append("[");
        for(int i = 0; i < N - 1; i++) {
            s.append(values[i]);
            s.append(", ");
        }
        if(N > 0) {
            s.append(values[N - 1]);
        }
        s.append("]");
    }
};

template<int N>
Vector<N> operator*(float factor, const Vector<N>& v) {
    return v * factor;
}

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

template<>
Vector<3> Vector<3>::cross(const Vector<3>& other) const;

typedef Vector<4> Vector4;
typedef Vector<3> Vector3;
typedef Vector<2> Vector2;

#endif