// This file is part of Noggit3, licensed under GNU General Public License (version 3).

#include "Action.hpp"
#include <noggit/MapChunk.h>
#include <noggit/MapView.h>
#include <noggit/texture_set.hpp>
#include <noggit/ContextObject.hpp>
#include <noggit/Log.h>
#include <cstring>


Noggit::Action::Action(MapView* map_view)
: QObject()
, _map_view(map_view)
{
}

void Noggit::Action::setFlags(int flags)
{
  _flags = flags;
}

void Noggit::Action::addFlags(int flags)
{
  _flags |= flags;
}

int Noggit::Action::getFlags()
{
  return _flags;
}

void Noggit::Action::setModalityControllers(int modality_controls)
{
  _modality_controls = modality_controls;
}


void Noggit::Action::addModalityControllers(int modality_controls)
{
  _modality_controls |= modality_controls;
}

int Noggit::Action::getModalityControllers()
{
  return _modality_controls;
}

void Noggit::Action::undo(bool redo)
{
  _map_view->context()->makeCurrent(_map_view->context()->surface());
  OpenGL::context::scoped_setter const _ (::gl, _map_view->context());

  if (_flags & ActionFlags::eCHUNKS_TERRAIN)
  {
    for (auto& pair : redo ? _chunk_terrain_post : _chunk_terrain_pre)
    {
      std::memcpy(&pair.first->mVertices, pair.second.data(), 145 * 3 * sizeof(float));

      pair.first->registerChunkUpdate(ChunkUpdateFlags::VERTEX);

    }
    for (auto& pair : redo ? _chunk_terrain_post : _chunk_terrain_pre)
    {
      _map_view->getWorld()->recalc_norms(pair.first);
      pair.first->registerChunkUpdate(ChunkUpdateFlags::NORMALS);
    }
    _map_view->getWorld()->updateVertexCenter();
  }
  if (_flags & ActionFlags::eCHUNKS_TEXTURE)
  {
    for (auto& pair : redo ? _chunk_texture_post : _chunk_texture_pre)
    {
      auto texture_set = pair.first->getTextureSet();

      texture_set->setAlphamaps(pair.second.alphamaps);

      if (pair.second.tmp_edit_values)
        texture_set->getTempAlphamaps() = std::make_unique<tmp_edit_alpha_values>(*pair.second.tmp_edit_values);
      else
        texture_set->getTempAlphamaps().reset();

      std::memcpy(texture_set->getMCLYEntries(), &pair.second.layers_info, sizeof(layer_info) * 4);
      texture_set->setNTextures(pair.second.n_textures);

      auto textures = texture_set->getTextures();
      textures->clear();
      textures->reserve(pair.second.n_textures);

      for (auto& filename : pair.second.textures)
      {
        textures->emplace_back(filename, Noggit::NoggitRenderContext::MAP_VIEW);
      }

      texture_set->markDirty();
      texture_set->apply_alpha_changes();
      pair.first->registerChunkUpdate(ChunkUpdateFlags::FLAGS); // for texture anim flags
    }
  }
  if (_flags & ActionFlags::eCHUNKS_VERTEX_COLOR)
  {
    for (auto& pair : redo ? _chunk_vertex_color_post : _chunk_vertex_color_pre)
    {
      std::memcpy(&pair.first->mccv, pair.second.data(), 145 * 3 * sizeof(float));
      pair.first->registerChunkUpdate(ChunkUpdateFlags::MCCV);;
    }
  }
  if (_flags & ActionFlags::eOBJECTS_ADDED
  || _flags & ActionFlags::eOBJECTS_REMOVED
  || _flags & ActionFlags::eOBJECTS_TRANSFORMED)
  {

    tsl::robin_map<unsigned, std::vector<unsigned>> new_operation_map;

    for(auto& pair : _object_operations)
    {
      if (redo)
      {
        unsigned uid = pair.first;
        for (unsigned token : pair.second)
        {
          switch (token)
          {
            case ActionFlags::eOBJECTS_ADDED:
              uid = handleObjectAdded(pair.first, redo);
              break;
            case ActionFlags::eOBJECTS_REMOVED:
              uid = handleObjectRemoved(uid, redo);
              break;
            case ActionFlags::eOBJECTS_TRANSFORMED:
              uid = handleObjectTransformed(uid, redo);
              break;
            default:
              break;
          }
        }
        new_operation_map[uid] = pair.second;
      }
      else
      {
        unsigned uid = pair.first;
        for (auto it = pair.second.rbegin(); it !=  pair.second.rend(); ++it)
        {
          switch (*it)
          {
            case ActionFlags::eOBJECTS_ADDED:
              uid = handleObjectAdded(uid, redo);
              break;
            case ActionFlags::eOBJECTS_REMOVED:
              uid = handleObjectRemoved(uid, redo);
              break;
            case ActionFlags::eOBJECTS_TRANSFORMED:
              uid = handleObjectTransformed(uid, redo);
              break;
            default:
              break;
          }
        }
        new_operation_map[uid] = pair.second;
      }
    }
    _object_operations = std::move(new_operation_map);
  }
  if (_flags & ActionFlags::eCHUNKS_HOLES)
  {
    for (auto& pair : redo ? _chunk_holes_post : _chunk_holes_pre)
    {
      pair.first->holes = pair.second;
      pair.first->registerChunkUpdate(ChunkUpdateFlags::HOLES);
    }
  }
  if (_flags & ActionFlags::eCHUNKS_AREAID)
  {
    for (auto& pair : redo ? _chunk_area_id_post : _chunk_area_id_pre)
    {
      pair.first->areaID = pair.second;
      pair.first->registerChunkUpdate(ChunkUpdateFlags::AREA_ID);
    }
  }
  if (_flags & ActionFlags::eCHUNKS_FLAGS)
  {
    for (auto& pair : redo ? _chunk_flags_post : _chunk_flags_pre)
    {
      pair.first->header_flags = pair.second;
      pair.first->registerChunkUpdate(ChunkUpdateFlags::FLAGS);
    }
  }
  if (_flags & ActionFlags::eCHUNKS_WATER)
  {
    for (auto& pair : redo ? _chunk_liquid_post : _chunk_liquid_pre)
    {
      auto liquid_chunk = pair.first->liquid_chunk();
      *liquid_chunk->getLayers() = pair.second;
      liquid_chunk->update_layers();
      liquid_chunk->tagUpdate();
    }
  }
  if (_flags & ActionFlags::eVERTEX_SELECTION)
  {
    _map_view->getWorld()->setVertexSelectionCache(redo ? _vertex_selection_post : _vertex_selection_pre);
  }
  if (_flags & ActionFlags::eCHUNK_SHADOWS)
  {
    for (auto& pair : redo ? _chunk_shadow_map_post : _chunk_shadow_map_pre)
    {
      std::memcpy(&pair.first->_shadow_map, pair.second.data(), 64 * 64 * sizeof(uint8_t));
      pair.first->update_shadows();
    }
  }
  if (_flags & ActionFlags::eCHUNK_DOODADS_EXCLUSION)
  {
      for (auto& pair : redo ? _chunk_detaildoodad_exclusion_post : _chunk_detaildoodad_exclusion_pre)
      {
          std::memcpy(&pair.first->texture_set->_doodadStencil, pair.second.data(), 8 * sizeof(std::uint8_t));

          pair.first->registerChunkUpdate(ChunkUpdateFlags::DETAILDOODADS_EXCLUSION);
      }
  }
  if (_flags & ActionFlags::eCHUNKS_LAYERINFO)
  {
      for (auto& pair : redo ? _chunk_layerinfos_post : _chunk_layerinfos_pre)
      {
          auto texture_set = pair.first->getTextureSet();
          std::memcpy(texture_set->getMCLYEntries(), &pair.second, sizeof(layer_info) * 4);

          // TODO, enable this if texture flags get moved to this action flag.
          // pair.first->registerChunkUpdate(ChunkUpdateFlags::FLAGS); // for texture anim flags. 

          pair.first->registerChunkUpdate(ChunkUpdateFlags::GROUND_EFFECT);
      }
  }

}

