#pragma once
#include "a83/error.hpp"
#include "image/image.hpp"
#include "common/Vec.hpp"
#include "common/debuger.hpp"
#include "vector.hpp"
template<typename T> T pow2(T x) { return x*x; }
///@file набор геом фигур, с тестом стоклновений
enum class figure_e {
  virt = 0, ///< фигура не задана
  circ, ///< круг
  rect, ///< прямоугольник
  line ///< линия две точки
};
//! интерфейсный класс для фигур
struct Figure {
  virtual figure_e type() const { return figure_e::virt; }
  virtual void print(Image& /*dst*/, CN(Vec) /*pos*/, Color /*col*/) const {}
  //! ret true if detected collision
  virtual bool collide(CN(Vec) offset1, CN(Figure) figure2, CN(Vec) offset2) const {
    (void)figure2;
    (void)offset1;
    (void)offset2;
    return false;
  };
  virtual void rotate(float deg) { (void)deg; }
  virtual void add_offset(CN(Vec) offset) { (void)offset; }
  virtual ~Figure() = default;
};
//! верктор ссылок на фигуры
using vp_figure_t = lni::vector<Figure*>;
namespace { vp_figure_t vp_figure_empty = {}; }
// trick for correct dynamic cast in collide func:
struct pre_rect: Figure {
  float sx = 0, sy = 0, mx = 0, my = 0;
  constexpr pre_rect(float _sx, float _sy, float _mx, float _my)
    : sx(_sx), sy(_sy), mx(_mx), my(_my) {}
};
struct pre_line: Figure {
  Vec a = {}, b = {};
  constexpr pre_line(CN(Vec) _a, CN(Vec) _b): a(_a), b(_b) {}
};
struct pre_circle: Figure {
  Vec pos = {}; //!< offset
  float radius = 0.0f;
  constexpr pre_circle(CN(Vec) _pos, float _radius): pos(_pos), radius(_radius) {}
};
//! проверка столкновения окружностей
bool collide_circle(pre_circle *c1, CN(Vec) offset1,
pre_circle *c2, CN(Vec) offset2) {
  auto x1 = offset1.x + c1->pos.x;
  auto y1 = offset1.y + c1->pos.y;
  auto x2 = offset2.x + c2->pos.x;
  auto y2 = offset2.y + c2->pos.y;
  // оптимизированный вариант: квадрат дистанций
  return pow2(x1 - x2) + pow2(y1 - y2) < pow2(c1->radius + c2->radius);
}
//! проверка столкновения окружностей с прямоугольниками
bool collide_circle_rect(pre_circle *circ, CN(Vec) offset1,
pre_rect *rect, CN(Vec) offset2) {
  auto cx = offset1.x + circ->pos.x;
  auto cy = offset1.y + circ->pos.y;
  auto rx = offset2.x + rect->sx;
  auto ry = offset2.y + rect->sy;
  cnauto rw = rect->mx;
  cnauto rh = rect->my;
  float testX = cx;
  float testY = cy;
  // which edge is closest?
  if (cx < rx)           testX = rx;        // test left edge
  else if (cx > rx + rw) testX = rx + rw;   // right edge
  if (cy < ry)           testY = ry;        // top edge
  else if (cy > ry + rh) testY = ry + rh;   // bottom edge
  // get distance from closest edges
  float distX = cx - testX;
  float distY = cy - testY;
  float dist = pow2(distX) + pow2(distY);
  // if the distance is less than the radius, collision!
  return dist <= pow2(circ->radius);
}
//! определение столкновеня с прямоугольников
bool collide_rect(pre_rect *rect1, CN(Vec) offset1,
pre_rect *rect2, CN(Vec) offset2) {
  return
    offset1.x + rect1->sx             < offset2.x + rect2->sx + rect2->mx &&
    offset1.x + rect1->sx + rect1->mx > offset1.x + rect2->sx &&
    offset1.y + rect1->sy             < offset1.y + rect2->sy + rect2->my &&
    offset1.y + rect1->sy + rect1->my > offset1.y + rect2->sy;
}
bool collide_circle_line(pre_circle *circ, CN(Vec) offset1,
pre_line *line, CN(Vec) offset2) {
  auto x1 = line->a.x + offset2.x;
  auto y1 = line->a.y + offset2.y;
  auto x2 = line->b.x + offset2.x;
  auto y2 = line->b.y + offset2.y;
  auto xc = offset1.x + circ->pos.x;
  auto yc = offset1.y + circ->pos.y;
  auto dx_2_1 = x2 - x1;
  auto dy_2_1 = y2 - y1;
  auto d = 0.0f;
  auto t = ((yc - y1) * dy_2_1 + (xc - x1) * dx_2_1) /
    (pow2(dy_2_1) + pow2(dx_2_1));
  if (0.f <= t and t <= 1.f) {
    auto xd = x1 + t * dx_2_1;
    auto yd = y1 + t * dy_2_1;
    d = std::sqrt(pow2(xd - xc) + pow2(yd - yc));
    return d <= circ->radius;
  } else {
    d = std::sqrt(pow2(xc - x1) + pow2(yc - y1));
    if (d <= circ->radius)
      return true;
    else {
      d = std::sqrt(pow2(xc - x2) + pow2(yc - y2));
      if (d <= circ->radius)
        return true;
      else
        return false;
    }
  }
  return false;
}
//! определение пересечения прямых
bool collide_line(pre_line *line1, CN(Vec) offset1,
pre_line *line2, CN(Vec) offset2) {
  auto a_X = offset1.x + line1->a.x;
  auto a_Y = offset1.y + line1->a.y;
  auto b_X = offset1.x + line1->b.x;
  auto b_Y = offset1.y + line1->b.y;
  auto c_X = offset2.x + line2->a.x;
  auto c_Y = offset2.y + line2->a.y;
  auto d_X = offset2.x + line2->b.x;
  auto d_Y = offset2.y + line2->b.y;
  auto denominator = ((b_X - a_X) * (d_Y - c_Y)) - ((b_Y - a_Y) * (d_X - c_X));
  auto numerator1 = ((a_Y - c_Y) * (d_X - c_X)) - ((a_X - c_X) * (d_Y - c_Y));
  auto numerator2 = ((a_Y - c_Y) * (b_X - a_X)) - ((a_X - c_X) * (b_Y - a_Y));
  // Detect coincident lines
  if (denominator == 0 and line1 == line2 and offset1 == offset2)
    return numerator1 == 0 && numerator2 == 0;
  auto r = numerator1 / denominator;
  auto s = numerator2 / denominator;
  return (r >= 0 && r <= 1) && (s >= 0 && s <= 1);
}
//! detect rect & point collision
bool collide_rect_point(pre_rect *rect, CN(Vec) offset1,
CN(Vec) point, CN(Vec) offset2) {
  return in_bound(
    offset2.x + point.x,
    offset1.x + rect->sx,
    offset1.x + rect->sx + rect->mx,
    offset2.y + point.y,
    offset1.y + rect->sy,
    offset1.y + rect->sy + rect->my);
}
//! определение пересечения прямоугольника линией
bool collide_rect_line(pre_rect *rect, CN(Vec) offset1,
pre_line *line, CN(Vec) offset2) {
// поиск пересечений линий прямоугольника:
  #define CHECK(x1, y1, x2, y2) { \
    pre_line _line({x1, y1}, {x2, y2}); \
    if (collide_line(&_line, offset1, line, offset2)) \
      return true; \
  }
  CHECK(rect->sx, rect->sy, rect->sx + rect->mx, rect->sy)
  CHECK(rect->sx, rect->sy, rect->sx, rect->sy + rect->my)
  CHECK(rect->sx, rect->sy + rect->my, rect->sx + rect->mx, rect->sy)
  CHECK(rect->sx + rect->mx, rect->sy, rect->sx, rect->sy + rect->my)
  #undef CHECK
