#pragma once

#include "concat_strings.h"

#include <util/generic/string.h>
#include <util/stream/output.h>
#include <library/cpp/html/pcdata/pcdata.h>
#include <util/system/tls.h>

extern Y_POD_THREAD(IOutputStream*) HtmlOutputStreamPtr;

static IOutputStream& HtmlOutputStream() {
    Y_VERIFY(!!HtmlOutputStreamPtr);
    return *HtmlOutputStreamPtr;
}

struct THtmlOutputStreamPushPop {
    IOutputStream* const Prev;

    THtmlOutputStreamPushPop(IOutputStream* outputStream)
        : Prev(HtmlOutputStreamPtr)
    {
        HtmlOutputStreamPtr = outputStream;
    }

    ~THtmlOutputStreamPushPop() {
        HtmlOutputStreamPtr = Prev;
    }
};

struct TChars {
    TString Text;
    bool NeedEscape;

    TChars(TStringBuf text)
        : Text(text)
        , NeedEscape(true)
    {
    }
    TChars(TStringBuf text, bool escape)
        : Text(text)
        , NeedEscape(escape)
    {
    }
    TChars(const char* text)
        : Text(text)
        , NeedEscape(true)
    {
    }
    TChars(const char* text, bool escape)
        : Text(text)
        , NeedEscape(escape)
    {
    }

    TString Escape() {
        if (NeedEscape) {
            return EncodeHtmlPcdata(Text);
        } else {
            return Text;
        }
    }
};

struct TAttr {
    TString Name;
    TString Value;

    TAttr(TStringBuf name, TStringBuf value)
        : Name(name)
        , Value(value)
    {
    }

    TAttr() {
    }

    bool operator!() const {
        return !Name;
    }
};

static inline void Doctype() {
    HtmlOutputStream() << "<!doctype html>\n";
}

static inline void Nl() {
    HtmlOutputStream() << "\n";
}

static inline void Sp() {
    HtmlOutputStream() << " ";
}

static inline void Text(TStringBuf text) {
    HtmlOutputStream() << EncodeHtmlPcdata(text);
}

static inline void Line(TStringBuf text) {
    Text(text);
    Nl();
}

static inline void WriteAttr(TAttr a) {
    if (!!a) {
        HtmlOutputStream() << " " << a.Name << "='" << EncodeHtmlPcdata(a.Value) << "'";
    }
}

static inline void Open(TStringBuf tag, TAttr a1 = TAttr(), TAttr a2 = TAttr(), TAttr a3 = TAttr(), TAttr a4 = TAttr()) {
    HtmlOutputStream() << "<" << tag;
    WriteAttr(a1);
    WriteAttr(a2);
    WriteAttr(a3);
    WriteAttr(a4);
    HtmlOutputStream() << ">";
}

static inline void Open(TStringBuf tag, TStringBuf cssClass, TStringBuf id = "") {
    Open(tag, TAttr("class", cssClass), !!id ? TAttr("id", id) : TAttr());
}

static inline void OpenBlock(TStringBuf tag, TStringBuf cssClass = "") {
    Open(tag, cssClass);
    Nl();
}

static inline void Close(TStringBuf tag) {
    HtmlOutputStream() << "</" << tag << ">\n";
}

static inline void CloseBlock(TStringBuf tag) {
    Close(tag);
    Nl();
}

static inline void TagWithContent(TStringBuf tag, TChars content) {
    HtmlOutputStream() << "<" << tag << ">" << content.Escape() << "</" << tag << ">";
}

static inline void BlockTagWithContent(TStringBuf tag, TStringBuf content) {
    TagWithContent(tag, content);
    Nl();
}

static inline void TagWithClass(TStringBuf tag, TStringBuf cssClass) {
    Open(tag, cssClass);
    Close(tag);
}

static inline void Hn(unsigned n, TStringBuf title) {
    BlockTagWithContent(ConcatStrings("h", n), title);
}

static inline void Small(TStringBuf text) {
    TagWithContent("small", text);
}