unsigned Noggit::Action::handleObjectAdded(unsigned uid, bool redo)
{
  for (auto& pair : _added_objects_pre)
  {
    if (pair.first != uid)
      continue;

    unsigned new_uid = pair.first;

    if (redo)
    {
      unsigned old_uid = pair.first;
      SceneObject* obj;
      if (pair.second.type == ActionObjectTypes::WMO)
        obj = _map_view->getWorld()->addWMOAndGetInstance(pair.second.file_key, pair.second.pos, pair.second.dir, pair.second.scale, false);
      else
        obj = _map_view->getWorld()->addM2AndGetInstance(pair.second.file_key, pair.second.pos,
                                                         pair.second.scale,  pair.second.dir, nullptr, false, false);

      obj->instance_model()->wait_until_loaded();
      obj->instance_model()->waitForChildrenLoaded();
      obj->recalcExtents();

      new_uid = obj->uid;
      pair.first = new_uid;

      // fix references in other actions
      for (auto& pair_ : _removed_objects_pre)
      {
        if (pair_.first == old_uid)
        {
          pair_.first = new_uid;
          break;
        }
      }
      for (auto& pair_ : _transformed_objects_pre)
      {
        if (pair_.first == old_uid)
        {
          pair_.first = new_uid;
          break;
        }
      }
      for (auto& pair_ : _transformed_objects_post)
      {
        if (pair_.first == old_uid)
        {
          pair_.first = new_uid;
          break;
        }
      }

    }
    else
    {
      _map_view->getWorld()->deleteInstance(pair.first, false);
    }

    return new_uid;
  }

  return -1;
}