// если точки внутри прямоугольника:
  if (collide_rect_point(rect, offset1, {line->a.x, line->a.y}, offset2)
  || collide_rect_point(rect, offset1, {line->b.x, line->b.y}, offset2))
    return true;
  return false;
}
//! круглик
struct Circle: public pre_circle {
  Circle(const Vec& _pos, float _radius): pre_circle(_pos, _radius) {}
  figure_e type() const override { return figure_e::circ; }
  void add_offset(CN(Vec) offset) override { this->pos += offset; }
  void print(Image& dst, const Vec& _pos, Color col) const override {
    if ( !dst.check()) return;
    int x = radius;
    int y = 0;
    int err = 0;
    while (x >= y) {
      dst.set(_pos.x + pos.x + x, _pos.y + pos.y + y, col);
      dst.set(_pos.x + pos.x + y, _pos.y + pos.y + x, col);
      dst.set(_pos.x + pos.x - y, _pos.y + pos.y + x, col);
      dst.set(_pos.x + pos.x - x, _pos.y + pos.y + y, col);
      dst.set(_pos.x + pos.x - x, _pos.y + pos.y - y, col);
      dst.set(_pos.x + pos.x - y, _pos.y + pos.y - x, col);
      dst.set(_pos.x + pos.x + y, _pos.y + pos.y - x, col);
      dst.set(_pos.x + pos.x + x, _pos.y + pos.y - y, col);
      if (err <= 0) {
        ++y;
        err += 2 * y + 1;
      }
      if (err > 0) {
        --x;
        err -= 2 * x + 1;
      }
    } // while
  }
  bool collide(CN(Vec) circ_offset, CN(Figure) fig, CN(Vec) fig_offset) const override {
    switch (fig.type()) {
      default:
      case figure_e::virt:
        return false;
      // опр-е столкн-я с кругом
      case figure_e::circ: {
        auto circ1 = dynamic_cast<const pre_circle&>(*this);
        auto circ2 = dynamic_cast<const pre_circle&>(fig);
        return collide_circle(&circ1, circ_offset, &circ2, fig_offset);
      } // case circ
      // опр-е столкн-я с прямоуг-м
      case figure_e::rect: {
        auto circ = dynamic_cast<const pre_circle&>(*this);
        auto rect = dynamic_cast<const pre_rect&>(fig);
        return collide_circle_rect(&circ, circ_offset, &rect, fig_offset);
      } // case rect
      // опр-е столкн-я с прямой
      case figure_e::line: {
        auto circ = dynamic_cast<const pre_circle&>(*this);
        auto line = dynamic_cast<const pre_line&>(fig);
        return collide_circle_line(&circ, circ_offset, &line, fig_offset);
      } // line
    } // switch type
  }
  void rotate(float deg) override { pos = pos.get_rotate_deg({}, deg); }
};
//! прямоугольник
struct Rect: pre_rect {
  Rect(float _sx, float _sy, float _mx, float _my)
    : pre_rect(_sx, _sy, _mx, _my) {}
  figure_e type() const override { return figure_e::rect; }
  void add_offset(CN(Vec) offset) override
    { this->sx += offset.x; this->sy += offset.y; }
  void print(Image& dst, const Vec& pos, Color col) const override {
    if ( !dst.check()) return;
    if (mx <= 0.0f || my <= 0.0f) return;
    cauto ex = sx + mx;
    cauto ey = sy + my;
    // рисование линий по два раза:
    for (int x = sx; x < ex; ++x) {
      dst.set(pos.x + x, pos.y + sy, col);
      dst.set(pos.x + x, pos.y + ey, col);
    }
    for (int y = sy; y < ey; ++y) {
      dst.set(pos.x + sx, pos.y + y, col);
      dst.set(pos.x + ex, pos.y + y, col);
    }
    dst.set(pos.x + ex, pos.y + ey, col); // последняя точка справа снизу
  }
  bool collide(CN(Vec) rect_offset, CN(Figure) fig, CN(Vec) fig_offset) const override {
    switch (fig.type()) {
      default:
      case figure_e::virt:
        return false;
      // столкновение с кругом
      case figure_e::circ: {
        auto rect = dynamic_cast<const pre_rect&>(*this);
        auto circ = dynamic_cast<const pre_circle&>(fig);
        return collide_circle_rect(&circ, fig_offset, &rect, rect_offset);
      } // case circ
      // определение столкновеня с прямоугольником
      case figure_e::rect: {
        auto rect1 = dynamic_cast<const pre_rect&>(*this);
        auto rect2 = dynamic_cast<const pre_rect&>(fig);
        return collide_rect(&rect1, rect_offset, &rect2, fig_offset);
      } // case rect
      // опр-е столкн-я с прямой
      case figure_e::line: {
        auto rect = dynamic_cast<const pre_rect&>(*this);
        auto line = dynamic_cast<const pre_line&>(fig);
        return collide_rect_line(&rect, rect_offset, &line, fig_offset);
      } // line
    } // switch type
  } // check collide
  void rotate(float deg) override {
    // крутится только позиция прямоугольника:
    Vec pos(sx, sy);
    pos = pos.get_rotate_deg({}, deg);
    sx = pos.x;
    sy = pos.y;
  }
};
//! линия из двух точек
struct Line: pre_line {
  Line(CN(Vec) _a, CN(Vec) _b): pre_line(_a, _b) {}
  figure_e type() const override { return figure_e::line; }
  void add_offset(CN(Vec) offset) override
    { this->a += offset; this->b += offset; }
  void print(Image& dst, CN(Vec) _pos, Color col) const override
    { dst.draw_line(_pos.x + a.x, _pos.y + a.y, _pos.x + b.x, _pos.y + b.y, col); }
  //! ret true if detected collision
  bool collide(CN(Vec) line_offset, CN(Figure) fig, CN(Vec) fig_offset)
  const override {
    switch (fig.type()) {
      default:
      case figure_e::virt:
        return false;
      // столкновение с кругом
      case figure_e::circ: {
        auto line = dynamic_cast<const pre_line&>(*this);
        auto circ = dynamic_cast<const pre_circle&>(fig);
        return collide_circle_line(&circ, fig_offset, &line, line_offset);
      } // case circ
      // определение столкновеня с прямоугольником
      case figure_e::rect: {
        auto line = dynamic_cast<const pre_line&>(*this);
        auto rect = dynamic_cast<const pre_rect&>(fig);
        return collide_rect_line(&rect, fig_offset, &line, line_offset);
      } // case rect
      // опр-е столкн-я с прямой
      case figure_e::line: {
        auto line1 = dynamic_cast<const pre_line&>(*this);
        auto line2 = dynamic_cast<const pre_line&>(fig);
        return collide_line(&line1, line_offset, &line2, fig_offset);
      } // line
    } // witch
  };
  void rotate(float deg) override {
    a = a.get_rotate_deg({}, deg);
    b = b.get_rotate_deg({}, deg);
  }
};
/** получает массив хитбоксов из текстовой строки. ret 0 if no hits
* @details управляющие символы:
* C круг
* R прямоугольник
* L прямая
* ; разделитель */
bool parse_hits(vp_figure_t& vp_figure, CN(a83::Str) raw) {
  using namespace a83;
  if (raw.empty())
    return false;
  bool ret = false;
  auto figures = a83::split_str(raw, ';');
  for (cnauto figure: figures) {
    auto vals = a83::split_str(figure, ' ');
    IFERROR(vals.empty(), "parse_hits: empty val");
    if (a83::toupper(vals[0]) == "C") {
      IFERROR(vals.size() < 4, "parse_hits: for circle need more params");
      float sx = s2n<float>(vals[1]);
      float sy = s2n<float>(vals[2]);
      float radius = s2n<float>(vals[3]);
      vp_figure.push_back(new Circle({sx, sy}, radius));
      ret = true;
    }
    if (a83::toupper(vals[0]) == "R") {
      IFERROR(vals.size() < 5, "parse_hits: for rect need more params");
      float sx = s2n<float>(vals[1]);
      float sy = s2n<float>(vals[2]);
      float mx = s2n<float>(vals[3]);
      float my = s2n<float>(vals[4]);
      vp_figure.push_back(new Rect(sx,sy, mx,my));
      ret = true;
    }
    if (a83::toupper(vals[0]) == "L") {
      IFERROR(vals.size() < 5, "parse_hits: for line need more params");
      float ax = s2n<float>(vals[1]);
      float ay = s2n<float>(vals[2]);
      float bx = s2n<float>(vals[3]);
      float by = s2n<float>(vals[4]);
      vp_figure.push_back(new Line({ax,ay}, {bx,by}));
      ret = true;
    }
  }
  return ret;
}
//! универсальное копирование фигуры
Figure* make_figure(CN(Figure) fig) {
  switch (fig.type()) {
    default:
    case figure_e::virt: return nullptr;
    case figure_e::circ: {
      auto circ = dynamic_cast<const Circle&>(fig);
      return new Circle(circ);
    }
    case figure_e::rect: {
      auto rect = dynamic_cast<const Rect&>(fig);
      return new Rect(rect);
    }
    case figure_e::line: {
      auto line = dynamic_cast<const Line&>(fig);
      return new Line(line);
    }
  } // switch fig type
  return nullptr;
}
//! вращение хитбоксов вокруг точки
void rotate(CN(vp_figure_t) figures, vp_figure_t& dst, float degree) {
  for (cnauto fig: figures) {
    auto fig_ptr = make_figure(*fig);
    if ( !fig_ptr)
      continue;
    dst.push_back(fig_ptr);
    dst.back()->rotate(degree);
  }
} // rotate figures
//! ret 1 if point b in circle a
bool collide_circ_point(CN(Vec) pos_a, float r_a, CN(Vec) pos_b) {
  return pow2(pos_a.x - pos_b.x) + pow2(pos_a.y - pos_b.y) < pow2(r_a);
}