Source File: dinkyecs.hpp

#pragma once

#include <any>
#include <functional>
#include <queue>
#include <tuple>
#include <typeindex>
#include <typeinfo>
#include <unordered_map>
#include <optional>
#include <memory>

namespace DinkyECS
{
  using Entity = unsigned long;
  const Entity NONE = 0;

  template <typename T>
    struct ComponentStorage {
      std::vector<T> data;
    };

  using EntityMap = std::unordered_map<Entity, size_t>;
  using TypeMap = std::unordered_map<std::type_index, std::any>;

  struct World {
    unsigned long entity_count = NONE+1;
    std::unordered_map<std::type_index, EntityMap> $components;
    std::unordered_map<std::type_index, std::any> $component_storages;
    std::unordered_map<std::type_index, std::queue<size_t>> $free_indices;

    Entity entity() { return ++entity_count; }

    void destroy(Entity entity) {
      for(auto& [tid, map] : $components) {
        if(map.contains(entity)) {
          size_t index = map.at(entity);
          auto& free_queue = $free_indices.at(tid);
          free_queue.push(index);
          map.erase(entity);
        }
      }
    }

    template <typename Comp>
      size_t make_component() {
        auto &storage = component_storage_for<Comp>();
        auto &free_queue = $free_indices.at(std::type_index(typeid(Comp)));
        size_t index;

        if(!free_queue.empty()) {
          index = free_queue.front();
          free_queue.pop();
        } else {
          storage.data.emplace_back();
          index = storage.data.size() - 1;
        }

        return index;
      }

    template <typename Comp>
      ComponentStorage<Comp> &component_storage_for() {
        auto type_index = std::type_index(typeid(Comp));
        $component_storages.try_emplace(type_index, ComponentStorage<Comp>{});
        $free_indices.try_emplace(type_index, std::queue<size_t>{});
        return std::any_cast<ComponentStorage<Comp> &>(
            $component_storages.at(type_index));
      }

    template <typename Comp>
      EntityMap &entity_map_for() {
        return $components[std::type_index(typeid(Comp))];
      }

    template <typename Comp>
      void set(Entity ent, Comp val) {
        EntityMap &map = entity_map_for<Comp>();

        if(has<Comp>(ent)) {
          get<Comp>(ent) = val;
          return;
        }

        map.insert_or_assign(ent, make_component<Comp>());
        get<Comp>(ent) = val;
      }

    template <typename Comp>
      void remove(Entity ent) {
        EntityMap &map = entity_map_for<Comp>();

        if(map.contains(ent)) {
          size_t index = map.at(ent);
          auto& free_queue = $free_indices.at(std::type_index(typeid(Comp)));
          free_queue.push(index);
          map.erase(ent);
        }
      }

    template <typename Comp>
      Comp &get(Entity ent) {
        EntityMap &map = entity_map_for<Comp>();
        auto &storage = component_storage_for<Comp>();
        auto index = map.at(ent);
        return storage.data[index];
      }

    template <typename Comp>
      bool has(Entity ent) {
        EntityMap &map = entity_map_for<Comp>();
        return map.contains(ent);
      }

    template <typename Comp>
      Comp* get_if(Entity entity) {
        EntityMap &map = entity_map_for<Comp>();
        auto &storage = component_storage_for<Comp>();
        if(map.contains(entity)) {
          auto index = map.at(entity);
          return &storage.data[index];
        } else {
          return nullptr;
        }
      }

    template <typename Comp>
      void query(std::function<void(Entity, Comp &)> cb) {
        EntityMap &map = entity_map_for<Comp>();

        for(auto &[entity, index] : map) {
          cb(entity, get<Comp>(entity));
        }
      }


    template <typename CompA, typename CompB>
      void query(std::function<void(Entity, CompA &, CompB &)> cb) {
        EntityMap &map_a = entity_map_for<CompA>();
        EntityMap &map_b = entity_map_for<CompB>();

        for(auto &[entity, index_a] : map_a) {
          if(map_b.contains(entity)) {
            cb(entity, get<CompA>(entity), get<CompB>(entity));
          }
        }
      }

  };
}