unsigned Noggit::Action::handleObjectRemoved(unsigned uid, bool redo)
{
  for (auto& pair : _removed_objects_pre)
  {
    if (pair.first != uid)
      continue;

    unsigned new_uid = pair.first;

    if (!redo)
    {
      unsigned old_uid = pair.first;
      SceneObject* obj;
      if (pair.second.type == ActionObjectTypes::WMO)
        obj = _map_view->getWorld()->addWMOAndGetInstance(pair.second.file_key, pair.second.pos, pair.second.dir, pair.second.scale, false);
      else
        obj = _map_view->getWorld()->addM2AndGetInstance(pair.second.file_key, pair.second.pos,
                                                         pair.second.scale,  pair.second.dir, nullptr, false, false);

      obj->instance_model()->wait_until_loaded();
      obj->instance_model()->waitForChildrenLoaded();
      obj->recalcExtents();

      new_uid = obj->uid;
      pair.first = new_uid;

      // fix references in other actions
      for (auto& pair_ : _added_objects_pre)
      {
        if (pair_.first == old_uid)
        {
          pair_.first = new_uid;
          break;
        }
      }
      for (auto& pair_ : _transformed_objects_pre)
      {
        if (pair_.first == old_uid)
        {
          pair_.first = new_uid;
          break;
        }
      }
      for (auto& pair_ : _transformed_objects_post)
      {
        if (pair_.first == old_uid)
        {
          pair_.first = new_uid;
          break;
        }
      }

    }
    else
    {
        _map_view->getWorld()->deleteInstance(pair.first, false);
    }

    return new_uid;
  }

  return -1;
}

unsigned Noggit::Action::handleObjectTransformed(unsigned uid, bool redo)
{
   for (auto& pair : redo ? _transformed_objects_post : _transformed_objects_pre)
   {
     if (pair.first != uid)
       continue;

     auto obj = _map_view->getWorld()->getObjectInstance(pair.first);

     if (!obj)
       continue;

     _map_view->getWorld()->updateTilesEntry(obj, model_update::remove);

     obj->pos = pair.second.pos;
     obj->dir = pair.second.dir;
     obj->scale = pair.second.scale;
     obj->recalcExtents();

     _map_view->getWorld()->updateTilesEntry(obj, model_update::add);

     return pair.first;
   }

   return -1;
}

