#include "custom_document_generator.h"

#include <library/cpp/logger/global/global.h>
#include <saas/protos/rtyserver.pb.h>

#include <dict/dictutil/xml.h>
#include <library/cpp/charset/wide.h>
#include <util/charset/utf8.h>

TCustomDocumentGenerator::TCustomDocumentGenerator(const TOptions& options)
    : Options(options)
    , Counter(0)
    , HeaderOpened(false)
{
    Result.MimeType = MIME_HTML;
}

void TCustomDocumentGenerator::CloseHeader() {
    if (HeaderOpened) {
        Result.Body << ">";
        HeaderOpened = false;
    }
}

void TCustomDocumentGenerator::AddZone(const TString& name) {
    CloseHeader();
    Result.MimeType = MIME_XML;
    HeaderOpened = true;
    Tags.push_back(GetZoneName(name));
    Result.Body << "<" << Tags.back();
    if (!!name)
        ConfigZones[name].push_back(Tags.back());
}

void TCustomDocumentGenerator::AddZoneAttribute(const TString& name, const TString& value, ATTR_TYPE type) {
    VERIFY_WITH_LOG(HeaderOpened, "invalid usage");
    const TString xmlAttrName = GetAttributeName(name);
    const TUtf16String wvalue = (UTF8Detect(value) == UTF8) ? UTF8ToWide(value) : CharToWide(value, csYandex);
    Result.Body << " " << xmlAttrName << "=\"" << WideToUTF8(XmlEncode(CODES_UTF8, wvalue)) <<"\"";
    TZoneAttr& attr = ConfigZoneAttrs[name];
    if (!attr.Descr)
        attr.Descr = ToString(type).substr(5);
    attr.Elements.push_back(Tags.back() + "." + xmlAttrName);
}

void TCustomDocumentGenerator::AddText(const TString& text) {
    CloseHeader();
    const TUtf16String wtext = (UTF8Detect(text) == UTF8) ? UTF8ToWide(text) : CharToWide(text, csYandex);
    Result.Body << WideToUTF8(XmlEncode(CODES_UTF8, wtext));
}

void TCustomDocumentGenerator::EndZone() {
    VERIFY_WITH_LOG(!Tags.empty(), "Close not opened zone");
    CloseHeader();
    if (!!Tags.back())
        Result.Body << "</" << Tags.back() << ">";
    Tags.pop_back();
}

void TCustomDocumentGenerator::Finish() {
    VERIFY_WITH_LOG(Tags.empty(), "Some zones do not closed");
    GenerateConfig();
}

void TCustomDocumentGenerator::GenerateConfig() {
    Result.ParserConfig << "<XMLParser>\n<DOCTYPE>\n";
    if (!ConfigZones.empty()) {
        Result.ParserConfig << "<Zones>\n";
        for (THashMap<TString, TVector<TString>>::const_iterator i = ConfigZones.begin(); i != ConfigZones.end(); ++i)
            Result.ParserConfig << i->first << ":" << JoinStrings(i->second, ",") << "\n";
        Result.ParserConfig << "</Zones>\n";
    }
    if (!ConfigZoneAttrs.empty()) {
        Result.ParserConfig << "<Attributes>\n";
        for (THashMap<TString, TZoneAttr>::const_iterator i = ConfigZoneAttrs.begin(); i != ConfigZoneAttrs.end(); ++i)
            Result.ParserConfig << i->first << ":" << i->second.Descr << ",any/" << JoinStrings(i->second.Elements, ",") << "\n";
        Result.ParserConfig << "</Attributes>\n";
    }
    Result.ParserConfig << "</DOCTYPE>\n</XMLParser>\n";
}

TString TCustomDocumentGenerator::GetZoneName(const TString& base) {
    if (Options.PreserveNames && !base.empty()) {
        return base;
    }

    return "z" + ToString(Counter++);
}

TString TCustomDocumentGenerator::GetAttributeName(const TString& base) {
    if (Options.PreserveNames && !base.empty()) {
        return base;
    }

    return "a" + ToString(Counter++);
}

void TZoneParser::ParseZone(const NRTYServer::TZone& zone) {
    if (!zone.GetName() && zone.SearchAttributesSize())
        throw "unnamed zone cannot have search attributes";
    AddZone(zone.GetName());
    for (size_t i = 0; i < zone.SearchAttributesSize(); ++i) {
        const NRTYServer::TAttribute& attr = zone.GetSearchAttributes(i);
        ATTR_TYPE type;
        switch (attr.type()) {
        case NRTYServer::TAttribute::LITERAL_ATTRIBUTE:
            type = ATTR_LITERAL;
            break;
        case NRTYServer::TAttribute::INTEGER_ATTRIBUTE:
            type = ATTR_INTEGER;
            break;
        default:
            throw yexception() << "zone attributes can be only integer or literal";
        }
        AddZoneAttribute(attr.name(), attr.value(), type);
    }
    for (size_t i = 0; i < zone.ChildrenSize(); ++i)
        ParseZone(zone.GetChildren(i));
    AddText(zone.GetText());
    EndZone();
}
