#pragma once

#include <set>
#include <map>
#include <vector>
#include <string>

struct Coordinates {
    size_t x;
    size_t y;
};

bool operator<(const Coordinates& lhs, const Coordinates& rhs) {
    return tie(lhs.x, lhs.y) < tie(rhs.x, rhs.y);
}

bool operator>(const Coordinates& lhs, const Coordinates& rhs) {
    return tie(lhs.x, lhs.y) > tie(rhs.x, rhs.y);
}

bool operator==(const Coordinates& lhs, const Coordinates& rhs) {
    return tie(lhs.x, lhs.y) == tie(rhs.x, rhs.y);
}

bool operator<(const GameObject& lhs, const GameObject& rhs) {
    return lhs.id < rhs.id;
}

bool operator>(const GameObject& lhs, const GameObject& rhs) {
    return lhs.id > rhs.id;
}

bool operator==(const GameObject& lhs, const GameObject& rhs) {
    return lhs.id == rhs.id;
}

class GameDatabase {
 public:
    GameDatabase() = default;

    /// вставляет в базу объект с именем [name] и позицией [x, y]
    /// если объект с таким id в базе уже есть, заменяет его новым
    void Insert(ObjectId id, string name, size_t x, size_t y) {
        Remove(id);

        auto new_game_object = GameObject{id, name, x, y};
        database_.insert(new_game_object);
        database_by_id_[id] = new_game_object;
        database_by_name_[name].insert(new_game_object);
        database_by_coordinates_[Coordinates{x, y}].insert(new_game_object);
    }

    /// удаляет элемент по id
    /// если такого элемента нет, ничего не делает
    void Remove(ObjectId id) {
        auto find_it = database_by_id_.find(id);

        if (find_it != database_by_id_.end()) {
            database_.erase(find_it->second);
            database_by_name_[find_it->second.name].erase(find_it->second);
            database_by_coordinates_[
                    Coordinates{find_it->second.x, find_it->second.y}].
                                                 erase(find_it->second);
            database_by_id_.erase(id);
        }
    }

    /// возвращает вектор объектов c именем [name]
    /// сортировка по убыванию id
    vector<GameObject> DataByName(string name) const {
        vector<GameObject> v;
        try {
            v = vector<GameObject>(database_by_name_.at(name).rbegin(),
                                         database_by_name_.at(name).rend());
        } catch (out_of_range& e) {}

        return v;
    }

    /// возвращает вектор объектов, находящихся в позиции [x, y]
    /// сортировка по убыванию id
    vector<GameObject> DataByPosition(size_t x, size_t y) const {
        vector<GameObject> v;
        try {
            return vector<GameObject>(
                    database_by_coordinates_.at(Coordinates{x, y}).rbegin(),
                    database_by_coordinates_.at(Coordinates{x, y}).rend());
        } catch (out_of_range& e) {}

        return v;
    }

    /// возвращает вектор всех объектов из базы
    /// сортировка по убыванию id
    vector<GameObject> Data() const {
        return vector<GameObject>(database_.rbegin(), database_.rend());
    }

 private:
    set<GameObject> database_;
    map<ObjectId, GameObject> database_by_id_;
    map<string, set<GameObject>> database_by_name_;
    map<Coordinates, set<GameObject>> database_by_coordinates_;
};