package ru.yandex.autodoc.common.out.json.builder;

import ru.yandex.autodoc.common.util.enums.IEnumResolver;

import java.util.LinkedHashSet;
import java.util.Set;

/**
 * @author avhaliullin
 */
public class CommentedJsonAppendable extends PureJsonAppendable {
    private final String indent;
    protected Set<String> comments = new LinkedHashSet<>();

    private int depth = 0;

    public CommentedJsonAppendable(Appendable appendable, String indent) {
        super(appendable);
        this.indent = indent;
    }

    public void newLine() {
        appendKeyComment();
        append("\n");
        for (int i = 0; i < depth; i++) {
            append(indent);
        }
    }

    protected void printMultilineCommentStart() {
        append("/*");
    }

    protected void printMultilineCommentEnd() {
        append("*/");
    }

    protected void printSingleLineCommentStart() {
        append(" //");
    }

    protected void printSingleLineCommentEnd() {
    }

    protected void printSingleLineCommentPart(String part) {
        append(part);
    }

    protected void printArrayContinuation() {
        append(", ...");
    }

    @Override
    public void appendKey(String key) {
        newLine();
        super.appendKey(key);
        append(" ");
    }

    @Override
    public void startObject() {
        super.startObject();
        depth++;
    }

    @Override
    public void endObject() {
        depth--;
        newLine();
        super.endObject();
    }

    @Override
    public void endMapObject() {
        printArrayContinuation();
        endObject();
    }

    @Override
    public void comment(String s) {
        if (s != null) {
            printMultilineCommentStart();
            append(s);
            printMultilineCommentEnd();
        }
    }

    @Override
    public void startArray() {
        super.startArray();
        depth++;
        newLine();
    }

    @Override
    public void endArray() {
        printArrayContinuation();
        depth--;
        newLine();
        super.endArray();
    }

    @Override
    public void commentForLastKey(String s) {
        if (s != null) {
            comments.add(s);
        }
    }

    @Override
    public <T> PolymorphicHandler<T> polymorphic(IEnumResolver<T> er, String fieldName) {
        return new CommentedPolyHandler<>(fieldName, er);
    }

    @Override
    public NoTypePolymorphicHandler polymorphic() {
        return new CommentedNoTypePolyHandler();
    }

    private void appendKeyComment() {
        if (!comments.isEmpty()) {
            printSingleLineCommentStart();
            for (String comment : comments) {
                printSingleLineCommentPart(comment);
                append(". ");
            }
            printSingleLineCommentEnd();
            comments = new LinkedHashSet<>();
        }
    }

    protected class CommentedNoTypePolyHandler implements NoTypePolymorphicHandler {
        @Override
        public JsonAppendable withCase(String caseId) {
            comment("Type: " + caseId);
            startObject();
            return CommentedJsonAppendable.this;
        }

        @Override
        public void endCase() {
            endObject();
        }

        @Override
        public JsonAppendable getParent() {
            return CommentedJsonAppendable.this;
        }
    }

    protected class CommentedPolyHandler<T> implements PolymorphicHandler<T> {
        private final String fieldName;
        private final IEnumResolver<T> er;

        private CommentedPolyHandler(String fieldName, IEnumResolver<T> er) {
            this.fieldName = fieldName;
            this.er = er;
        }

        @Override
        public JsonAppendable withCase(T tpe) {
            comment("Type: " + er.getName(tpe));
            startObject();
            appendKey(fieldName);
            appendStringValue(er.getName(tpe));
            return CommentedJsonAppendable.this;
        }

        @Override
        public void endCase() {
            endObject();
        }

        @Override
        public JsonAppendable getParent() {
            return CommentedJsonAppendable.this;
        }
    }
}
