/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
package org.netbeans.modules.javascript2.json.parser;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.ArrayDeque;
import java.util.Deque;
import org.antlr.v4.runtime.ParserRuleContext;
import org.antlr.v4.runtime.Token;
import org.antlr.v4.runtime.tree.TerminalNode;
import org.netbeans.api.annotations.common.NonNull;
import org.openide.util.Parameters;
import org.openide.xml.XMLUtil;
import org.w3c.dom.Document;
import org.w3c.dom.Element;

/**
 *
 * @author Tomas Zezula
 */
public class ParseTreeToXml extends JsonBaseVisitor<Document> {
    private final JsonLexer lexer;
    private final JsonParser parser;
    private Document doc;
    private final Deque<Element> currentNode;

    public ParseTreeToXml (
            @NonNull final JsonLexer lexer,
            @NonNull final JsonParser parser) {
        Parameters.notNull("lexer", lexer); //NOI18N
        Parameters.notNull("parser", parser);    //NOI18N
        this.lexer = lexer;
        this.parser = parser;
        currentNode = new ArrayDeque<>();
    }


    @Override
    public Document visitJson(JsonParser.JsonContext ctx) {
        final Element e = copyAttrs(
                getRuleContextElement(ctx.getRuleIndex()),
                ctx);
        currentNode.addLast(e);
        super.visitJson(ctx);
        currentNode.removeLast();
        return doc;
    }

    @Override
    public Document visitObject(JsonParser.ObjectContext ctx) {
        final Element e = copyAttrs(
                getRuleContextElement(ctx.getRuleIndex()),
                ctx);
        currentNode.addLast(e);
        super.visitObject(ctx);
        currentNode.removeLast();
        return doc;
    }

    @Override
    public Document visitArray(JsonParser.ArrayContext ctx) {
        final Element e = copyAttrs(
                getRuleContextElement(ctx.getRuleIndex()),
                ctx);
        currentNode.addLast(e);
        super.visitArray(ctx);
        currentNode.removeLast();
        return doc;
    }

    @Override
    public Document visitKey(JsonParser.KeyContext ctx) {
        final Element e = copyAttrs(
                getRuleContextElement(ctx.getRuleIndex()),
                ctx);
        currentNode.addLast(e);
        super.visitKey(ctx);
        currentNode.removeLast();
        return doc;
    }

    @Override
    public Document visitValue(JsonParser.ValueContext ctx) {
        final Element e = copyAttrs(
                getRuleContextElement(ctx.getRuleIndex()),
                ctx);
        currentNode.addLast(e);
        super.visitValue(ctx);
        currentNode.removeLast();
        return doc;
    }

    @Override
    public Document visitPair(JsonParser.PairContext ctx) {
        final Element e = copyAttrs(
                getRuleContextElement(ctx.getRuleIndex()),
                ctx);
        currentNode.addLast(e);
        super.visitPair(ctx);
        currentNode.removeLast();
        return doc;
    }

    @Override
    public Document visitTerminal(TerminalNode node) {
        if (node.getSymbol().getType() != Token.EOF) {
            copyAttrs(
                    getTerminalNodeElement(node),
                    node);
        }
        super.visitTerminal(node);
        return doc;
    }

    @NonNull
    public static String stringify(@NonNull final Document doc) throws IOException {
        final ByteArrayOutputStream out = new ByteArrayOutputStream();
        XMLUtil.write(doc, out, "UTF-8");   //NOI18N
        return new String(out.toByteArray(),"UTF-8");   //NOI18N
    }


    private static Element copyAttrs(
            @NonNull final Element e,
            @NonNull final ParserRuleContext ctx) {
        e.setAttribute("start", Integer.toString(ctx.start.getStartIndex()));   //NOI18N
        e.setAttribute("stop",  Integer.toString(ctx.stop.getStopIndex()));     //NOI18N
        return e;
    }

    private static Element copyAttrs(
            @NonNull final Element e,
            @NonNull final TerminalNode ctx) {
        e.setAttribute("start", Integer.toString(ctx.getSymbol().getStartIndex())); //NOI18N
        e.setAttribute("stop",  Integer.toString(ctx.getSymbol().getStopIndex()));  //NOI18N
        e.setAttribute("text",  ctx.getSymbol().getText());  //NOI18N
        return e;
    }

    @NonNull
    private Element getRuleContextElement(final int ruleIndex) {
        return getParseTreeElement(parser.getRuleNames()[ruleIndex]);
    }

    @NonNull
    private Element getTerminalNodeElement(@NonNull final TerminalNode node) {
        return getParseTreeElement(lexer.getVocabulary().getSymbolicName(
                node.getSymbol().getType()));
    }

    @NonNull
    private Element getParseTreeElement(@NonNull final String name) {
        if (!currentNode.isEmpty()) {
            Element parent = currentNode.peekLast();
            Element me = doc.createElement(name);
            parent.appendChild(me);
            return me;
        } else if (doc != null) {
            Element parent = doc.getDocumentElement();
            Element me = doc.createElement(name);
            parent.appendChild(me);
            return me;
        } else {
            doc = XMLUtil.createDocument(name, null, null, null);
            return doc.getDocumentElement();
        }
    }

}
