package fi.evident.apina.tsang;

import java.io.IOException;
import java.util.Iterator;
import java.util.Map;

import static java.util.Objects.requireNonNull;

/**
 * Helper for generating TypeScript code. Keeps track of indentation level
 * and supports proper writing of literal values.
 */
final class CodeWriter {

    private final Appendable out;
    private int level = 0;
    private boolean beginningOfLine = true;

    CodeWriter(Appendable out) {
        this.out = requireNonNull(out);
    }

    public CodeWriter writeLine(String s) throws IOException {
        write(s);
        writeLine();
        return this;
    }

    public CodeWriter write(String s) throws IOException {
        if (!s.isEmpty()) {
            writeIndentIfAtBegin();
            out.append(s);
        }

        return this;
    }

    private void writeIndentIfAtBegin() throws IOException {
        if (beginningOfLine) {
            writeIndent();
            beginningOfLine = false;
        }
    }

    public CodeWriter writeLine() throws IOException {
        out.append('\n');
        beginningOfLine = true;
        return this;
    }

    public CodeWriter writeValue(Object obj) throws IOException {
        writeIndentIfAtBegin();

        if (obj instanceof Number) {
            out.append(obj.toString());
        } else if (obj instanceof Map<?,?>) {
            writeMap((Map<?, ?>) obj);
        } else if (obj instanceof String) {
            writeString((String) obj);
        } else {
            out.append(String.valueOf(obj));
        }

        return this;
    }

    private void writeMap(Map<?, ?> obj) throws IOException {
        if (obj.isEmpty()) {
            write("{}");
            return;
        }

        write("{");
        writeLine();
        indent();

        for (Iterator<? extends Map.Entry<?, ?>> it = obj.entrySet().iterator(); it.hasNext(); ) {
            Map.Entry<?, ?> entry = it.next();
            writeValue(entry.getKey());
            write(": ");
            writeValue(entry.getValue());

            if (it.hasNext())
                write(", ");

            writeLine();
        }

        dedent();
        write("}");
    }

    private void writeString(String s) throws IOException {
        out.append('\'');
        for (int i = 0, len = s.length(); i < len; i++) {
            char c = s.charAt(i);
            switch (c) {
                case '\'':
                    out.append("\\'");
                    break;
                case '\n':
                    out.append("\\n");
                    break;
                default:
                    out.append(c);
            }
        }
        out.append('\'');
    }

    public CodeWriter indent() {
        level++;
        return this;
    }

    public CodeWriter dedent() {
        if (level == 0) throw new IllegalStateException();

        level--;
        return this;
    }

    private void writeIndent() throws IOException {
        for (int i = 0; i < level; i++)
            out.append("    ");
    }
}

@@ -1568,32 +1568,241 @@
 rn this;%0A    %7D%0A%0A
+    public CodeWriter writeBlock(WriteCallback block) throws IOException %7B%0A        writeLine(%22%7B%22);%0A        indent();%0A%0A        block.write();%0A%0A        dedent();%0A        write(%22%7D%22);%0A%0A        return this;%0A    %7D%0A%0A
     private void
@@ -1949,55 +1949,26 @@
 rite
-(%22%7B%22);%0A        writeLine();%0A        indent();%0A%0A
+Block(() -%3E %7B%0A    
     
@@ -2074,16 +2074,20 @@
         
+    
 Map.Entr
@@ -2117,32 +2117,36 @@
 ();%0A            
+  
+  
 writeValue(entry
@@ -2165,24 +2165,28 @@
             
+    
 write(%22: %22);
@@ -2190,32 +2190,36 @@
 %22);%0A            
+   
+ 
 writeValue(entry
@@ -2229,24 +2229,28 @@
 tValue());%0A%0A
+    
             
@@ -2279,24 +2279,28 @@
             
+    
 write(%22, %22);
@@ -2305,32 +2305,36 @@
 );%0A%0A            
+ 
+   
 writeLine();%0A   
@@ -2339,49 +2339,26 @@
     
-   %7D%0A%0A
        
- dedent();%0A        write(%22%7D%22
+%7D%0A        %7D
 );%0A 
@@ -3214,11 +3214,96 @@
 );%0A    %7D
+%0A%0A    public interface WriteCallback %7B%0A        void write() throws IOException;%0A    %7D
 %0A%7D%0A

Add support for easier block-writing to CodeWriter