//
// Created by trotfunky on 30/09/2019.
//

#ifndef TESTS_OPENGL_TYPES_H
#define TESTS_OPENGL_TYPES_H

#include <fstream>

/// Group of coordinates with the input operator overloaded.
/// \tparam T Content of the vector
/// \tparam N Number of coordinates
template <typename T, unsigned int N>
struct CoordinatesVector
{
    T coordinates[N];

    // Use SFINAE to declare the function if and only if the type is arithmetic
    template <typename Scalar>
    typename std::enable_if<std::is_arithmetic<Scalar>::value,CoordinatesVector<T,N>>::type
    operator*(const Scalar& scalar)
    {
        CoordinatesVector result;
        for (unsigned int i = 0;i<N;i++)
        {
            result.coordinates[i] = coordinates[i] * scalar;
        }
    }

    CoordinatesVector operator+(const CoordinatesVector<T,N>& op)
    {
        CoordinatesVector result;
        for (unsigned int i = 0;i<N;i++)
        {
            result.coordinates[i] = coordinates[i] + op.coordinates[i];
        }
        return result;
    }

    CoordinatesVector operator-(const CoordinatesVector<T,N>& op)
    {
        CoordinatesVector result;
        for (unsigned int i = 0;i<N;i++)
        {
            result.coordinates[i] = coordinates[i] - op.coordinates[i];
        }

        return result;
    }

    CoordinatesVector& operator=(const CoordinatesVector<T,N>& original)
    {
        if (this != &original)
        {
            std::copy(std::begin(original.coordinates), std::end(original.coordinates), std::begin(coordinates));
        }
        return *this;
    }

    friend std::fstream& operator>>(std::fstream& stream, CoordinatesVector<T,N>& vector)
    {
        for (unsigned int i = 0;i<N;i++)
        {
            stream >> vector.coordinates[i];
        }
        return stream;
    }
};


template <typename T>
struct Vec2 : public CoordinatesVector<T,2>
{
    T& x = CoordinatesVector<T,2>::coordinates[0];
    T& y = CoordinatesVector<T,2>::coordinates[1];

    Vec2() = default;

    // Cast-copy constructor in order to use the operators of the mother class.
    Vec2(const CoordinatesVector<T,2>& origin)
    {
        if (this != &origin)
        {
            std::copy(std::begin(origin.coordinates),std::end(origin.coordinates),std::begin(CoordinatesVector<T,2>::coordinates));
        }
    }

    Vec2& operator=(const Vec2<T>& origin)
    {
        if (this != &origin)
        {
            std::copy(std::begin(origin.coordinates), std::end(origin.coordinates), std::begin(CoordinatesVector<T,2>::coordinates));
        }
        return *this;
    }
};

template <typename T>
struct Vec3 : CoordinatesVector<T,3>
{

    T& x = CoordinatesVector<T,3>::coordinates[0];
    T& y = CoordinatesVector<T,3>::coordinates[1];
    T& z = CoordinatesVector<T,3>::coordinates[2];

    Vec3() = default;

    // Cast-copy constructor in order to use the operators of the mother class.
    Vec3(const CoordinatesVector<T,3>& origin)
    {
        if (this != &origin)
        {
            std::copy(std::begin(origin.coordinates),std::end(origin.coordinates),std::begin(CoordinatesVector<T,2>::coordinates));
        }
    }

    Vec3& operator=(const Vec3<T>& original)
    {
        if (this != &original)
        {
            std::copy(std::begin(original.coordinates), std::end(original.coordinates), std::begin(CoordinatesVector<T,3>::coordinates));
        }
        return *this;
    }
};


// Define aliases for common types
typedef Vec2<int> Vec2i;
typedef Vec2<float> Vec2f;

typedef Vec3<int> Vec3i;
typedef Vec3<float> Vec3f;


#endif //TESTS_OPENGL_TYPES_H