void Noggit::Action::finish()
{
  if (_flags & ActionFlags::eCHUNKS_TERRAIN)
  {
    _chunk_terrain_post.resize(_chunk_terrain_pre.size());

    for (int i = 0; i < _chunk_terrain_pre.size(); ++i)
    {
      auto& post =_chunk_terrain_post.at(i);
      auto& pre = _chunk_terrain_pre.at(i);
      post.first = pre.first;
      std::memcpy(post.second.data(), &post.first->mVertices, 145 * 3 * sizeof(float));
    }
  }
  if (_flags & ActionFlags::eCHUNKS_TEXTURE)
  {
    _chunk_texture_post.reserve(_chunk_texture_pre.size());

    for (int i = 0; i < _chunk_texture_pre.size(); ++i)
    {
      auto& pre = _chunk_texture_pre.at(i);

      TextureChangeCache cache;
      auto texture_set = pre.first->getTextureSet();

      cache.n_textures = texture_set->num();

      const auto& sourceAlphamaps = *texture_set->getAlphamaps();
      for (size_t i = 0; i < MAX_ALPHAMAPS; ++i) {
        if (sourceAlphamaps[i])
          cache.alphamaps[i] = std::make_unique<Alphamap>(*sourceAlphamaps[i]);
        else
          cache.alphamaps[i].reset();
      }

      const auto& source_temp_alphas = texture_set->getTempAlphamaps();
      if (source_temp_alphas)
          cache.tmp_edit_values = std::make_unique<tmp_edit_alpha_values>(*source_temp_alphas);
      else
          cache.tmp_edit_values.reset();

      std::memcpy(&cache.layers_info, texture_set->getMCLYEntries(), sizeof(layer_info) * 4);

      for (int j = 0; j < cache.n_textures; ++j)
      {
        cache.textures.push_back(texture_set->filename(j));
      }

      _chunk_texture_post.emplace_back(std::make_pair(pre.first, std::move(cache)));
    }
  }
  if (_flags & ActionFlags::eCHUNKS_VERTEX_COLOR)
  {
    _chunk_vertex_color_post.resize(_chunk_vertex_color_pre.size());

    for (int i = 0; i < _chunk_vertex_color_pre.size(); ++i)
    {
      auto& post =_chunk_vertex_color_post.at(i);
      auto& pre = _chunk_vertex_color_pre.at(i);
      post.first = pre.first;
      std::memcpy(post.second.data(), &post.first->mccv, 145 * 3 * sizeof(float));
    }
  }
  if (_flags & ActionFlags::eOBJECTS_TRANSFORMED)
  {
    _transformed_objects_post.resize(_transformed_objects_pre.size());

    for (int i = 0; i < _transformed_objects_pre.size(); ++i)
    {
      auto& post =_transformed_objects_post.at(i);
      auto& pre = _transformed_objects_pre.at(i);
      post.first = pre.first;
      post.second = pre.second;

      auto instance = _map_view->getWorld()->get_model(post.first);

      if (!instance)
      {
        post.second.pos = pre.second.pos;
        post.second.dir = pre.second.dir;
        post.second.scale = pre.second.scale;
        continue;
      }

      auto obj = std::get<selected_object_type>(instance.value());

      post.second.pos = obj->pos;
      post.second.dir = obj->dir;
      post.second.scale = obj->scale;
    }
  }
  if (_flags & ActionFlags::eCHUNKS_HOLES)
  {
    _chunk_holes_post.resize(_chunk_holes_pre.size());

    for (int i = 0; i < _chunk_holes_pre.size(); ++i)
    {
      auto& post = _chunk_holes_post.at(i);
      auto& pre = _chunk_holes_pre.at(i);
      post.first = pre.first;
      post.second = pre.first->holes;
    }
  }
  if (_flags & ActionFlags::eCHUNKS_AREAID)
  {
    _chunk_area_id_post.resize(_chunk_area_id_pre.size());

    for (int i = 0; i < _chunk_area_id_pre.size(); ++i)
    {
      auto& post = _chunk_area_id_post.at(i);
      auto& pre = _chunk_area_id_pre.at(i);
      post.first = pre.first;
      post.second = pre.first->areaID;
    }
  }
  if (_flags & ActionFlags::eCHUNKS_FLAGS)
  {
    _chunk_flags_post.resize(_chunk_flags_pre.size());

    for (int i = 0; i < _chunk_flags_pre.size(); ++i)
    {
      auto& post = _chunk_flags_post.at(i);
      auto& pre = _chunk_flags_pre.at(i);
      post.first = pre.first;
      post.second = pre.first->header_flags;
    }
  }
  if (_flags & ActionFlags::eCHUNKS_WATER)
  {
    _chunk_liquid_post.resize(_chunk_liquid_pre.size());

    for (int i = 0; i < _chunk_liquid_pre.size(); ++i)
    {
      auto& post = _chunk_liquid_post.at(i);
      auto& pre = _chunk_liquid_pre.at(i);
      post.first = pre.first;
      post.second = *pre.first->liquid_chunk()->getLayers();
    }
  }
  if (_flags & ActionFlags::eVERTEX_SELECTION)
  {
    _vertex_selection_post = _map_view->getWorld()->getVertexSelectionCache();
  }
  if (_flags & ActionFlags::eCHUNK_SHADOWS)
  {
    _chunk_shadow_map_post.resize(_chunk_shadow_map_pre.size());

    for (int i = 0; i < _chunk_shadow_map_pre.size(); ++i)
    {
      auto& post =_chunk_shadow_map_post.at(i);
      auto& pre = _chunk_shadow_map_pre.at(i);
      post.first = pre.first;
      std::memcpy(post.second.data(), &post.first->_shadow_map, 64 * 64 * sizeof(std::uint8_t));
    }
  }

  if (_post)
      _post();
}

