package ru.yandex.autodoc.common.doc.view.renderers;

import ru.yandex.autodoc.common.doc.view.Markup;
import ru.yandex.autodoc.common.doc.view.format.XmlValue;

/**
 * @author avhaliullin
 */
public class XmlRenderer {
    private static final String BASE_PADDING = "  ";

    public String renderXml(HtmlRenderer.ElementContext ctx, XmlValue xml) {
        StringBuilder sb = new StringBuilder();
        String id = ctx.rCtx.bindId(ctx.idPrefix, "xml");
        sb.append("<pre id='").append(id).append("' parentid='").append(ctx.idPrefix).append("' class='xml code'>");
        XmlAppender appender = new XmlAppender(sb, ctx);
        appender.render(xml, "");
        sb.append("</pre>");
        return sb.toString();
    }

    private class XmlAppender {
        private final StringBuilder sb;
        private final HtmlRenderer.ElementContext ctx;

        public XmlAppender(StringBuilder sb, HtmlRenderer.ElementContext ctx) {
            this.sb = sb;
            this.ctx = ctx;
        }

        public void render(XmlValue xml, String padding) {
            sb.append(padding);
            if (xml instanceof XmlValue.InlineTag) {
                XmlValue.InlineTag it = (XmlValue.InlineTag) xml;
                String tagName = renderMarkup(ctx, it.getName());
                putStartTag(tagName);
                putInlineTagContent(renderMarkup(ctx, it.getValue()), it.getBaseType());
                putEndTag(tagName);
                if (it.getDescription() != Markup.EMPTY) {
                    putComment(renderMarkup(ctx, it.getDescription()));
                }
            } else if (xml instanceof XmlValue.MultiLineTag) {
                XmlValue.MultiLineTag mt = (XmlValue.MultiLineTag) xml;
                String tagName = renderMarkup(ctx, mt.getName());
                putStartTag(tagName);
                if (mt.getDescription() != Markup.EMPTY) {
                    putComment(renderMarkup(ctx, mt.getDescription()));
                }
                newLine();
                mt.getValues().forEach(
                        child -> {
                            render(child, padding + BASE_PADDING);
                            newLine();
                        });
                sb.append(padding);
                putEndTag(tagName);
            } else if (xml instanceof XmlValue.PolyObjectTag) {
                XmlValue.PolyObjectTag poly = (XmlValue.PolyObjectTag) xml;
                String tagName = renderMarkup(ctx, poly.getName());
                putStartTag(tagName);
                if (poly.getDescription() != Markup.EMPTY) {
                    putComment(renderMarkup(ctx, poly.getDescription()));
                }
                newLine();
                poly.getCases().forEach(cse ->
                {
                    String innerPadding = padding + BASE_PADDING;
                    sb.append(innerPadding);
                    String spoilerId = ctx.rCtx.nextSpoilerId();
                    sb.append("<span class='xml xml-fake xml-fake-comment'>")
                            .append(ctx.htmlRenderer.href("<!-- " + cse.getCaseName() + " -->", "javascript:spoiler(\"" + spoilerId + "\");"))
                            .append("</span>");
                    if (cse.getDescription() != Markup.EMPTY) {
                        putComment(renderMarkup(ctx, cse.getDescription()));
                    }
                    sb.append("<span id='" + spoilerId + "' class='hide'>");
                    newLine();
                    render(new XmlValue.InlineTag(
                            new Markup.Text(poly.getDiscriminator()),
                            new Markup.Text(cse.getCaseName()),
                            Markup.EMPTY,
                            "string"
                    ), innerPadding);
                    cse.getValues().forEach(child -> {
                        newLine();
                        render(child, innerPadding);
                    });
                    sb.append("</span>");
                    newLine();
                });
                sb.append(padding);
                putEndTag(tagName);

            } else if (xml instanceof XmlValue.Comment) {
                putComment(renderMarkup(ctx, ((XmlValue.Comment) xml).getComment()));
            } else if (xml == XmlValue.CONTINUATION) {
                sb.append("<span class='xml xml-fake xml-fake-continuation'>...</span>");
            }
        }

        private void newLine() {
            sb.append("\n");
        }

        private void putStartTag(String name) {
            sb.append("<span class='xml xml-tag'>&lt;<span class='xml xml-tag-name'>")
                    .append(name)
                    .append("</span>&gt;</span>");
        }

        private void putEndTag(String name) {
            sb.append("<span class='xml xml-tag'>&lt;/<span class='xml xml-tag-name'>")
                    .append(name)
                    .append("</span>&gt;</span>");
        }

        private void putInlineTagContent(String content, String baseType) {
            sb.append("<span class='xml xml-value xml-value-" + baseType + "'>")
                    .append(content)
                    .append("</span>");
        }

        private void putComment(String comment) {
            sb.append("<span class='xml xml-fake xml-fake-comment'>&lt;!-- ")
                    .append(comment)
                    .append(" --&gt;</span>");
        }
    }

    private String renderMarkup(HtmlRenderer.ElementContext ctx, Markup m) {
        return ctx.htmlRenderer.renderElement(ctx.rCtx, ctx.level, m, ctx.idPrefix, ctx.inInvisible);
    }
}
