#pragma once
#include "Vec.hpp"
#include <algorithm>
//! менеджер векторов движения для всех двигающихся объектов
struct Phys {
  Vec pos = {}; ///< кординаты объекта
  Vec dir = {}; ///< единичный вектор направления
  float deg = 0; ///< угол движения в градусах
  float spd = 0; ///< скорость движения. velocity 
  float rst = 0; ///< сопротивление вычитается из скорости каждый update
  Phys() = default;
  ~Phys() = default;
  Phys(CN(Phys)) = default;
  Phys& operator = (CN(Phys)) = default;
  explicit Phys(CN(Vec) pos_, float deg_ = 0,
  float rst_ = 0, float spd_ = 0) {
    set_pos(pos_);
    set_deg(deg_);
    set_rst(rst_);
    set_spd(spd_);
  }
  void set_pos(CN(Vec) new_pos) { pos = new_pos; }
  //! при установке еденичного вектора угол автоматически настраивается
  void set_dir(CN(Vec) new_dir) {
    #ifdef DEBUG
    cauto len = new_dir.length();
    if (len < 0.5f or len > 1.5f)
      a83::error("Phys.set_dir: dir vec len incorrect: " + a83::n2s(len));
    #endif
    deg = vec2deg(new_dir);
    dir = new_dir;
  }
  //! изменяет направление с нормализацией входного вектора
  void set_dir2(CN(Vec) new_dir) {
    if (new_dir.empty())
      return;
    set_dir(new_dir.get_normalized());
  }
  void set_spd(float new_spd) {
    #ifdef DEBUG
    if (new_spd < 0.f)
      a83::error("Phys.set_spd: speed < 0");
    #endif
    spd = new_spd;
  }
  //! меняет направление в зависимости от угла
  void update_dir() { dir = deg2vec(deg); }
  //! при установке угола еденичный вектор  автоматически настраивается
  void set_deg(float new_deg) {
    a83::clampDeg(new_deg);
    dir = deg2vec(new_deg);
    deg = new_deg;
  }
  //! устанавливает угол, но не менает направление движения
  void set_deg2(float new_deg) {
    a83::clampDeg(new_deg);
    deg = new_deg;
  }
  void set_rst(float new_rst) {
    #ifdef DEBUG
    if (new_rst < 0.f)
      a83::error("Phys.set_rst: rst < 0");
    #endif
    rst = new_rst;
  }
  //! добавка ускорения
  void add_spd(float accel, float dt) { spd = std::max(0.0f, spd + accel * dt); }
  CN(Vec) get_pos() const { return pos; }
  Vec* get_pos_ptr() { return &pos; }
  CN(Vec) get_dir() const { return dir; }
  CN(float) get_deg() const { return deg; }
  CN(float) get_spd() const { return spd; }
  CN(float) get_rst() const { return rst; }
  //! сложение векторов
  void add_vec(CN(Phys) other) {
    this->dir *= this->spd; // вектор полной длины
    Vec other_dir = other.get_dir();
    other_dir *= other.get_spd();
    this->dir += other_dir; // складывание векторов
    this->spd = this->dir.length(); // скорость сложенных векторов
    this->dir.normalize(); // сделать вектро единичным
    this->deg = vec2deg(this->dir); // новый угол
  }
  /** изменяет позицию объекта в зависимости от настроек векторов и
    * меняет скорость движения */
  void move(float dt, float spd_min = 0.0f,
  float spd_max = float_inf) {
    update_speed(dt, spd_min, spd_max);
    pos += dir * (spd * dt);
  }
  //! обновляет только скорость
  void update_speed(float dt, float spd_min = 0.0f, float spd_max = float_inf) {
    spd = std::clamp<float>(spd - (spd * rst * dt), spd_min, spd_max);
  }
  //! поворот объекта без изменения направляения движения
  void rotate(float accel_rot, float dt) {
    cauto _accel_rot = accel_rot * dt;
    if (_accel_rot == 0.0f)
      return;
    deg += _accel_rot;
    a83::clampDeg(deg);
  }
  //! поворачивает самолёт к цели
  void rotate_to(CN(Vec) target, float accel_rot, float dt) {
    #ifdef DEBUG
    IFERROR(dir.empty(), "rotate_to: need set degree for fix dir");
    #endif
    cauto deg_to_target = degvec(target);
    if (deg_to_target < 0)
      set_deg(deg + std::min(std::abs(deg_to_target), accel_rot * dt));
    if (deg_to_target > 0)
      set_deg(deg - std::min(deg_to_target, accel_rot * dt));
  } // rotate_to
  //! получение угла между вектором направления и вектором до цели
  float degvec(CN(Vec) target) const {
    auto target_vec = (pos - target) * -1.0f;
    target_vec.normalize();
    return dir.degvec(target_vec);
  }
  //! true, когда объект отдаляется от hunter
  bool leaving(CN(Phys) hunter) const
    { return std::abs(dir.degvec(hunter.get_dir())) < 90.0f; }
  /** найти точку упреждения движения
   *@param self_pos позиция стреляющего объекта
   *@param self_speed скорость пули стреляющего
   *@param target движущаяся цель, в которую стреляют */
  static Vec predict(CN(Vec) self_pos, float self_speed,
  CN(Phys) target) {
    if (self_speed <= 0.0f)
      return {};
    cnauto target_pos = target.get_pos();
    cauto est_time = distance(self_pos, target_pos) / self_speed;
    return target_pos + (target.get_dir() * target.get_spd() * est_time);
  }
  bool empty() const
    { return !pos.test() && !dir.test() && deg == 0 && spd == 0 && rst == 0; }
  #ifdef DEBUG
  void debug() const {
    a83::log(
      "pos x/y " + a83::n2s(pos.x) + " / " + a83::n2s(pos.y) +
      " dir x/y " + a83::n2s(dir.x) + " / " + a83::n2s(dir.x) +
      " deg " + a83::n2s(deg) +
      " spd " + a83::n2s(spd) +
      " rst " + a83::n2s(rst)
    );
  } // debug
  a83::Str dbg_str(int prec = 6) const {
    if (empty())
      return "none";
    return a83::Str("pos: ") + a83::n2s(pos.x, prec) + ", "
      + a83::n2s(pos.y, prec) + ";" +
      " dir: " + a83::n2s(dir.x, prec) + ", " + a83::n2s(dir.x, prec) + ";" +
      " deg: " + a83::n2s(deg, prec) + ";" +
      " spd: " + a83::n2s(spd, prec) + ";" +
      " rst: " + a83::n2s(rst, prec) + ".";
  } // dbg_str
  #endif
}; // Phys
//! переносит объект в центр с опр-й скоростью
void centering(Vec& v, CN(Vec) center, float speed, float dt) {
  if (v.x < center.x)
    v.x = std::min(v.x + speed * dt, center.x);
  if (v.x > center.x)
    v.x = std::max(v.x - speed * dt, center.x);
  if (v.y < center.y)
    v.y = std::min(v.y + speed * dt, center.y);
  if (v.y > center.y)
    v.y = std::max(v.y - speed * dt, center.y);
} // centering