#pragma once

#include "editor_config.h"
#include "message_reporter.h"

#include <yandex/maps/wiki/revision/common.h>
#include <yandex/maps/wiki/revision/revisionid.h>

#include <list>
#include <vector>
#include <tuple>

namespace maps {
namespace wiki {
namespace importer {

class Object;
struct Relation;
struct FutureRelation;
struct DeleteRelation;
struct DeleteMasterRelation;

using ObjectPtr = std::shared_ptr<Object>;
using Objects = std::list<ObjectPtr>;
using Relations = std::vector<Relation>;
using FutureRelations = std::vector<FutureRelation>;
using DeleteRelations = std::vector<DeleteRelation>;
using DeleteMasterRelations = std::vector<DeleteMasterRelation>;

struct PointComparator
{
    bool operator()(const OGRPoint& lhs, const OGRPoint& rhs) const
    {
        return std::make_tuple(lhs.getX(), lhs.getY()) < std::make_tuple(rhs.getX(), rhs.getY());
    }
};

class ObjectsCache
{
public:
    ~ObjectsCache();

    const Objects& objects() const { return objects_; }

    ObjectPtr makeObject(ID id, TObjectId dbId, const cfg::Category& category);
    ObjectPtr getOrCreateObject(ID id, const cfg::Category& category);
    ObjectPtr getObject(ID id, const cfg::Category& category) const;

    ObjectPtr getOrCreateJunction(ID id, const OGRPoint& point, const cfg::Category& category);

private:
    Objects objects_;
    std::multimap<ID, ObjectPtr> idToObject_;
    std::map<OGRPoint, ObjectPtr, PointComparator> junctions_;
};

enum class ObjectState
{
    New,
    Existing
};

class Object
{
public:
    const cfg::Category& category() const { return category_; }

    const ID& id() const { return id_; }
    ObjectState state() const { return state_; }

    bool isModified() const { return isModified_; }

    bool isSkipped() const { return isSkipped_; }
    void setSkipped(bool b) { isSkipped_ = b; }

    TObjectId dbId() const { return dbId_; }
    void setDbId(TObjectId dbId, ObjectState state) { dbId_ = dbId; state_ = state; }

    const std::string& wkb() const { return wkb_; }
    void setWkb(std::string wkb) { wkb_ = std::move(wkb); isModified_ = true; }

    const StringMap& attributesToAdd() const { return attributesToAdd_; }
    void addAttribute(std::string id, std::string value);

    const StringSet& attributesToDelete() const { return attributesToDelete_; }
    void deleteAttribute(std::string id);

    const Relations& slaveRelations() const { return slaveRelations_; }
    const Relations& masterRelations() const { return masterRelations_; }

    Relation& addSlave(std::string roleId, const cfg::Category& slaveCategory);
    Relation& addSlave(std::string roleId, const ObjectPtr& slave);

    const DeleteRelations& deleteSlaveRelations() const { return deleteSlaveRelations_; }

    void deleteSlave(DeleteRelation relation);

    Relation& addMaster(std::string roleId, TObjectId masterDbId, const cfg::Category& masterCategory);
    Relation& addMaster(std::string roleId, const ObjectPtr& master);

    void deleteMaster(const std::string& roleId, TObjectId masterDbId = 0);
    DeleteMasterRelations mastersToDelete() { return mastersToDelete_; }

    const FutureRelations& futureMasters() const { return futureMasterRelations_; }
    void addFutureMaster(std::string roleId, ID masterId);

    void clear();

    ObjectsCache& cache() { return cache_; }

private:
    Object(ID id, TObjectId dbId, const cfg::Category& category, ObjectsCache& cache);

    friend ObjectsCache;
    ObjectsCache& cache_;

    const cfg::Category& category_;

    ID id_;
    ObjectState state_;
    bool isModified_;
    bool isSkipped_;
    TObjectId dbId_;
    std::string wkb_;
    StringMap attributesToAdd_;
    StringSet attributesToDelete_;

    Relations slaveRelations_;
    Relations masterRelations_;
    FutureRelations futureMasterRelations_;
    DeleteRelations deleteSlaveRelations_;
    DeleteMasterRelations mastersToDelete_;
};

struct Relation
{
    std::string roleId;
    ObjectPtr relatedObject;
};

/**
Relation from one shapefile to another
Example: addr to rd
*/
struct FutureRelation
{
    std::string roleId;
    ID relatedId;
};

/**
Currently it is used for deleting names
But later it may be extended to delete arbitrary relations
*/
struct DeleteRelation
{
    std::string roleId;
    std::string lang;
};

/**
Describes which masters to delete
If no masterDbId is given, will delete all masters with roleId
*/
struct DeleteMasterRelation
{
    std::string roleId;
    TObjectId masterDbId;
};

} // importer
} // wiki
} // maps
