#pragma once
#include "a83/error.hpp"
#include "a83/string-util.hpp"
#include "image/sprite.hpp"
#include "common/Vec.hpp"
#include "common/ini.hpp"
#include <deque>
#include <vector>
#include "common/omp_mutex.hpp"
#include "common/archive.hpp"
//! 1 or 16 sprites 4 bank
struct Rot16_anim { std::vector<Sprite> rot16_store = {}; };
//! режим отображения спрайта. blend mode
enum class BM {
  past = 0, blink, diff, add_safe, max_, min_,
  softlight, overlay,
};
//! storage 4 animated-ro16 sprites
class Bank_anim {
public:
  //! 1 or .. items 4 animation
  struct Item {
    std::vector<Rot16_anim> sprite_store = {};
    void print(CN(Vec) pos, Image& dst, index_t cur_spr = 0,
    float deg = 0, BM mode = BM::past) const {
      auto *spr = get_sprite(cur_spr, deg);
      if ( !spr)
        return;
      switch (mode) {
        default:
        case BM::past: spr->print_mask(pos, dst); break;
        case BM::blink: spr->print_blink(dst, pos, Game::timer_anim); break;
        case BM::diff: spr->make_print_mask<blend_diff>(pos, dst); break;
        case BM::max_: spr->make_print_mask<blend_max>(pos, dst); break;
        case BM::min_: spr->make_print_mask<blend_min>(pos, dst); break;
        case BM::add_safe: spr->make_print_mask<blend_add_safe>(pos, dst); break;
        case BM::softlight: spr->make_print_mask<blend_softlight>(pos, dst); break;
        case BM::overlay: spr->make_print_mask<blend_overlay>(pos, dst); break;
      }
      #ifdef DEBUG
      // отобразить хитбоксы
      if (debuger::status & DBG_HITS)
        for (cnauto hit: spr->hits)
          hit->print(debuger::dbg_layer, pos, Color::RED);
      #endif
    } // print
    static index_t get_spr_idx_by_deg(float deg) {
      deg += 11.25f; // центрирование угла
      if (deg >= 360.0f)
        deg -= 360.0f;
      return deg * (1.0f / 22.5f);
    }
    const Sprite* get_sprite(index_t cur_spr, float deg = 0) const {
      if (sprite_store.empty())
        return nullptr;
      if (sprite_store.size() == 1)
        cur_spr = 0;
      auto &rot16_anim = sprite_store[cur_spr];
      if (rot16_anim.rot16_store.empty())
        return nullptr;
      index_t spr16_idx = 0;
      if (rot16_anim.rot16_store.size() > 15)
        spr16_idx = get_spr_idx_by_deg(deg);
      return &rot16_anim.rot16_store[spr16_idx];
    } // get_sprite
    //! получить хитбоксы текущего спрайта
    CN(vp_figure_t) get_hits(index_t idx, float deg) const {
      if (cnauto p_spr = get_sprite(idx, deg); p_spr)
        return p_spr->hits;
      return vp_figure_empty;
    } // get_hits
  }; // struct Item
protected:
  std::deque<Item*> bank = {};
public:
  enum class Loader {
    none = 0, ///< 4 err
    inv, ///< no sprite
    AN, ///< anim, no rot
    NA, ///< no anim, auto rot
    NN, ///< no anim, no rot
    AA, ///< anim, auto rot16
    NR3, ///< no anim, rot3
    AR3, ///< anim, rot3
    NR4, ///< no anim, rot4
  };
  Bank_anim() = default;
  ~Bank_anim() { free(); }
  void free() { 
    for (auto it = bank.begin(); it != bank.end(); ++it)
      zdelete(*it);
    bank.clear();
  }
  /// create hit-only item
  Item* add_invisible(CN(vp_figure_t) hits) {
    if (hits.empty())
      return nullptr;
    bank.push_back(new Item());
    Item* ptr = bank.back();
    ptr->sprite_store.push_back({});
    ptr->sprite_store.back().rot16_store.push_back({});
    Sprite& spr = ptr->sprite_store.back().rot16_store.back();
    for (cnauto hit_src: hits)
      spr.hits.push_back(make_figure(*hit_src));
    return ptr;
  } // inv
  /// add new rot16 anims
  Item* add_anim_norot(CN(Archive) arch, CN(a83::v_str_t) image_names,
  CN(Vec) offset, CN(vp_figure_t) hits = {},
  Sprite::LightMode mode = {}) {
    if (image_names.empty())
      return nullptr;
    bank.push_back(new Item());
    Item* ptr = bank.back();
    for (cnauto name: image_names) {
      ptr->sprite_store.push_back({});
      auto* rot16_store_ptr = &ptr->sprite_store.back().rot16_store;
      rot16_store_ptr->push_back(Sprite(arch, name));
      auto& sprite = rot16_store_ptr->back();
      sprite.make_light_mask(sprite.mask, Color::WHITE, mode);
      sprite.offset = offset;
      rotate(hits, sprite.hits, 0);
    }
    return ptr;
  } // AN
  /// particle contain 1 unrotatable sprite
  Item* add_noanim_norot(CN(Archive) arch, CN(a83::Str) image_name,
  CN(Vec) offset, CN(vp_figure_t) hits = {}, Sprite::LightMode mode = {}) {
    if (image_name.empty())
      return nullptr;
    bank.push_back(new Item());
    Item* item_ptr = bank.back();
    item_ptr->sprite_store.push_back({});
    auto* rot16_store_ptr = &item_ptr->sprite_store.back().rot16_store; 
    rot16_store_ptr->push_back(Sprite(arch, image_name));
    auto& sprite = rot16_store_ptr->back();
    sprite.make_light_mask(sprite.mask, Color::WHITE, mode);
    sprite.offset = offset;
    rotate(hits, sprite.hits, 0);
    return item_ptr;
  } // NN
  /// add autorotated (rot16) particle set
  Item* add_anim_autorot(CN(Archive) arch, CN(a83::v_str_t) image_names,
  CN(Vec) offset, CN(vp_figure_t) hits = {},
  Sprite::LightMode mode = {}) {
    if (image_names.empty())
      return nullptr;
    bank.push_back(new Item());
    Item* ptr = bank.back();
    for (cnauto name: image_names) {
      ptr->sprite_store.push_back({});
      auto* rot16_store_ptr = &ptr->sprite_store.back().rot16_store;
      Sprite sprite_4_rotate(arch, name);
      sprite_4_rotate.offset = offset;
      auto degree = 90.0f;
      Sprite rotated_sprite;
      for (uint i = 0; i < 16; ++i) {
        /* Создаётся  новый повёрнутый спрайт из базового 0 град.
        Хитбоксы вращаются и сохраняются в этот спрайт.
        Добавляется освещение.
        Спрайт записывается как вариант поворота в anim item. */
        sprite_4_rotate.make_rotated(degree, rotated_sprite);
        rotate(hits, rotated_sprite.hits, degree);
        rotated_sprite.make_light_mask(rotated_sprite.mask, Color::WHITE, mode);
        rot16_store_ptr->push_back(rotated_sprite);
        degree -= 360.0f / 16.0f;
        a83::clampDeg(degree);
      }
    }
    return ptr;
  } // AA
  //! add autorotated (rot16) particle set (no anim)
  Item* add_noanim_autorot(CN(Archive) arch, CN(a83::Str) image_name,
  CN(Vec) offset, CN(vp_figure_t) hits = {}, Sprite::LightMode mode = {}) {
    if (image_name.empty())
      return nullptr;
    bank.push_back(new Item());
    Item* item_ptr = bank.back();
    item_ptr->sprite_store.push_back({});
    auto* rot16_store_ptr = &item_ptr->sprite_store.back().rot16_store; 
    Sprite sprite_4_rotate(arch, image_name);
    sprite_4_rotate.offset = offset;
    auto degree = 90.0f;
    Sprite rotated_sprite;
    for (uint i = 0; i < 16; ++i) {
      sprite_4_rotate.make_rotated(degree, rotated_sprite);
      rotate(hits, rotated_sprite.hits, degree);
      rotated_sprite.make_light_mask(rotated_sprite.mask, Color::WHITE, mode);
      rot16_store_ptr->push_back(rotated_sprite);
      degree -= 360.0f / 16.0f;
      a83::clampDeg(degree);
    }
    return item_ptr;
  }
  /// not animated particles from rot3 format
  Item* add_noanim_rot3(CN(Archive) arch, CN(a83::v_str_t) image_names,
  CN(Vec) offset, CN(vp_figure_t) hits = {},
  Sprite::LightMode mode = {}) {
    if (image_names.empty())
      return nullptr;
    bank.push_back(new Item());
    Item* ptr = bank.back();
    ptr->sprite_store.push_back({});
    auto& rot16_store = ptr->sprite_store.back().rot16_store;
    rot16_store.resize(16);
    #define ID(I)(16 - ((I + 11) % 16) - 1)
    rot16_store[ID(0)].load(arch, image_names[0], offset);
    rot16_store[ID(1)].load(arch, image_names[1], offset);
    rot16_store[ID(2)].load(arch, image_names[2], offset);
    Image tmp_img;
    rot16_store[ID(1)].tex.mirror_h(tmp_img);
    tmp_img.rotate_270(rot16_store[ID(3)].tex);
    rot16_store[ID(1)].mask.mirror_h(tmp_img);
    tmp_img.rotate_270(rot16_store[ID(3)].mask);
    rot16_store[ID(3)].offset = offset;
  // перевороты спрайтов:
    #define MOV_ROT90(a, b) \
    rot16_store[ID(b)].tex.rotate_90(tmp_img); \
    rot16_store[ID(a)].tex = tmp_img; \
    rot16_store[ID(b)].mask.rotate_90(tmp_img); \
    rot16_store[ID(a)].mask = tmp_img; \
    rot16_store[ID(a)].offset = offset;
    MOV_ROT90(4, 0)
    MOV_ROT90(5, 1)
    MOV_ROT90(6, 2)
    MOV_ROT90(7, 3)
    MOV_ROT90(8, 4)
    MOV_ROT90(9, 5)
    MOV_ROT90(10, 6)
    MOV_ROT90(11, 7)
    MOV_ROT90(12, 8)
    MOV_ROT90(13, 9)
    MOV_ROT90(14, 10)
    MOV_ROT90(15, 11)
    #undef MOV_ROT90
    #undef ID
    float degree = 90.0f;
    for (auto& it: rot16_store) {
      it.optimize_size();
      rotate(hits, it.hits, degree);
      it.make_light_mask(it.mask, Color::WHITE, mode);
      degree -= 360.0f / 16.0f;
      a83::clampDeg(degree);
    }
    return ptr;
  }
  /// not animated particles from rot4 format
  Item* add_noanim_rot4(CN(Archive) arch, CN(a83::v_str_t) image_names,
  CN(Vec) offset, CN(vp_figure_t) hits = {}, Sprite::LightMode mode = {}) {
    if (image_names.empty())
      return nullptr;
    bank.push_back(new Item());
    Item* ptr = bank.back();
    ptr->sprite_store.push_back({});
    auto& rot16_store = ptr->sprite_store.back().rot16_store;
    rot16_store.resize(16);
    #define ID(I)(16 - ((I + 11) % 16) - 1)
    rot16_store[ID(0)].load(arch, image_names[0], offset);
    rot16_store[ID(1)].load(arch, image_names[1], offset);
    rot16_store[ID(2)].load(arch, image_names[2], offset);
    rot16_store[ID(3)].load(arch, image_names[3], offset);
  // перевороты спрайтов:
    Image tmp_img;
    #define MOV_ROT90(a, b) \
    rot16_store[ID(b)].tex.rotate_90(tmp_img); \
    rot16_store[ID(a)].tex = tmp_img; \
    rot16_store[ID(b)].mask.rotate_90(tmp_img); \
    rot16_store[ID(a)].mask = tmp_img; \
    rot16_store[ID(a)].offset = offset;
    MOV_ROT90(4, 0)
    MOV_ROT90(5, 1)
    MOV_ROT90(6, 2)
    MOV_ROT90(7, 3)
    MOV_ROT90(8, 4)
    MOV_ROT90(9, 5)
    MOV_ROT90(10, 6)
    MOV_ROT90(11, 7)
    MOV_ROT90(12, 8)
    MOV_ROT90(13, 9)
    MOV_ROT90(14, 10)
    MOV_ROT90(15, 11)
    #undef MOV_ROT90
    #undef ID
    float degree = 90.0f;
    for (auto& it: rot16_store) {
      it.optimize_size();
      rotate(hits, it.hits, degree);
      it.make_light_mask(it.mask, Color::WHITE, mode);
      degree -= 360.0f / 16.0f;
      a83::clampDeg(degree);
    }
    return ptr;  
  }
  /** загрузить стандартную инфу в банк анимаций из .ini файла
  @details folder, name, first, count, offset_x/y, hits */
  Item* add(CN(Archive) arch, CN(INI) ini, CN(a83::Str) dir,
  CN(a83::Str) section, Bank_anim::Loader type,
  Sprite::LightMode lgt_mode = Sprite::LightMode::none) {
    IFERROR( !ini.checkSection(section), "Bank_anim.add: section \""
      + section + "\" not found");
    cauto FOLD = dir + ini.getStr(section, "folder", {});
    cauto NAME = ini.getStr(section, "name", {});
    // add new sprites 2 bank
    Bank_anim::Item* item_ptr = nullptr;
    const Vec offset = {
      ini.getFloat(section, "offset_x", 0),
      ini.getFloat(section, "offset_y", 0)};
    // получение всех хитбоксов первого спрайта
    vp_figure_t hits;
    parse_hits(hits, ini.getStr(section, "hits", {}));
    switch (type) {
      case Bank_anim::Loader::none:
        a83::error("Bank_anim.add: type is none"); break;
      case Bank_anim::Loader::inv: {
        item_ptr = add_invisible(hits);
        break;
      }
      default:
      case Bank_anim::Loader::NN: {
        item_ptr = add_noanim_norot(arch, NAME, offset, hits);
        break;
      }
      case Bank_anim::Loader::AN: {
        cauto ST_IDX = ini.getInt(section, "first", 0);
        cauto COUNT_IDX = ini.getInt(section, "count", 0);
        a83::v_str_t image_names = {};
        for (int i = ST_IDX; i < ST_IDX + COUNT_IDX; ++i)
          image_names.push_back(FOLD + "/" + a83::n2s(i) + ".png");
        item_ptr = add_anim_norot(arch, image_names, offset, hits);
        break;
      }
      case Bank_anim::Loader::AA: {
        cauto ST_IDX = ini.getInt(section, "first", 0);
        cauto COUNT_IDX = ini.getInt(section, "count", 0);
        a83::v_str_t image_names = {};
        for (int i = ST_IDX; i < ST_IDX + COUNT_IDX; ++i)
          image_names.push_back(FOLD + "/" + a83::n2s(i) + ".png");
        item_ptr = add_anim_autorot(arch, image_names, offset, hits);
        break;
      }
      case Bank_anim::Loader::NR3: {
        a83::v_str_t image_names = {};
        FOR (i, 3)
          image_names.push_back(FOLD + "/" + a83::n2s(i) + ".png");
        item_ptr = add_noanim_rot3(arch, image_names, offset, hits, lgt_mode);
        break; 
      }
      case Bank_anim::Loader::NR4: {
        a83::v_str_t image_names = {};
        FOR (i, 4)
          image_names.push_back(FOLD + "/" + a83::n2s(i) + ".png");
        item_ptr = add_noanim_rot4(arch, image_names, offset, hits, lgt_mode);
        break;
      }
      case Bank_anim::Loader::NA: { // без анимаций с автоповоротом
        item_ptr = add_noanim_autorot(arch, NAME, offset, hits, lgt_mode);
        break;
      }
    } // switch type
    vp_delete(hits);
    return item_ptr;
  } // add
private:
  nocopy(Bank_anim);
}; // Bank_anim
namespace {
  Bank_anim bank_anim = {};
  omp::mutex bank_anim_mu = {};
}