#pragma once
#include "game/game-data.hpp"
#include "game/game-archive.hpp"
#include "common/common.hpp"
#include "common/debuger.hpp"
#include "common/Phys.hpp"
#include "common/bank-info.hpp"
#include "common/bank-anim.hpp"
#include "common/Haze.hpp"
#include "common/Light.hpp"
#include "common/Counter.hpp"
#include "enity-type.hpp"
#include "enity-flags.hpp"
#include "enity-id.hpp"
#include "enity-info.hpp"
#include "enity-data.hpp"
#include "enity-util.hpp"
#include "vector.hpp"
#include "smoke.hpp"
#include "expl.hpp"
namespace {
  //! глобальное смещение двигающее все объекты вокруг игрока
  Phys g_offset = {};
  uint current_uid = 0; ///< каждому объекту достаётся уникальный номер
}
//! базовый класс для всех игровых объектов
class Enity {
public:
  Smoke<Enity> smoke = {}; ///< эмитер дымового следа
  Phys phys = {}; ///< физический контекст объекта
  Haze haze = {}; ///< источник искажения воздуха
  Light light = {}; ///< источник света
  _enity_::data64_t data0 = {}; ///< память под параметры в enity-func.cpp
  Counter frame_timer = {}; ///< таймер анимации
  using func_t = bool (Enity::*)(float);
  //! доп функция обработки физики. ret 0, если объект умер
  func_t pf0 = {}, pf1 = {};
  const Enity_info* p_info = 0; ///< ссыль на данные о объекте
  const Enity* master = 0; ///< создатель объекта
  uint flags = EF_DEFAULT; ///< see enity-flags.hpp
  uint id = 0; ///< see enity-id.hpp
  uint uid = 0; ///< уникальный номкер
  BM blend_mode = BM::past;
  index_t frame_num = 0; ///< текущий номер кадра в bank_anim
  Enity_t type = Enity_t::base; // тип объекта
  bool is_collidable = false; ///< возмодность сталкиваться
  Enity() = default;
  virtual ~Enity() = default;
  Enity& operator =(CN(Enity)) = default;
  virtual void clear() { *this = {}; }
  //! отобразить объект. ret 0, если не надо продолжать отрисовку
  virtual bool draw(Image& dst, CN(Vec) offset) const {
    #ifdef DEBUG
    if (is_live())
      draw_debug(debuger::dbg_layer, offset);
    #endif
    if (flags & EF_NO_DRAW)
      return false;
    if ( !check_draw_dist())
      return false;
    //! нарисовать анимацию, если есть
    if (p_info) {
      if (auto anim = p_info->p_anim; anim) {
        anim->print(offset + phys.pos, dst, get_frame_num(), get_deg(),
          blend_mode);
      }
    } // if live & info
    draw_post(dst, offset);
    return true;
  }
  //! обновить состояние объекта. ret 0, если не надо продолжать обнову
  virtual bool update(float dt, uint update_idx) {
    if (flags & EF_NO_UPDATE) // не обновляться
      return false;
    if (flags & EF_KILL_NOW) { // уничтожиться сейчас
      kill();
      return false;
    }
    if ( !update_physic(dt, update_idx))
      return false;
    if ( !distance_fix())
      return false;
    haze.update(dt);
    light.update(dt);
    update_smoke();
    frame_timer.update(dt); // обнова таймера анимации
    if (frame_timer()) { // если надо сменить кадр
      flags |= EF_END_FRAME;
      if (flags & EF_KILL_TIMER) { // сдохнуть по EF_KILL_TIMER | EF_KILL_FRAME
        kill();
        return false;
      }
      frame_timer.reset();
      if ( !next_frame())
        return false;
    }
    return true;
  } // updste
  //! рисует только маску в уменьшеном виде
  void draw_mask_resized(int x0, int y0,
  float scale, Image& dst, CN(Color) col) {
    if (p_info and p_info->p_anim) {
      cnauto p_spr = p_info->p_anim->get_sprite(0, get_deg());
      if ( !p_spr)
        return;
      cnauto mask = p_spr->mask;
      const int dst_x = mask.getX() * scale;
      const int dst_y = mask.getY() * scale;
      const float ix = mask.X / float(dst_x);
      const float iy = mask.Y / float(dst_y);
      dst.set(x0, y0, col); // точку, если объект слишком мелкий
      x0 -= dst_x / 2; // центрирование картинки
      y0 -= dst_y / 2;
      FOR (y, dst_y)
      FOR (x, dst_x) {
        auto p_dst_col = dst.get_ptr(x0 + x, y0 + y);
        nauto mask_col = mask.fast_get(std::ceil(x * ix), std::ceil(y * iy));
        if (p_dst_col and (~mask_col).val)
          *p_dst_col = col;
      } // FOR y, x
    } // if anim
  } // draw_mask_resized
  //! 1, если объект жив и готов к использованию
  virtual void kill() { flags &= ~EF_LIVE; }
  bool is_live() const { return flags & EF_LIVE; }
  bool is_dead() const { return !is_live(); }
  void set_delay(float t) { frame_timer.set_count(t); }
  //! переключиться на следующий кадр. ret 0 если сдохли
  bool next_frame() {
    ++frame_num;
    if (frame_num >= max_frame()) { // анимация по кругу
      frame_num = 0;
      flags |= EF_END_ANIM;
      if (flags & EF_KILL_ANIM) { // умереть по окончании анимации
        kill();
        return false;
      }
    }
    return true;
  } // next_frame
  //! пропускает cnt кадров
  bool frame_skip(uint cnt) {
    bool ret = false;
    FOR (i, cnt)
      ret |= next_frame();
    return ret;
  }
  virtual CN(vp_figure_t) get_hits() const {
    if (p_info && p_info->p_anim)
      return p_info->p_anim->get_hits(frame_num, phys.deg);
    return vp_figure_empty;
  } // get_hits
  //! частица не исчезает по обычным причинам
  virtual void set_infinity() { flags &= ~EF_KILL_ANIM; }
  virtual void reset() {}
  virtual void init() {}
  // see enity-func.cpp:
  bool kill_by_timer(float dt); ///< уничтожить объект, когда счётчик data0.f0 <= 0
  bool kill_by_collide(float dt); ///< if EF_COLLIDED, killself
  bool rand_spin(float dt); ///< случайное вращение со скоростью data0.f1
  bool frag(float dt); ///< для крутящихся осколков исчезающих при остановке. data0.f0
  bool homing_plr(float dt); ///< приследование игрока. data0.f0 - rot speed
protected:
  //! получить номер текущего кадра с учётом флагов
  index_t get_frame_num() const {
    cauto max_frame_ = max_frame();
    if (max_frame_ == 0)
      return 0;
    if (flags & EF_R_FRAME_ANY) // true rand
      return a83::irand() % max_frame_;
    else if (flags & EF_R_FRAME) // rand every frame
      return a83::doom_table(uid + frame_num) % max_frame_;
    return frame_num;
  } // get_frame_num
  //! получить угол объекта с учётом флагов
  float get_deg() const {
    if (flags & EF_R_DEG_ANY) // true rand
      return rand_deg();
    else if (flags & EF_R_DEG) // rand every frame
      return std::fmod(a83::doom_table(uid + frame_num) * 2, 360.0f);
    return phys.deg;
  }
  //! узнать число кадров в анимации
  size_t max_frame() const {
    if (p_info && p_info->p_anim)
      return p_info->p_anim->sprite_store.size();
    return 0;
  }
  #ifdef DEBUG
  virtual void draw_debug(Image& dst, CN(Vec) offset) const {
    (void)dst;
    // нарисовать позицию объекта
    if (debuger::status & DBG_POS) {
      cauto draw_pos = offset + phys.pos;
      Game::font->print(debuger::dbg_layer, draw_pos.x, draw_pos.y,
        n2s<utf32>(phys.pos.x, 0)
        + U", " + n2s<utf32>(phys.pos.y, 0));
      // красный крестик
      cauto ix = std::floor(draw_pos.x);
      cauto iy = std::floor(draw_pos.y);
      for (int y = iy - 2; y < iy + 3; ++y)
        debuger::dbg_layer.set(ix, y, Color::RED);
      for (int x = ix - 2; x < ix + 3; ++x)
        debuger::dbg_layer.set(x, iy, Color::RED);
    }
  } // draw_debug
  #endif
  //! постэффекты
  void draw_post(Image& dst, CN(Vec) offset) const {
    cauto draw_pos = offset + phys.pos;
    if (Game::status & GF_DRAW_LIGHT)
      light.print(dst, draw_pos, phys.deg);
    if (Game::status & GF_DRAW_HAZE)
      haze.print(dst, draw_pos);
  }
  //! двигает объект с проверкой столкновений
  virtual bool update_physic(float dt, uint update_idx);
  bool check_hit(uint update_idx); ///< проверят столкновение со всем вокруг. ret 0 if killed
  //! двигается с шагом проверки хитбокса и проверяет столкновения
  bool checkmove(CN(Vec) dir, float spd, float check_dist, float dt,
    uint update_idx);
  virtual void sub_hp(uint val) { (void)val; } ///< отнимает жизни
  /** получение урона.
   * @param other_flags флаги нападающего для
   * определения кому можно дамажить */
  void damage(uint dmg, CN(Enity) other) {
    if (flags & EF_NO_KILL)
      return;
    bool damage_it = false;
    switch (type) {
      case Enity_t::boss:
      case Enity_t::enemy: if (other.flags & EF_DMG_ENEMY) { damage_it = true; } break;
      case Enity_t::player: if (other.flags & EF_DMG_PLAYER) { damage_it = true; } break;
      case Enity_t::bullet: if (other.flags & EF_DMG_BULLET) { damage_it = true; } break;
      default: a83::error("Enity.damage: add imp for new type");
    }
    if (damage_it)
      sub_hp(dmg);
  } // damage
  //! определяет что делать, если частица за пределами опр-й дистанции
  bool distance_fix() {
    switch (type) {
      case Enity_t::boss:
      case Enity_t::enemy: {
        if (distance(g_offset.pos, phys.pos)
        >= Game::SHIP_FIX_DIST)
          bound_fix();
        break;
      }
      case Enity_t::bonus: {
        a83::error("Enity.distance_fix: add impl 4 bonus");
        break;
      }
      case Enity_t::bullet: {
        if (distance(g_offset.pos, phys.pos)
        >= Game::BULLET_KILL_DIST) {
          kill();
          return false;
        }
        break;
      }
      case Enity_t::particle: {
        if (distance(g_offset.pos, phys.pos)
        >= Game::PARTICLE_KILL_DIST) {
          kill();
          return false;
        }
        break;
      }
      default: break;
    }
    return true;
  } // distance_fix
  //! ret 0, если объект далеко за экраном
  virtual bool check_draw_dist() const {
    if (fast_distance(g_offset.pos, phys.pos)
    >= Game::DRAW_DIST)
      return false;
    return true;
  } // check_draw_dist
  virtual void update_smoke() { smoke(phys); }
  //! не улетать за барьер
  virtual void bound_fix() {
    if (phys.pos.x < -Game::SHIP_FIX_DIST) {
      phys.pos.x = -Game::SHIP_FIX_DIST;
      flags |= EF_BOUND;
    }
    if (phys.pos.x > Game::SHIP_FIX_DIST) {
      phys.pos.x = Game::SHIP_FIX_DIST;
      flags |= EF_BOUND;
    }
    if (phys.pos.y < -Game::SHIP_FIX_DIST) {
      phys.pos.y = -Game::SHIP_FIX_DIST;
      flags |= EF_BOUND;
    }
    if (phys.pos.y > Game::SHIP_FIX_DIST) {
      phys.pos.y = Game::SHIP_FIX_DIST;
      flags |= EF_BOUND;
    }
  } // bound_fix
}; // Enity
using vp_enity_t = lni::vector<Enity*>;
namespace {
  vp_enity_t vp_enity = {}; ///< хранилище всех объектов в игре
  omp::mutex mu_vp_enity = {};
  Vec camera = {}; ///< камера относительно игрока
}
#include "enity-physic.cpp"
#include "enity-all.hpp"
#include "enity-util.cpp"
#include "smoke.cpp"
#include "expl.cpp"
#include "enity-func.cpp"