#pragma once

#include <maps/wikimap/mapspro/services/editor/src/common.h>
#include <maps/wikimap/mapspro/services/editor/src/attributes.h>

#include <maps/wikimap/mapspro/services/editor/src/tablevalues.h>

#include <maps/wikimap/mapspro/services/editor/src/objectclassinfo.h>
#include <maps/wikimap/mapspro/services/editor/src/relation_infos.h>

#include <yandex/maps/wiki/configs/editor/fwd.h>

#include <boost/current_function.hpp>

namespace maps {
namespace wiki {

namespace revision {

class ObjectRevision;

} // namespace revision

class ObjectVisitor;
class ObjectProcessor;
class ObjectsCache;
class Generalization;

/**
 * GeoObject. Represents basic object of wikimap
 */
class GeoObject
{
public:
    /** Object state */
    enum class State
    {
        Draft,   /** object just created or updated */
        Deleted  /** object marked as deleted */
    };

    friend class GeoObjectFactory;
    friend class RelationsManager;
    friend class RevisionsFacade;

    template <class T > friend class Constructor;

    GeoObject(TRevisionId id, ObjectsCache& cache);
    GeoObject(const GeoObject&);
    virtual ~GeoObject();

    virtual bool syncView() const;
    bool isPrivate() const;

    /** Init object properties from provided xml data
    * @param data - represents xml source
    */
    void initAttributes(const std::string& categoryId,
        const StringMultiMap& attributes,
        const TableAttributesValues& tableAttributes);

    /**
     * Construct full object data
     * using provided componentns
     */
    virtual void init(const revision::ObjectRevision& objectRev);

    const Category& category() const;

    const std::string& categoryId() const
    {
        return categoryId_;
    }

    /**
     * @returns object tablename
     */
    virtual const std::string& tablename() const = 0;

    //Set attributes from another object
    virtual void cloneAttributes(const GeoObject* source);

    //Create exect copy of this object
    virtual ObjectPtr clone() const = 0;
    //Set if Object is main edit target
    void primaryEdit(bool b);

    bool primaryEdit() const { return primaryEdit_; }

    TOid id() const { return revision_.objectId(); }

    //Return object current revision
    const TRevisionId& revision() const { return revision_; }
    const TRevisionId& prevRevision() const { return prevRevisionId_; }

    std::string screenLabel() const;
    void setScreenLabel(const std::string& l);

    std::string renderLabel() const;
    void setRenderLabel(const std::string& l);

    // !Calculates zmin and zmax values for object
    virtual void generalize(Transaction& work);

    TZoom zmin() const { return zmin_; }
    TZoom zmax() const { return zmax_; }

    /*
     * Returns center of geometry. If it is not NULL calculates it
     */
    Geom center() const;

    /*
    * Compute envelope of the object with descendants
    */
    geos::geom::Envelope envelope() const;

    /**
    * Change object state
    * @param s -new state
    */
    void setState(State s);
    State state() const { return state_; }

    /**
    * @return true if object is marked as deleted
    */
    bool isDeleted() const { return state_ == State::Deleted; }

    /**
    * Get object original image
    */
    const ObjectPtr& original() const { return original_; }

    bool hasExistingOriginal() const;

    /**
    * @return true if object is new or reverted from deleted state
    */
    bool isCreated() const;

    bool isBlocked() const;

    /**
    * Mark object as having modified attributes
    */
    void setModifiedAttr() { modifiedAttr_ = true; }

    void setModifiedSysAttr() { modifiedSysAttr_ = true; }

    /**
    * Mark everything modified
    */
    void setAllModified();

    /**
    * Mark modified parts comparing to other copy
    */
    virtual void calcModified();

    /**
    * Get objects modified flag
    * @return flag value
    */
    virtual bool isModified() const;
    bool isModifiedGeom() const;
    bool isModifiedCategory() const;
    bool isModifiedAttr() const;
    bool isModifiedAttr(const std::string& attrId) const;
    bool isModifiedTableAttrs() const;

    bool isModifiedSysAttr() const;
    bool isModifiedRichContent() const;
    bool isModifiedState() const;
    bool isModifiedClass() const;
    bool isModifiedLinksToSlaves() const;
    bool isModifiedLinksToMasters() const;

    bool isUpdatePropertiesRequested() const;
    void requestPropertiesUpdate();

    /**
    * Get ref to geom of this object
    */
    const Geom& geom() const { return geom_; }
    void setGeometry(const Geom& geom);
    virtual double geometryCompareTolerance() const;

    /**
    * Set attributes values from packed string
    */
    void attributes(const StringMap& packed);

