#include <maps/wikimap/mapspro/services/tasks-ng/lib/xml_writer.h>
#include <maps/libs/common/include/exception.h>

namespace maps {
namespace wiki {
namespace tasks_ng {

namespace {

const std::string XML_VERSION = "<?xml version='1.0' encoding='utf-8'?>\n";
const std::string CDATA_BEGIN = "<![CDATA[";
const std::string CDATA_END = "]]>";
const std::string CDATA_END_ESCAPED = "]]]]><![CDATA[>";
const std::string TOTAL_COUNT_XML = "total-count";
const std::string PER_PAGE_XML = "per-page";
const std::string PAGE = "page";

} // namespace

XmlWriter::XmlWriter(std::ostream& outStream)
    : outStream_(outStream)
{
}

XmlWriter::~XmlWriter()
{
    closeTagHead();
    while (!xmlStack_.empty()) {
        closeTag();
    }
}

void XmlWriter::checkOpenedTag() const
{
    REQUIRE(!xmlStack_.empty(), "xml writer stack empty");

    const auto& top = xmlStack_.top();
    REQUIRE(
        !top.tagHeadClosed,
        "xml writer tag <" << top.tag << "> already closed");
}

void XmlWriter::openTagHead(const std::string& tag)
{
    outStream_ << "<" << tag;
    xmlStack_.push(XmlStackNode(tag));
}

void XmlWriter::closeTagHead()
{
    if (!xmlStack_.empty() && !xmlStack_.top().tagHeadClosed) {
        xmlStack_.top().tagHeadClosed = true;
        outStream_ << ">";
    }
}

void XmlWriter::closeTag()
{
    if (!xmlStack_.empty()) {
        outStream_ << "</" << xmlStack_.top().tag << ">";
        xmlStack_.pop();
    }
}

XmlWriter& XmlWriter::push(const std::string& tag)
{
    closeTagHead();
    openTagHead(tag);
    return *this;
}

XmlWriter& XmlWriter::pop()
{
    if (!xmlStack_.empty()) {
        if (xmlStack_.top().tagHeadClosed) {
            closeTag();
        } else {
            outStream_ << "/>";
            xmlStack_.pop();
        }
    }
    return *this;
}

XmlWriter& XmlWriter::addXmlVersion()
{
    outStream_ << XML_VERSION;
    return *this;
}

XmlWriter& XmlWriter::addCdata(const std::string& data)
{
    *this << CDATA_BEGIN;

    size_t iPartBegin = 0;
    size_t iPartEnd = data.find(CDATA_END, iPartBegin);
    while (iPartEnd != std::string::npos) {
        *this
            << data.substr(iPartBegin, iPartEnd - iPartBegin)
            << CDATA_END_ESCAPED;

        iPartBegin = iPartEnd + CDATA_END.size();
        iPartEnd = data.find(CDATA_END, iPartBegin);
    }
    *this << data.substr(iPartBegin) << CDATA_END;
    return *this;
}

XmlWriter& XmlWriter::addPager(const common::Pager& pager)
{
    addAttribute(TOTAL_COUNT_XML, pager.totalCount());
    addAttribute(PER_PAGE_XML, pager.perPage());
    addAttribute(PAGE, pager.page());
    return *this;
}

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