float* Noggit::Action::getChunkTerrainOriginalData(MapChunk* chunk)
{
  for (auto& pair : _chunk_terrain_pre)
  {
    if (pair.first == chunk)
      return pair.second.data();
  }
  return nullptr;
}

void Noggit::Action::setDelta(float delta)
{
  _delta = delta;
}

float Noggit::Action::getDelta() const
{
  return _delta;
}

void Noggit::Action::setBlockCursor(bool state)
{
  _block_cursor = state;
}

bool Noggit::Action::getBlockCursor() const
{
  return _block_cursor;

}

void Noggit::Action::setPostCallback(std::function<void()> function)
{
  _post = function;
}


/* ============ */
/* Registrators */
/* ============ */

void Noggit::Action::registerChunkTerrainChange(MapChunk* chunk)
{
  _flags |= ActionFlags::eCHUNKS_TERRAIN;

  for (auto& pair : _chunk_terrain_pre)
  {
    if (pair.first == chunk)
      return;
  }

  std::array<float, 145 * 3> data{};
  std::memcpy(data.data(), &chunk->mVertices, 145 * 3 * sizeof(float));
  _chunk_terrain_pre.emplace_back(std::make_pair(chunk, data));
  //LogDebug << "Chunk: " << chunk->px << "_" << chunk->py << "on tile: " << chunk->mt->index.x << "_" << chunk->mt->index.z << std::endl;
}

