#pragma once
#include "a83/mat.hpp"
#include "a83/macro.hpp"
#ifdef DEBUG
#include "a83/string-util.hpp"
#endif
struct Vec;
Vec deg2vec(float deg);
//! 2D вектор
struct Vec { 
  float x{0}, y{0};
  Vec() = default;
  constexpr Vec(float nx, float ny): x(nx), y(ny) {}
  bool operator ==(CN(Vec) val) const { return x == val.x and y == val.y; }
  bool operator !=(CN(Vec) val) const { return x != val.x and y != val.y; }
  constexpr void operator -=(CN(Vec) val) { x -= val.x; y -= val.y; }
  void operator +=(CN(Vec) val) { x += val.x; y += val.y; }
  void operator *=(CN(Vec) val) { x *= val.x; y *= val.y; }
  bool test() const { return x != 0 or y != 0; }
  bool empty() const { return x == 0 && y == 0; }
  template<typename T> void operator +=(T val) { x += val; y += val; }
  template<typename T> void operator -=(T val) { x -= val; y -= val; }
  template<typename T> void operator *=(T val) { x *= val; y *= val; }
  template<typename T> void operator /=(T val) { x /= val; y /= val; }
  template<typename T> Vec operator + (T val) const { return Vec(x + val, y + val); }
  template<typename T> Vec operator - (T val) const { return Vec(x - val, y - val); }
  template<typename T> Vec operator * (T val) const { return Vec(x * val, y * val); }
  Vec operator + (CN(Vec) val) const { return Vec(x + val.x, y + val.y); }
  Vec operator - (CN(Vec) val) const { return Vec(x - val.x, y - val.y); }
  Vec operator * (CN(Vec) val) const { return Vec(x * val.x, y * val.y); }
  Vec operator -() const { return *this * -1.0f; }
  Vec get_rotate_rad(CN(Vec) center, float radian) const;
  Vec get_rotate_deg(CN(Vec) center, float degree) const;
  float length() const { return a83::sqrt((x * x) + (y * y)); }
  void normalize() { 
    auto len = length();
    if (len == 0.0f)
      return;
    x /= len;
    y /= len;
  }
  Vec get_normalized() const {
    auto len = length();
    if (len == 0.0f)
      return Vec();
    return Vec(x / len, y / len);
  }
  //! угол между вектором в радианах
  float radvec(CN(Vec) other) const {
    cauto dot = x * other.x + y * other.y; // dot product between [x1, y1] and [x2, y2]
    cauto det = x * other.y - y * other.x; // determinant
    return atan2(det, dot); // atan2(y, x) or atan2(sin, cos)
  }
  //! угол между вектором
  float degvec(CN(Vec) other) const{ return a83::rad2deg(radvec(other)); }
  //! получить случайную точку вокруг pos в радиусе r
  static Vec make_rand_by_radius(CN(Vec) pos, float r) {
    return pos + (deg2vec(a83::frand() * 360.0f) * (a83::frand() * r));
  }
  #ifdef DEBUG
  a83::Str to_str() const
    { return a83::Str("{") + a83::n2s(x) + ", " + a83::n2s(y) + "}"; }
  #endif
}; // Vec
//! единичный вектор направления по углу
Vec deg2vec(float deg) {
  return { a83::cos(a83::deg2rad(-deg)), a83::sin(a83::deg2rad(-deg)) };
}
//! единичный вектор направления по углу
float vec2deg(Vec vec) {
  using namespace a83;
  if (vec.x == 0.0f) // special cases
    return (vec.y > 0.0f) ? 90.0f : (vec.y == 0.0f) ? 0.0f : 270.0f;
  else if (vec.y == 0.0f) // special cases
    return (vec.x >= 0.0f) ? 0.0f : 180.0f;
  float ret = rad2deg(std::atan(vec.y / vec.x));
  if (vec.x < 0.0f && vec.y < 0.0f) // quadrant Ⅲ
    ret = 180.0f + ret;
  else if (vec.x < 0.0f) // quadrant Ⅱ
    ret = 180.0f + ret; // it actually substracts
  else if (vec.y < 0.0f) // quadrant Ⅳ
    ret = 270.0f + (90.0f + ret); // it actually substracts
  return ret;
}
//! случайный угол
float degrand() { return a83::frand() * 360.0f; }
//! вращает this точку вокруг точки center с углом в радианах
Vec Vec::get_rotate_rad(CN(Vec) center, float radian) const {
  float out_x = center.x + (x - center.x) * a83::cos(radian) -
    (y - center.y) * a83::sin(radian);
  float out_y = center.y + (x - center.x) * a83::sin(radian) +
    (y - center.y) * a83::cos(radian);
  return {out_x, out_y};
}
//! вращает this точку вокруг точки center с углом в градусах
Vec Vec::get_rotate_deg(CN(Vec) center, float degree) const {
  auto radian = a83::deg2rad(degree);
  float out_x = center.x + (x - center.x) * a83::cos(radian) -
    (y - center.y) * a83::sin(radian);
  float out_y = center.y + (x - center.x) * a83::sin(radian) +
    (y - center.y) * a83::cos(radian);
  return {out_x, out_y};
}
float distance(Vec a, CN(Vec) b) {
  a -= b;
  return a.length();
}
constexpr float fast_distance(Vec a, CN(Vec) b) {
  a -= b;
  return std::abs(a.x) + std::abs(a.y);
}
//! дистанция между точками. выбирается самая большая из кординат
constexpr float max_cord_distance(Vec a, CN(Vec) b) {
  a -= b;
  return std::abs(std::max(a.x, a.y));
}