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;
    }

    public CodeWriter writeBlock(WriteCallback block) throws IOException {
        writeLine("{");
        indent();

        block.write();

        dedent();
        write("}");

        return this;
    }

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

        writeBlock(() -> {
            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();
            }
        });
    }

    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("    ");
    }

    public interface WriteCallback {
        void write() throws IOException;
    }
}

@@ -362,17 +362,28 @@
 ate int 
-l
+indentationL
 evel = 0
@@ -2917,25 +2917,36 @@
 ) %7B%0A        
-l
+indentationL
 evel++;%0A    
@@ -3014,17 +3014,28 @@
     if (
-l
+indentationL
 evel == 
@@ -3081,17 +3081,28 @@
         
-l
+indentationL
 evel--;%0A
@@ -3209,17 +3209,28 @@
  0; i %3C 
-l
+indentationL
 evel; i+

Rename CodeWriter's level to indentationLevel