#pragma once
#include <unordered_map>
#include <map>
#include <set>
#include <string>
#include <vector>
#include <functional>
#include <utility>


bool operator>(const GameObject& a, const GameObject& b) {
    return a.id > b.id;
}

template<class Tp, template<class> class Compare>
class DereferenceCompare {
    Compare<Tp> comp;

 public:
    bool operator()(const Tp* const a, const Tp* const b) const {
        return comp(*a, *b);
    }
};


class GameDatabase {
    std::map<ObjectId, GameObject, std::greater<ObjectId>> a;
    std::unordered_map<std::string,
    std::set<GameObject*, DereferenceCompare<GameObject, std::greater>>> b;
    std::map<std::pair<size_t, size_t>,
    std::set<GameObject*, DereferenceCompare<GameObject, std::greater>>> c;

 public:
    GameDatabase() = default;

    void Insert(ObjectId id, std::string name, size_t x, size_t y) {
        ObjectId id_ = id;
        GameObject* d = new GameObject();
        *d = { id, name, x, y };
        Remove(id);
        a.insert(make_pair(id_, *d));
        c[make_pair(x, y)].insert(d);
        b[name].insert(d);
    }

    void Remove(ObjectId id) {
        GameObject a1 = a[id];
        a.erase(id);
        c[{a1.x, a1.y}].erase(&a1);
        b[a1.name].erase(&a1);
    }

    std::vector<GameObject> DataByName(std::string name) const {
        std::vector<GameObject> a1;
        for (auto& el : b) {
            if (el.first == name) {
                std::set<GameObject*, DereferenceCompare
                    <GameObject, std::greater>> b2 = b.at(name);
                for (auto a2 : b2) {
                    (a1).push_back(*a2);
                }
            }
        }
        return a1;
    }

    std::vector<GameObject> DataByPosition(size_t x, size_t y) const {
        std::vector<GameObject> a1;
        for (auto& el : c) {
            if (el.first.first == x && el.first.second == y) {
                std::set<GameObject*, DereferenceCompare
                    <GameObject, std::greater>> c2 = c.at({ x, y });
                for (auto a2 : c2) {
                    a1.push_back(*a2);
                }
            }
        }
        return a1;
    }

    std::vector<GameObject> Data() const {
        std::vector<GameObject> a1;
        for (auto& el : a) {
             a1.push_back(el.second);
        }
        return a1;
    }
};