#pragma once

#include <yandex/maps/wiki/common/paged_result.h>

#include <list>
#include <stack>
#include <string>
#include <sstream>

namespace maps {
namespace wiki {
namespace tasks_ng {

class XmlWriter
{
public:
    explicit XmlWriter(std::ostream& outStream);
    virtual ~XmlWriter();

    XmlWriter& addTag(const std::string& tag)
    {
        push(tag);
        return pop();
    }

    template <typename F>
    XmlWriter& addTag(const std::string& tag, const F& f)
    {
        push(tag);
        f();
        return pop();
    }

    template <typename T>
    XmlWriter& addTagData(const std::string& tag, const T& o)
    {
        push(tag);
        addData(o);
        return pop();
    }

    template <typename T>
    XmlWriter& addAttribute(const std::string& tagAttr, const T& tagAttrValue)
    {
        checkOpenedTag();
        outStream_ << " " << tagAttr << "=\"" << tagAttrValue << "\"";
        return *this;
    }

    // Close current open tag start tag and write contents
    template <typename T>
    XmlWriter& operator << (const T& o)
    {
        addData(o);
        return *this;
    }

    XmlWriter& addXmlVersion();
    XmlWriter& addCdata(const std::string& data);
    XmlWriter& addPager(const common::Pager& pager);

private:
    // Begin a new child tag inside the current if the current has no contents
    // otherwise create sibling
    XmlWriter& push(const std::string& tag);

    // Close current open tag, moving up the tree
    XmlWriter& pop();

    template <typename T>
    void addData(const T& o)
    {
        closeTagHead();
        outStream_ << o;
    }

    void checkOpenedTag() const;

    void openTagHead(const std::string& tag);
    void closeTagHead();
    void closeTag();

    struct XmlStackNode
    {
        explicit XmlStackNode(std::string tag_)
            : tag(std::move(tag_))
            , tagHeadClosed(false)
        {}

        std::string tag;
        bool tagHeadClosed;
    };
    std::stack<XmlStackNode, std::list<XmlStackNode>> xmlStack_;
    std::ostream& outStream_;
};

template <>
inline XmlWriter& XmlWriter::addAttribute(const std::string& tagAttr, const bool& tagAttrValue)
{
    return addAttribute(tagAttr, tagAttrValue ? "true" : "false");
}

template <>
inline void XmlWriter::addData(const bool& data)
{
    closeTagHead();
    outStream_ << (data ? "true" : "false");
}

} // namespace tasks_ng
} // namespace wiki
} // namespace maps
