#pragma once

#include <boost/type_traits.hpp>

// TODO templated version of arithmetic operator?

/**
 * @brief Generic 2-dimensional vector that may be used for mathematical
 * operations on vectors.
 * @tparam T type of values the vector contains.
 */
template<class T>
class Vector2
{
  static_assert<boost::is_signed<T>::value || boost::is_float<T>::value> arithmetic_operations_assume_signed_types;
  static_assert<boost::is_scalar<T>::value> arithmetic_operations_assume_scalar_types;

public:
  /**
   * @brief Vector constructor.
   * @param x X coordinate of the vector.
   * @param y Y coordinate of the vector.
   */
  Vector2(const T& x, const T& y) : m_X(x), m_Y(y) { }

  /**
   * @brief Vector type-conversion constructor.
   * @tparam U other type of values to convert.
   * @param x X coordinate of the vector.
   * @param y Y coordinate of the vector.
   */
  template<class U>
  Vector2(const U& x, const U& y) : m_X(static_cast<T>(x)), m_Y(static_cast<T>(y)) { }
  
  /**
   * @brief Type-conversion copy constructor
   * @tparam U other type of values to convert.
   * @param other other vector to copy values from.
   */
  template<class U>
  Vector2(const Vector2<U>& other)
    :m_X(static_cast<T>(other.x()))
    ,m_Y(static_cast<T>(other.y()))
  {
  }

  /**
   * @brief Type-conversion assignment operator.
   * @tparam U other type of values to convert.
   * @param other other vector to copy values from.
   * @return a reference to the present vector.
   */
  template<class U>
  Vector2<T>& operator=(const Vector2<U>& other)
  {
    m_X = static_cast<T>(other.x());
    m_Y = static_cast<T>(other.y());
    return *this;
  }

  T& x() { return m_X; }
  T& y() { return m_Y; }

  T x() const { return m_X; }
  T y() const { return m_Y; }

  /**
   * @brief Addition operator.
   * @param other other vector with which to compute the sum.
   * @return a new vector containing the result of the sum.
   */
  Vector2<T> operator+(const Vector2<T>& other) const
  {
    return Vector2<T>(m_X + other.m_X, m_Y + other.m_Y);
  }

  /**
   * @brief Addition and assignment operator.
   * @param other other vector with which to compute the sum.
   * @return a reference to the present vector.
   */
  Vector2<T>& operator+=(const Vector2<T>& other)
  {
    m_X += other.m_X;
    m_Y += other.m_Y;
    return *this;
  }

  /**
   * @brief Subtraction operator.
   * @param other other vector with which to compute the difference.
   * @return a new vector containing the result of the difference.
   */
  Vector2<T> operator-(const Vector2<T>& other) const
  {
    return Vector2<T>(m_X - other.m_X, m_Y - other.m_Y);
  }

  /**
   * @brief Subtraction and assignment operator.
   * @param other vector with which to compute the difference.
   * @return a reference to the present vector.
   */
  Vector2<T>& operator-=(const Vector2<T>& other)
  {
    m_X -= other.m_X;
    m_Y -= other.m_Y;
    return *this;
  }

  /**
   * @brief Unary negation operator.
   * @return a new vector containing the result of the negation.
   */
  Vector2<T> operator-() const
  {
    return Vector2<T>(-m_X, -m_Y);
  }

  // TODO use least restrictive type for return value
  /**
   * @brief Multiplication by a scalar operator.
   * @tparam U scalar type of the multiplication factor.
   * @param val scalar value to multiply the vector with.
   * @return a new vector containing the result of the product.
   */
  template<class U>
  Vector2<T> operator*(const U& val) const
  {
    static_assert<boost::is_scalar<U>::value> multiplication_by_a_scalar_value;
    unused(multiplication_by_a_scalar_value);

    return Vector2<T>(static_cast<T>(val * m_X), static_cast<T>(val * m_Y));
  }

  /**
   * @brief Multiplication by a scalar and assignment operator.
   * @tparam U scalar type of the multiplication factor.
   * @param val scalar value to multiply the vector with.
   * @return a reference to the present vector.
   */
  template<class U>
  Vector2<T>& operator*=(const U& val)
  {
    static_assert<boost::is_scalar<U>::value> multiplication_by_a_scalar_value;
    unused(multiplication_by_a_scalar_value);

    m_X *= val;
    m_Y *= val;
    return *this;
  }

  /**
   * @brief Computes the dot product (scalar product) of this vector with another one.
   * @param other other vector with which to compute the dot product.
   * @return the dot product of the two vectors.
   */
  T dot(const Vector2<T>& other) const
  {
    return (m_X * other.m_X) + (m_Y * other.m_Y);
  }

  /**
   * @brief Creates a vector perpendicular to this one.
   * @return a new vector perpendicular to this one.
   */
  Vector2<T> perpendicular() const
  {
    return Vector2<T>(-m_Y, m_X);
  }

  /**
   * @brief Equality comparison operator.
   * @param other vector to compare this one to.
   * @return true if both vectors have the same coordinates, false otherwise.
   */
  bool operator==(const Vector2<T>& other) const
  {
    return m_X == other.m_X && m_Y == other.m_Y;
  }

  /**
   * @brief Inequality comparison operator.
   * @param other vector to compare this one to.
   * @return true if the vectors have different coordinates, false otherwise.
   */
  bool operator!=(const Vector2<T>& other) const
  {
    return !(*this == other);
  }

  /**
   * @brief Computes the squared length of this vector (assuming origin at (0, 0)).
   * @return the squared length of this vector.
   */
  double squared_length() const
  {
    return static_cast<double>(dot(*this));
  }

  /**
   * @brief Computes the length of this vector (assuming origin at (0, 0)).
   * @return the length of this vector.
   */
  double length() const
  {
    return sqrt(squared_length());
  }

  /**
   * @brief Computes the squared distance to another vector.
   * @param other vector to compute the squared distance to.
   * @return the squared distance between the vectors.
   */
  double squared_distance_to(const Vector2<T>& other) const
  {
    return (*this - other).squared_length();
  }

  /**
   * @brief Computes the distance to another vector.
   * @param other vector to compute the distance to.
   * @return the distance between the vectors.
   */
  double distance_to(const Vector2<T>& other) const
  {
    return sqrt(squared_distance_to(other));
  }

private:
  T m_X;
  T m_Y;
};

/**
 * @brief Multiplication of a scalar and a vector operator.
 * @tparam T type of values in the vector.
 * @tparam U scalar type of the multiplication factor.
 * @param val scalar value to multiply the vector with.
 * @param vec vector to multiply the scalar value with.
 * @return a new vector containing the result of the product.
 */
template<class T, class U>
Vector2<T> operator*(const U& val, const Vector2<T>& vec)
{
  static_assert<boost::is_scalar<U>::value> multiplication_by_a_scalar_value;
  unused(multiplication_by_a_scalar_value);

  return vec * val;
};

typedef Vector2<int>    Vector2i;
typedef Vector2<float>  Vector2f;
typedef Vector2<double> Vector2d;