void Noggit::Action::registerChunkTextureChange(MapChunk* chunk)
{
  _flags |= ActionFlags::eCHUNKS_TEXTURE;

  for (auto& pair : _chunk_texture_pre)
  {
    if (pair.first == chunk)
      return;
  }

  TextureChangeCache cache;
  auto texture_set = chunk->getTextureSet();

  cache.n_textures = texture_set->num();
  // cache.alphamaps = *texture_set->getAlphamaps();
  // cache.tmp_edit_values = *texture_set->getTempAlphamaps();
  const auto& sourceAlphamaps = *texture_set->getAlphamaps();
  for (size_t i = 0; i < MAX_ALPHAMAPS; ++i) 
  {
      if (sourceAlphamaps[i])
          cache.alphamaps[i] = std::make_unique<Alphamap>(*sourceAlphamaps[i]);
      else
          cache.alphamaps[i].reset();
  }

  const auto& source_temp_alphas = texture_set->getTempAlphamaps();
  if (source_temp_alphas)
      cache.tmp_edit_values = std::make_unique<tmp_edit_alpha_values>(*source_temp_alphas);
  else
      cache.tmp_edit_values.reset();

  std::memcpy(&cache.layers_info, texture_set->getMCLYEntries(), sizeof(layer_info) * 4);

  for (int i = 0; i < cache.n_textures; ++i)
  {
    cache.textures.push_back(texture_set->filename(i));
  }
  // _chunk_texture_pre.emplace_back(std::make_pair(chunk, std::move( cache)));

  auto cache_pair = std::make_pair(chunk, std::move(cache));
  _chunk_texture_pre.emplace_back(std::move(cache_pair));
}

void Noggit::Action::registerChunkVertexColorChange(MapChunk* chunk)
{
  _flags |= ActionFlags::eCHUNKS_VERTEX_COLOR;

  for (auto& pair : _chunk_vertex_color_pre)
  {
    if (pair.first == chunk)
      return;
  }

  std::array<float, 145 * 3> data{};
  std::memcpy(data.data(), &chunk->mccv, 145 * 3 * sizeof(float));
  _chunk_vertex_color_pre.emplace_back(std::make_pair(chunk, data));
}

void Noggit::Action::registerObjectTransformed(SceneObject* obj)
{
  _flags |= ActionFlags::eOBJECTS_TRANSFORMED;

  for (auto& pair : _transformed_objects_pre)
  {
    if (pair.first == obj->uid)
      return;
  }

  _object_operations[obj->uid].emplace_back(ActionFlags::eOBJECTS_TRANSFORMED);

  ActionObjectTypes type = obj->which() == eWMO ? ActionObjectTypes::WMO : ActionObjectTypes::M2;
  _transformed_objects_pre.emplace_back(std::make_pair(obj->uid,
                                             ObjectInstanceCache{obj->instance_model()->file_key()
                                                                 , type
                                                                 , obj->pos
                                                                 , obj->dir
                                                                 , obj->scale}));
}

void Noggit::Action::registerObjectAdded(SceneObject* obj)
{
  _flags |= ActionFlags::eOBJECTS_ADDED;

  for (auto& pair : _added_objects_pre)
  {
    if (pair.first == obj->uid)
      return;
  }

  _object_operations[obj->uid].emplace_back(ActionFlags::eOBJECTS_ADDED);

  ActionObjectTypes type = obj->which() == eWMO ? ActionObjectTypes::WMO : ActionObjectTypes::M2;
  _added_objects_pre.emplace_back(std::make_pair(obj->uid,
                                       ObjectInstanceCache{obj->instance_model()->file_key()
                                                           , type
                                                           , obj->pos
                                                           , obj->dir
                                                           , obj->scale}
                                                           ));
}

void Noggit::Action::registerObjectRemoved(SceneObject* obj)
{
  _flags |= ActionFlags::eOBJECTS_REMOVED;

  for (auto& pair : _removed_objects_pre)
  {
    if (pair.first == obj->uid)
      return;
  }

  _object_operations[obj->uid].emplace_back(ActionFlags::eOBJECTS_REMOVED);

  ActionObjectTypes type = obj->which() == eWMO ? ActionObjectTypes::WMO : ActionObjectTypes::M2;
  _removed_objects_pre.emplace_back(std::make_pair(obj->uid,
                                         ObjectInstanceCache{obj->instance_model()->file_key()
                                                             , type
                                                             , obj->pos
                                                             , obj->dir
                                                             , obj->scale}
                                                             ));
}

