package com.vladsch.flexmark.html;

import com.vladsch.flexmark.internal.util.Escaping;

import java.io.IOException;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;

public class HtmlWriter {
    private static final Map<String, String> NO_ATTRIBUTES = Collections.emptyMap();

    private final Appendable buffer;
    private char lastChar = 0;
    private int indent;
    private int indentSize = 2;
    private String indentPrefix = "";
    private String indentSizePrefix = "  ";
    private LinkedHashMap<String, String> currentAttributes;

    public HtmlWriter(Appendable out) {
        this.buffer = out;
    }

    public int getIndentSize() {
        return indentSize;
    }

    public void setIndentSize(int indentSize) {
        this.indentSize = indentSize;
        if (indentSizePrefix.length() != indentSize) {
            StringBuilder sb = new StringBuilder(indentSize);
            for (int i = 0; i < indentSize; i++) sb.append(' ');
            indentSizePrefix = sb.toString();

            if (indent > 0) {
                StringBuilder sbi = new StringBuilder(indent * indentSize);
                for (int i = 0; i < indent; i++) sbi.append(sb);
                indentPrefix = sbi.toString();
            }
        }
    }

    public HtmlWriter raw(String s) {
        append(s);
        return this;
    }

    public HtmlWriter text(String text) {
        append(Escaping.escapeHtml(text, false));
        return this;
    }

    public HtmlWriter attr(String name, String value) {
        if (currentAttributes == null) {
            currentAttributes = new LinkedHashMap<>();
        }
        currentAttributes.put(name, value);
        return this;
    }

    public HtmlWriter tag(String name) {
        return tag(name, NO_ATTRIBUTES);
    }

    public HtmlWriter tag(String name, Map<String, String> attrs) {
        return tag(name, attrs, false);
    }

    public HtmlWriter tag(String name, boolean voidElement) {
        return tag(name, null, voidElement);
    }

    public HtmlWriter tag(String name, Map<String, String> attrs, boolean voidElement) {
        if (currentAttributes != null) {
            if (attrs != null) {
                currentAttributes.putAll(attrs);
            }
            attrs = currentAttributes;
        }

        append("<");
        append(name);
        if (attrs != null && !attrs.isEmpty()) {
            for (Map.Entry<String, String> attrib : attrs.entrySet()) {
                append(" ");
                append(Escaping.escapeHtml(attrib.getKey(), true));
                append("=\"");
                append(Escaping.escapeHtml(attrib.getValue(), true));
                append("\"");
            }
        }

        if (voidElement) {
            append(" /");
        }

        append(">");
        currentAttributes = null;

        return this;
    }

    public HtmlWriter tag(String name, Runnable runnable) {
        return tag(name, null, runnable);
    }

    public HtmlWriter tag(String name, Map<String, String> attrs, Runnable runnable) {
        tag(name, attrs, false);
        int indentLevel = indent;
        runnable.run();
        while (indentLevel < indent) unIndent();
        append("</");
        append(name);
        append(">");
        return this;
    }

    public HtmlWriter line() {
        if (lastChar != 0 && lastChar != '\n') {
            append("\n");
        }
        return this;
    }

    public HtmlWriter indent() {
        line();
        indent++;
        indentPrefix += indentSizePrefix;
        return this;
    }

    public HtmlWriter unIndent() {
        if (indent > 0) {
            line();
            indent--;
            if (indent * indentSize > 0) indentPrefix = indentPrefix.substring(0, indent * indentSize);
            else indentPrefix = "";
        }
        return this;
    }

    protected void append(String s) {
        if (indentPrefix.length() > 0) {
            // convert \n to \n + indent except for the last one 
            // also if the last is \n then prefix indent size
            try {
                int lastPos = 0;
                boolean lastWasEOL = lastChar == '\n';

                while (lastPos < s.length()) {
                    int pos = s.indexOf('\n', lastPos);
                    if (pos < 0 || pos == s.length() - 1) {
                        if (lastWasEOL) buffer.append(indentPrefix);
                        buffer.append(s.substring(lastPos));
                        break;
                    }

                    if (pos > lastPos) {
                        if (lastWasEOL) buffer.append(indentPrefix);
                        buffer.append(s.substring(lastPos, pos));
                    }

                    buffer.append("\n");
                    lastWasEOL = true;
                    lastPos = pos + 1;
                }
            } catch (IOException e) {
                throw new RuntimeException(e);
            }

            int length = s.length();
            if (length != 0) {
                lastChar = s.charAt(length - 1);
            }
        } else {
            try {
                buffer.append(s);
            } catch (IOException e) {
                throw new RuntimeException(e);
            }

            int length = s.length();
            if (length != 0) {
                lastChar = s.charAt(length - 1);
            }
        }
    }
}

@@ -360,25 +360,28 @@
 ate 
-char lastChar = 0
+final int indentSize
 ;%0A  
@@ -394,50 +394,73 @@
 ate 
+f
 in
-t indent;%0A    private int indentSize = 2
+al String indentSizePrefix;%0A    %0A    private char lastChar = 0
 ;%0A  
@@ -473,32 +473,18 @@
 ate 
-Str
 in
-g
+t
  indent
-Prefix = %22%22
 ;%0A  
@@ -502,28 +502,24 @@
 tring indent
-Size
 Prefix = %22  
@@ -516,18 +516,16 @@
 efix = %22
-  
 %22;%0A    p
@@ -637,21 +637,16 @@
 this
-.buffer = out
+(out, 0)
 ;%0A  
@@ -665,94 +665,35 @@
 lic 
-int getIndentSize() %7B%0A        return indentSize;%0A    %7D%0A%0A    public void setIndentSize(
+HtmlWriter(Appendable out, 
 int 
@@ -723,31 +723,20 @@
 his.
-indentSize = indentSize
+buffer = out
 ;%0A  
@@ -737,28 +737,29 @@
 ut;%0A        
-if (
+this.
 indentSizePr
@@ -760,25 +760,9 @@
 Size
-Prefix.length() !
+ 
 = in
@@ -765,32 +765,27 @@
 = indentSize
-) %7B%0A    
+;%0A%0A
         Stri
@@ -830,36 +830,32 @@
 tSize);%0A        
-    
 for (int i = 0; 
@@ -895,28 +895,24 @@
 ');%0A        
-    
 indentSizePr
@@ -937,250 +937,74 @@
 ();%0A
-%0A
     
-        if (indent %3E 0) %7B%0A                StringBuilder sbi = new StringBuilder(indent * indentSize);%0A                for (int i = 0; i %3C indent; i++) sbi.append(sb);%0A                indentPrefix = sbi.toString();%0A            %7D%0A        %7D
+%7D%0A%0A    public int getIndentSize() %7B%0A        return indentSize;
 %0A   
@@ -2888,24 +2888,74 @@
 able.run();%0A
+        boolean hadIndent = indentLevel %3C indent;%0A
         whil
@@ -3048,32 +3048,65 @@
    append(%22%3E%22);%0A
+%0A        if (hadIndent) line();%0A%0A
         return t

add indenting, chained calls, etc