static inline void HnWithSmall(unsigned n, TStringBuf title, TStringBuf small) {
    TString tagName = ConcatStrings("h", n);
    Open(tagName);
    HtmlOutputStream() << title;
    Sp();
    Small(small);
    Close(tagName);
}

static inline void H1(TStringBuf title) {
    Hn(1, title);
}

static inline void H2(TStringBuf title) {
    Hn(2, title);
}

static inline void H3(TStringBuf title) {
    Hn(3, title);
}

static inline void H4(TStringBuf title) {
    Hn(4, title);
}

static inline void H5(TStringBuf title) {
    Hn(5, title);
}

static inline void H6(TStringBuf title) {
    Hn(6, title);
}

static inline void Pre(TStringBuf content) {
    HtmlOutputStream() << "<pre>" << EncodeHtmlPcdata(content) << "</pre>\n";
}

static inline void Li(TStringBuf content) {
    BlockTagWithContent("li", content);
}

static inline void LiWithClass(TStringBuf cssClass, TStringBuf content) {
    Open("li", cssClass);
    Text(content);
    Close("li");
}

static inline void OpenA(TStringBuf href) {
    Open("a", TAttr("href", href));
}

static inline void A(TStringBuf href, TStringBuf text) {
    OpenA(href);
    Text(text);
    Close("a");
}

static inline void Td(TStringBuf content) {
    TagWithContent("td", content);
}

static inline void Th(TStringBuf content, TStringBuf cssClass = "") {
    OpenBlock("th", cssClass);
    Text(content);
    CloseBlock("th");
}

static inline void DivWithClassAndContent(TStringBuf cssClass, TStringBuf content) {
    Open("div", cssClass);
    Text(content);
    Close("div");
}

static inline void BootstrapError(TStringBuf text) {
    DivWithClassAndContent("alert alert-danger", text);
}

static inline void BootstrapInfo(TStringBuf text) {
    DivWithClassAndContent("alert alert-info", text);
}

static inline void ScriptHref(TStringBuf href) {
    Open("script",
         TAttr("language", "javascript"),
         TAttr("type", "text/javascript"),
         TAttr("src", href));
    Close("script");
    Nl();
}

static inline void LinkStylesheet(TStringBuf href) {
    Open("link", TAttr("rel", "stylesheet"), TAttr("href", href));
    Close("link");
    Nl();
}

static inline void LinkFavicon(TStringBuf href) {
    Open("link", TAttr("rel", "shortcut icon"), TAttr("href", href));
    Close("link");
    Nl();
}

static inline void Title(TChars title) {
    TagWithContent("title", title);
    Nl();
}

static inline void Code(TStringBuf content) {
    TagWithContent("code", content);
}

struct TTagGuard {
    const TString TagName;

    TTagGuard(TStringBuf tagName, TStringBuf cssClass, TStringBuf id = "")
        : TagName(tagName)
    {
        Open(TagName, cssClass, id);
    }

    TTagGuard(TStringBuf tagName, TAttr a1 = TAttr(), TAttr a2 = TAttr(), TAttr a3 = TAttr(), TAttr a4 = TAttr())
        : TagName(tagName)
    {
        Open(tagName, a1, a2, a3, a4);
    }

    ~TTagGuard() {
        Close(TagName);
    }
};

struct TDivGuard: public TTagGuard {
    TDivGuard(TStringBuf cssClass, TStringBuf id = "")
        : TTagGuard("div", cssClass, id)
    {
    }

    TDivGuard(TAttr a1 = TAttr(), TAttr a2 = TAttr(), TAttr a3 = TAttr())
        : TTagGuard("div", a1, a2, a3)
    {
    }
};

struct TAGuard {
    TAGuard(TStringBuf href) {
        OpenA(href);
    }

    ~TAGuard() {
        Close("a");
    }
};

struct TScriptFunctionGuard {
    TTagGuard Script;

    TScriptFunctionGuard()
        : Script("script")
    {
        Line("$(function() {");
    }

    ~TScriptFunctionGuard() {
        Line("});");
    }
};