void Noggit::Action::registerChunkHoleChange(MapChunk* chunk)
{
  _flags |= ActionFlags::eCHUNKS_HOLES;

  for (auto& pair : _chunk_holes_pre)
  {
    if (pair.first == chunk)
      return;
  }
  _chunk_holes_pre.emplace_back(std::make_pair(chunk, chunk->holes));
}

void Noggit::Action::registerChunkAreaIDChange(MapChunk* chunk)
{
  _flags |= ActionFlags::eCHUNKS_AREAID;

  for (auto& pair : _chunk_area_id_pre)
  {
    if (pair.first == chunk)
      return;
  }
  _chunk_area_id_pre.emplace_back(std::make_pair(chunk, chunk->areaID));
}

void Noggit::Action::registerChunkFlagChange(MapChunk *chunk)
{
  _flags |= ActionFlags::eCHUNKS_FLAGS;

  for (auto& pair : _chunk_flags_pre)
  {
    if (pair.first == chunk)
      return;
  }
  _chunk_flags_pre.emplace_back(std::make_pair(chunk, chunk->header_flags));
}

void Noggit::Action::registerChunkLiquidChange(MapChunk* chunk)
{
  _flags |= ActionFlags::eCHUNKS_WATER;

  for (auto& pair : _chunk_liquid_pre)
  {
    if (pair.first == chunk)
      return;
  }
  _chunk_liquid_pre.emplace_back(std::make_pair(chunk, *chunk->liquid_chunk()->getLayers()));
}

void Noggit::Action::registerVertexSelectionChange()
{
  _flags |= ActionFlags::eVERTEX_SELECTION;

  if (_vertex_selection_recorded)
    return;

  _vertex_selection_pre = _map_view->getWorld()->getVertexSelectionCache();
  _vertex_selection_recorded = true;
}

void Noggit::Action::registerChunkShadowChange(MapChunk *chunk)
{
  _flags |= ActionFlags::eCHUNK_SHADOWS;

  for (auto& pair : _chunk_shadow_map_pre)
  {
    if (pair.first == chunk)
      return;
  }

  std::array<uint8_t, 64 * 64> data;
  std::memcpy(data.data(), &chunk->_shadow_map, 64 * 64 * sizeof(std::uint8_t));
  _chunk_shadow_map_pre.emplace_back(std::make_pair(chunk, std::move(data)));
}

void Noggit::Action::registerChunkLayerInfoChange(MapChunk* chunk)
{
    _flags |= ActionFlags::eCHUNKS_LAYERINFO;

    for (auto& pair : _chunk_layerinfos_pre)
    {
        if (pair.first == chunk)
            return;
    }
    std::array<layer_info, 4> layer_infos{};
    std::memcpy(&layer_infos, chunk->texture_set->getMCLYEntries(), sizeof(layer_info) * 4);

    _chunk_layerinfos_pre.emplace_back(chunk, std::move(layer_infos));
}

void Noggit::Action::registerChunkDetailDoodadExclusionChange(MapChunk* chunk)
{
    _flags |= ActionFlags::eCHUNK_DOODADS_EXCLUSION;

    for (auto& pair : _chunk_detaildoodad_exclusion_pre)
    {
        if (pair.first == chunk)
            return;
    }
    std::array<std::uint8_t, 8> data{};
    std::memcpy(&data, chunk->texture_set->getDoodadStencilBase(), sizeof(std::uint8_t) * 8);

    _chunk_detaildoodad_exclusion_pre.emplace_back(std::make_pair(chunk, data));
}

void Noggit::Action::registerAllChunkChanges(MapChunk* chunk)
{
  registerChunkTerrainChange(chunk);
  registerChunkTextureChange(chunk);
  registerChunkVertexColorChange(chunk);
  registerChunkHoleChange(chunk);
  registerChunkAreaIDChange(chunk);
  registerChunkFlagChange(chunk);
  registerChunkLiquidChange(chunk);
  registerVertexSelectionChange();
  registerChunkShadowChange(chunk);
  registerChunkLayerInfoChange(chunk);
  registerChunkDetailDoodadExclusionChange(chunk);
}

Noggit::Action::~Action()
{
}