    /**
    * Sets rich content field according to the following logic:
    * — Passing boost::none doesn't makes any changes
    * — Passing boost::optional<std::string>("") erases RichContent
    * — Passing boost::optional<std::string>("SomeData") sets RichContent to "SomeData"
    */
    void setRichContent(const boost::optional<std::string>& data);
    const boost::optional<std::string>& richContent() const;
    bool hasRichContent() const { return hasRichContent_; }

    const Attributes& attributes() const { return attributes_; }

    Attributes& attributes() { return attributes_; }

    const TableAttributesValues& tableAttributes() const { return tableAttributes_; }
    TableAttributesValues& tableAttributes() { return tableAttributes_; }

    // @return object generalization strategy
    const Generalization& generalization() const;

    std::string serviceAttrValue(const std::string& attrName) const;

    virtual void setServiceAttrValue(const std::string& attrName, const std::string& attrValue);

    const StringMap& serviceAttributes() const { return serviceAttributes_; }

    void setServiceAttributes(StringMap&& srvAttrs)
    {
        std::swap(serviceAttributes_, srvAttrs);
    }
    void removeServiceAttributes(const std::string& namePrefix);

    /**
    * Apply visitor to self
    */
    virtual void applyVisitor(ObjectVisitor& visitor) const;
    virtual void applyProcessor(ObjectProcessor& processor);

    RelationInfos relations(RelationType type) const
    {
        return RelationInfos(type == RelationType::Master
            ? masterRelationsHolder_ : slaveRelationsHolder_);
    }

    RelationInfos masterRelations() const
    {
        return RelationInfos(masterRelationsHolder_);
    }

    RelationInfos slaveRelations() const
    {
        return RelationInfos(slaveRelationsHolder_);
    }

    RelationInfosHolder& relationsHolderByType(RelationType type) const
    {
        return type == RelationType::Master
            ? masterRelationsHolder_ : slaveRelationsHolder_;
    }

    /**
    * Get restrictions applied to the object
    */
    const Restrictions& restrictions() const;

    const GeoObject* parent() const;

    ObjectsCache& cache() const
    { return cache_; }

protected:
    void calcModifiedGeom();
    void calcModifiedAttributes();
    void calcModifiedSystemAttributes();

    void setCategory(const std::string& categoryId);
    void initalizeAttributes();

    /**
    * Get and set new revision for object in database
    * @param commitId - changeset where new revision should belong to
    */
    void setRevisionCommit(TCommitId commitId);

protected:
    std::string categoryId_;
    //!Current object revision and id in database
    TRevisionId revision_;
    TRevisionId prevRevisionId_;
    //rich content might be empty due to librevision LoadingMode
    mutable boost::optional<std::string> richContent_;
    bool hasRichContent_;
    mutable bool loadedRichContent_;

    //!geometry in mercator coordinate space
    Geom geom_;
    //!minimum zoom for object
    TZoom zmin_;
    //!maximum zoom for object
    TZoom zmax_;

    //Object geo attributes
    Attributes attributes_;

    StringMap serviceAttributes_;

    //Runtime computed values

    //Identify primary editable object
    bool primaryEdit_;

    //Indicates, that object was updated
    //Generaly that means, that it was created from the xml
    //or it's geometry was modified by first neighbour
    bool modifiedGeom_;
    bool modifiedAttr_;
    bool modifiedSysAttr_;
    bool updatePropertiesRequested_;

    //Object original image
    ObjectPtr original_;

    ObjectsCache& cache_;

    mutable RelationInfosHolder masterRelationsHolder_;
    mutable RelationInfosHolder slaveRelationsHolder_;

private:
    void keepOriginal();

    void setModifiedGeom() { modifiedGeom_ = true; }

    //moderation state
    State state_;

    //Table attributes runtime storage only
    TableAttributesValues tableAttributes_;
};

std::istream& operator >> (std::istream& is, GeoObject::State& state);
std::ostream& operator << (std::ostream& os, GeoObject::State state);

template <class T> bool is(const GeoObject* obj)
{
    return nullptr != dynamic_cast<const T*>(obj);
}

template <class T> bool is(const ObjectPtr& obj)
{
    return is<T>(obj.get());
}

template <class T> T* as(GeoObject* obj)
{
    T* ret = dynamic_cast<T*>(obj);
    REQUIRE(ret, "Object " << obj->id() << " " << BOOST_CURRENT_FUNCTION);
    return ret;
}

template <class T> const T* as(const GeoObject* obj)
{
    const T* ret = dynamic_cast<const T*>(obj);
    REQUIRE(ret, "Object " << obj->id() << " " << BOOST_CURRENT_FUNCTION);
    return ret;
}

template <class T> T* as(const ObjectPtr& obj)
{
    return as<T>(obj.get());
}

} // namespace wiki
} // namespace maps
