#pragma once

#include "common.h"
#include "title.h"
#include <maps/wikimap/mapspro/services/editor/src/commit.h>
#include <maps/wikimap/mapspro/services/editor/src/relation_infos.h>
#include <maps/wikimap/mapspro/services/editor/src/configs/categories.h>
#include <maps/wikimap/mapspro/services/editor/src/configs/config.h>

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

namespace maps {
namespace wiki {

namespace revision {
class Commit;
} // namespace revision

namespace views {
class ViewObject;
} // namespace views

class GeoObjectCollection;
class GeoObject;
class Attribute;
class Attributes;
class TileObject;
class Hotspot;
class ObjectEditContext;
class ObjectsCache;
class FormatterContext;
struct DiffDetails;

class XmlWriter
{
public:
    explicit XmlWriter(FormatterContext& formatterContext);

    static const std::string& boolToStr(bool val);

    std::ostream& put(std::ostream& output, const GeoObjectCollection* collection, ObjectsCache& cache);
    std::ostream& put(std::ostream& output, const GeoObject* object, ObjectsCache& cache);

    static std::ostream& put(std::ostream& output, const Attributes* attributes,
        const TableAttributesValues* tableAttributes);
    static std::ostream& put(std::ostream& output, const Attribute* attribute);
    static std::ostream& putTableAttributeContents(std::ostream& output, const TableValues& contents);
    static std::ostream& put(std::ostream& os, const TileObject* tileObject);
    static std::ostream& put(std::ostream& os, const views::ViewObject* viewObject);
    static std::ostream& put(std::ostream& os, const Hotspot* hotspot);
    static std::ostream& putSlaves(std::ostream& os,
        const SlaveRoles& roles, FormatterContext& context, const GeoObject* object);
    static std::ostream& put(std::ostream& os, const RelationInfos* masterInfos);

    static std::ostream& put(std::ostream& os, const DiffDetails& diffDetails);

    static std::ostream& putHeader(std::ostream& os);
    static std::ostream& putToken(std::ostream& os, const std::string& methodName, const Token& token);
    static std::ostream& putFooter(std::ostream& os);

    static std::ostream& putGeometry(
            std::ostream& os,
            const Geom& geom,
            SpatialRefSystem outputRefSys);

    // FIXME pkostikov: putRootTag is a workaround
    // it's used in the only place
    // but right now its too difficult to get rid of it
    static std::ostream& putCommitModel(
        std::ostream& os,
        const CommitModel& commitModel,
        bool putRootTag = true);

    template <typename Object> static std::ostream&
    putRevision(std::ostream& os, const Object* obj)
    {
        os << "<revision id=\"" << obj->revision() << "\"/>";
        return os;
    }

    template <typename Object> static std::ostream&
    putCategory(std::ostream& os, const Object* obj)
    {
        os << "<category-id>" << obj->categoryId()  << "</category-id>";
        return os;
    }

    template <typename Object> static std::ostream&
    putScreenLabel(std::ostream& os, const Object* obj)
    {
        os << "<screen-label>" << common::escapeCData(title(obj))
           << "</screen-label>";
        return os;
    }

    template <typename RelativeInfo> static std::ostream&
    putRelative(std::ostream& os, const RelativeInfo* relative)
    {
        os << "<object id=\"" << relative->id() << "\"";
        if (!relative->serviceAttrValue("srv:invalid").empty()) {
            os << " valid=\"false\"";
        }
        os <<">";
        putRevision(os, relative);
        putCategory(os, relative);
        putScreenLabel(os, relative);
        if (!relative->geom().isNull()) {
            int oldPrecision = os.precision();
            os.precision(DOUBLE_FORMAT_PRECISION);
            XmlWriter::putGeometry(os, relative->geom(), SpatialRefSystem::Geodetic);
            os.precision(oldPrecision);
        }
        os << "</object>";
        return os;
    }

private:
    /**
    * Sanitize and write description
    */
    static void putRichContent(std::ostream& os, const GeoObject* object);

    static std::ostream&
    putSequentialRelatives(std::ostream& os, const std::string& roleId,
        const RelationInfos::Range& relatives);

    FormatterContext& formatterContext_;
};

template <typename Ids>
void
putXmlIds(std::ostream& os, const std::string& nodeName, const Ids& ids)
{
    os << "<" << nodeName << ">";
    for (auto id : ids) {
        os << "<object-id>" << id << "</object-id>";
    }
    os << "</" << nodeName << ">";
}

} // namespace wiki
} // namespace maps
