#pragma once
#include "enity-util.hpp"
#include "particle/prt.hpp"
#include "bullet/blt.hpp"
#include "player/plr.hpp"
#include "player/warning.hpp"
#include <bitset>
//! отметить предупреждения
static void draw_warn(Enity *p, Image& dst) {
  if (p->flags & EF_WARN_BLT)
    warning.draw_blt(p->phys.pos, camera, dst);
  if (p->flags & EF_WARN_ENE)
    warning.draw_ene(p->phys.pos, camera, dst);
  if (p->flags & EF_WARN_BNS)
    a83::error("draw_warn: need impl for EF_WARN_BNS");
}
//! обновляет все объекты
void update_enity(float dt) {
  FOR (update_idx, vp_enity.size()) {
    nauto enity = vp_enity[update_idx];
    if (enity->is_live())
      enity->update(dt, update_idx);
  }
} // update_enity
//! отрисовать все игровые объекты
void draw_enity(Image& dst) {
  // нижний слой
  for (cnauto enity: vp_enity) {
    if (enity->is_dead())
      continue;
    draw_warn(enity, dst);
    if ( !(enity->flags & EF_LRU))
      enity->draw(dst, camera);
  }
  // верхний слой
  for (cnauto enity: vp_enity)
    if (enity->is_live() && enity->flags & EF_LRU)
      enity->draw(dst, camera);
} // draw_enity
/** вставляет новые объекты в банк двигаясь по всем
элементам по кругу (с затиранием существующих) */
template <class T, Enity_t type, uint flags, auto& idx>
T* add_enity(uint id) {
  nauto p = vp_enity[idx()];
  #ifdef DEBUG
  IFERROR( p->type != type, "add_enity: p->type != type, idx: "
    + a83::n2s(idx()) + ", p->type: " + a83::n2s(int(p->type))
    + ", type: " + a83::n2s(int(type)) );
  #endif
  ++idx;
  // принудительно вставить на занятое место
  if (p->is_live()) 
    p->kill();
  p->clear();
  p->p_info = dcast(CP(Enity_info), mp_info[id]);
  #ifdef DEBUG
  if ( !p->p_info && id != ENI_NONE)
    a83::error("add_enity: no finded info for " + a83::n2s(id));
  #endif
  p->flags = flags;
  p->id = id;
  p->uid = current_uid;
  ++current_uid;
  p->init();
  p->is_collidable = check_collidable(type);
  p->type = type;
  return dcast(T*, p);
} // add_enity
Plr* add_plr(uint id) {
  Plr* p = nullptr;
  switch (id) {
    case PLR_DARK: p = new Plr_dark(); break;
    case PLR_WHITE: a83::error("add_plr: add impl PLR_WHITE"); break;
    case PLR_RED: a83::error("add_plr: add impl PLR_RED"); break;
    default: a83::log("add_plr: bad id " + a83::n2s(id)); return nullptr;
  }
  p->phys.set_deg(0);
  p->p_info = dcast(CP(Enity_info), mp_info[id]);
  #ifdef DEBUG
  if ( !p->p_info && id != ENI_NONE)
    a83::error("add_plr: no finded info for " + a83::n2s(id));
  #endif
  p->flags = EF_LIVE | EF_DMG_BULLET | EF_DMG_ENEMY | EF_NO_OFFSET;
  p->id = id;
  p->uid = current_uid;
  ++current_uid;
  p->is_collidable = check_collidable(Enity_t::player);
  p->type = Enity_t::player;
  p->expl = expl_bank::plr;
  omp::lock_guard lock(mu_vp_enity);
  vp_enity.push_back(p);
  p->init();
  return p;
} // add_plr
Prt* add_prt(uint id) {
  #ifdef NO_LOAD_PARTICLE
  return nullptr;
  #endif
  return add_enity<Prt, Enity_t::particle,
    EF_LIVE | EF_NO_KILL | EF_NO_COLLIDE | EF_KILL_ANIM, prt_index>(id); 
};
Blt* add_blt(uint id) {
  return add_enity<Blt, Enity_t::bullet, EF_LIVE | EF_DMG_PLAYER, blt_index>(id);
};
Enemy* add_ene(Enemy* p) {
  if ( !p)
    return nullptr;
  p->phys.set_deg(0);
  p->p_info = dcast(CP(Enity_info), mp_info[p->get_id()]);
  #ifdef DEBUG
  if ( !p->p_info && p->id != ENI_NONE)
    a83::error("add_ene: no finded info for " + a83::n2s(p->id));
  #endif
  p->flags = EF_LIVE | EF_DMG_PLAYER | EF_WARN_ENE;
  p->uid = current_uid;
  ++current_uid;
  p->is_collidable = true;
  p->type = Enity_t::enemy;
  p->init();
  omp::lock_guard lock(mu_vp_enity);
  vp_enity.push_back(p);
  return p;
} // add_ene
//! выделение памяти под стандартные объекты
void init_enity() {
  omp::lock_guard lock(mu_vp_enity);
  vp_enity.reserve(5000);
  #define FILL_VP_ENITY(CNT, TYPE, OBJ) FOR (i, CNT) { \
    auto p = new OBJ(); \
    p->type = Enity_t::TYPE; \
    vp_enity.push_back(p); \
  }
  // не менять местами порядок инита:
  FILL_VP_ENITY(Game::max_particle, particle, Prt)
  FILL_VP_ENITY(Game::max_bullet, bullet, Blt)
  #undef FILL_VP_ENITY
  // не используй здесь функции типа add_...
} // init_enity
void free_enity() {
  omp::lock_guard lock(mu_vp_enity);
  for (nauto p_enity: vp_enity)
    if (p_enity)
      delete p_enity;
  vp_enity.clear();
}
#ifdef DEBUG
//! выводит в консоль инфу по всем живым противникам
void dbg_info_enity() {
  using namespace a83;
  // отобразить банк инфы
  a83::log("mp_info:");
  for (cnauto val: mp_info)
    a83::log("  " + n2s(val.first) + " - " + a2s(val.second));
  // отобразить сущности
  uint i = 0;
  for (cnauto p_enity: vp_enity) {
    if ( !p_enity) {
      a83::log("enity[" + n2s(i) + "]:");
      a83::log("  zero");
      continue;
    }
    if ( !p_enity->is_live()) continue;
    a83::log("enity[" + n2s(i) + "]:");
    a83::log("  id: " + n2s(p_enity->id));
    a83::log("  phys: " + p_enity->phys.dbg_str());
    a83::log("  type: " + n2s(int(p_enity->type)));
    a83::log("  flags: " + std::bitset<sizeof(uint) * 8>
      (p_enity->flags).to_string());
    a83::log("  info:");
    a83::log(p_enity->p_info ? p_enity->p_info->dbg_info() : "    -");
    ++i;
  }
} // dbg_info_enity
//! показыет какие сейчас частицы задействованы
void dgb_counts_enity() {
  int nums[] = {
    0, // Enity_t::base
    0, // Enity_t::bullet
    0, // Enity_t::player
    0, // Enity_t::boss
    0, // Enity_t::enemy
    0, // Enity_t::bonus
    0, // Enity_t::particle
  };
  for (cnauto p_enity: vp_enity) {
    if (p_enity->is_live())
      ++nums[int(p_enity->type)];
  }
  a83::log("Total objects:");
  #define LOG(NAME) a83::log(a83::Str(#NAME) + " " + a83::n2s(nums[int(Enity_t::NAME)]))
  LOG(base);
  LOG(bullet);
  LOG(player);
  LOG(boss);
  LOG(enemy);
  LOG(bonus);
  LOG(particle);
  #undef LOG
} // dgb_inity_counts
#endif
/** отталкивает объект A от корд B
 *@param dist растояние действия 
 *@param power сила отбрасывания */
static float scatter(Phys& A, CN(Vec) B, float power, float dist) {
  float end_power = 0;
  cauto dist_a_b = distance(A.pos, B);
  if (dist_a_b > dist)
    return 0;
  cauto mul = dist_a_b / dist; // для линейного интерполирования
  end_power = mul * power;
  A.set_spd(A.spd + end_power * (1.f / 60.f));
  Vec _pos = A.pos;
  _pos -= B;
  // рандом направление
  if (_pos.x == 0 and _pos.y == 0) {
    A.set_dir( deg2vec(rand_deg()) );
  } else {
    _pos.normalize();
    A.set_dir(_pos);
  }
  return end_power;
} // scatter
void scatter_all(CN(Vec) pos, float power, float dist) {
  if (power <= 0 || dist <= 0)
    return;
  for (cnauto p: vp_enity) {
    if ( (p->flags & EF_NO_SCATTER)
    || p->is_dead())
      continue;
    float end_power = scatter(p->phys, pos, power, dist);
    // тряхнуть камеру, если это игрок
    if (p->type == Enity_t::player)
      plr_jolt += end_power;
  } // for particle array
}