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

using std::vector;
using std::string;

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, const string &name, size_t x, size_t y);

    /// удаляет элемент по id
    /// если такого элемента нет, ничего не делает
    void Remove(ObjectId id);

    /// возвращает вектор объектов c именем [name]
    /// сортировка по убыванию id
    vector<GameObject> DataByName(string name) const;

    /// возвращает вектор объектов, находящихся в позиции [x, y]
    /// сортировка по убыванию id
    vector<GameObject> DataByPosition(size_t x, size_t y) const;

    /// возвращает вектор всех объектов из базы
    /// сортировка по убыванию id
    vector<GameObject> Data() const;

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

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

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

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

void GameDatabase::Insert(ObjectId id, const string &name, size_t x, size_t y) {
    _data_id[id] = {id, name, x, y};
    _data_pos[{x, y}].insert(&_data_id[id]);
    _data_name[name].insert(&_data_id[id]);
}

void GameDatabase::Remove(ObjectId id) {
    auto item = _data_id[id];
    _data_pos[{item.x, item.y}].erase(&item);
    _data_name[item.name].erase(&item);
    _data_id.erase(id);
}

vector<GameObject> GameDatabase::DataByName(string name) const {
    vector<GameObject> data;
    if (_data_name.find(name) == _data_name.end())
        return data;
    for (auto gmObj : _data_name.at(name))
        data.push_back(*gmObj);
    return data;
}

vector<GameObject> GameDatabase::DataByPosition(size_t x, size_t y) const {
    vector<GameObject> data;
    if (_data_pos.find({x, y}) == _data_pos.end())
        return data;
    for (auto gmObj : _data_pos.at({x, y}))
        data.push_back(*gmObj);
    return data;
}

vector<GameObject> GameDatabase::Data() const {
    vector<GameObject> data;
    for (const auto &item : _data_id) {
        data.push_back(item.second);
    }
    return data;
}