#pragma once

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

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

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 {
 public:
    GameDatabase() = default;

    /// вставляет в базу объект с именем [name] и позицией [x, y]
    /// если объект с таким id в базе уже есть, заменяет его новым
    void Insert(ObjectId id, std::string name, size_t x, size_t y) {
        this->Remove(id);
        GameObject tmp{id, name, x, y};
        this->byId.insert(std::pair<ObjectId, GameObject>(id, tmp));
        this->byPos[std::pair<size_t, size_t>(x, y)].insert(tmp);
        this->byName[name].insert(tmp);
    }

    /// удаляет элемент по id
    /// если такого элемента нет, ничего не делает
    void Remove(ObjectId id) {
        if (this->byId.find(id) != this->byId.end()) {
            GameObject tmp = this->byId.at(id);
            this->byPos[{tmp.x, tmp.y}].erase(tmp);
            this->byName[tmp.name].erase(tmp);
            this->byId.erase(id);
        }
    }

    /// возвращает вектор объектов c именем [name]
    /// сортировка по убыванию id
    std::vector<GameObject> DataByName(std::string name) const {
        std::vector<GameObject> tmp;
        if (this->byName.find(name) != this->byName.end()) {
            for (auto obj : this->byName.at(name)) {
                tmp.push_back(obj);
            }
        }
        return tmp;
    }

    /// возвращает вектор объектов, находящихся в позиции [x, y]
    /// сортировка по убыванию id
    std::vector<GameObject> DataByPosition(size_t x, size_t y) const {
        std::vector<GameObject> tmp;
        if (this->byPos.find({x, y}) != this->byPos.end()) {
            for (auto obj : this->byPos.at({x, y})) {
                tmp.push_back(obj);
            }
        }
        return tmp;
    }

    /// возвращает вектор всех объектов из базы
    /// сортировка по убыванию id
    std::vector<GameObject> Data() const {
        std::vector<GameObject> tmp;
        for (auto pair = this->byId.rbegin();
        pair != this->byId.rend(); ++pair) {
            tmp.push_back(pair->second);
        }
        return tmp;
    }

 private:
    /// быстрый доступ по id
    std::map<ObjectId, GameObject> byId{};

    /// быстрый доступ по позиции
    std::map<std::pair<size_t, size_t>, std::set<GameObject>> byPos;

    /// быстрый доступ по имени
    std::unordered_map<std::string, std::set<GameObject>> byName;
};