/**
 * Copyright (c) 2005-2013 by Appcelerator, Inc. All Rights Reserved.
 * Licensed under the terms of the Eclipse Public License (EPL).
 * Please see the license.txt included with this distribution for details.
 * Any modifications to this file must keep this entire header intact.
 */
package org.python.pydev.parser.prettyprinterv2;

import java.io.IOException;
import java.util.List;
import java.util.Stack;

import org.python.pydev.parser.jython.ISpecialStr;
import org.python.pydev.parser.jython.SimpleNode;
import org.python.pydev.parser.jython.ast.ClassDef;
import org.python.pydev.parser.jython.ast.For;
import org.python.pydev.parser.jython.ast.FunctionDef;
import org.python.pydev.parser.jython.ast.If;
import org.python.pydev.parser.jython.ast.Name;
import org.python.pydev.parser.jython.ast.TryExcept;
import org.python.pydev.parser.jython.ast.TryFinally;
import org.python.pydev.parser.jython.ast.VisitorBase;
import org.python.pydev.parser.jython.ast.While;
import org.python.pydev.parser.jython.ast.With;
import org.python.pydev.parser.jython.ast.commentType;
import org.python.pydev.parser.jython.ast.stmtType;
import org.python.pydev.shared_core.structure.Tuple;

/**
 * Utilities for pretty printing.
 */
public class PrettyPrinterUtilsV2 extends VisitorBase {

    protected IPrettyPrinterPrefs prefs;
    protected PrettyPrinterDocV2 doc;

    public PrettyPrinterUtilsV2(IPrettyPrinterPrefs prefs, PrettyPrinterDocV2 doc) {
        this.prefs = prefs;
        this.doc = doc;
    }

    private void writeSpecialsBefore(SimpleNode node) throws IOException {
        if (node == null) {
            return;
        }
        List<Object> specialsBefore = node.specialsBefore;
        if (specialsBefore == null) {
            return;
        }
        writeSpecials(node, specialsBefore);
    }

    private void writeSpecialsAfter(SimpleNode node) throws IOException {
        if (node == null) {
            return;
        }
        List<Object> specialsAfter = node.specialsAfter;
        if (specialsAfter == null) {
            return;
        }
        writeSpecials(node, specialsAfter);
    }

    /**
     * Adds the special tokens as tokens in the document.
     */
    private void writeSpecials(SimpleNode node, List<Object> specials) {
        for (Object c : specials) {
            if (c instanceof commentType) {
                commentType comment = (commentType) c;
                doc.add(comment.beginLine, comment.beginColumn, comment.id, comment);

            } else if (c instanceof Name) {
                Name name = (Name) c;
                doc.add(name.beginLine, name.beginColumn, name.id, name);

            } else if (c instanceof ISpecialStr) {
                ISpecialStr specialStr = (ISpecialStr) c;
                doc.add(specialStr.getBeginLine(), specialStr.getBeginCol(), specialStr.toString(), specialStr);

            } else {
                throw new RuntimeException("Unexpected special: '" + c + "' Class: " + c.getClass() + ". Node: " + node);
            }
        }
    }

    Stack<Integer> ids = new Stack<Integer>();

    /**
     * Writes the specials before and starts recording
     * @throws Exception 
     */
    protected void beforeNode(SimpleNode node) throws Exception {
        this.lastNode = node;
        beforeNodeWithoutSettintgLastNode(node);
    }

    protected void beforeNodeWithoutSettintgLastNode(SimpleNode node) throws IOException {
        if (node instanceof stmtType && !isMultiLineStmt((stmtType) node)) {
            startStatementPart();
        }
        writeSpecialsBefore(node);
    }

    public static boolean isMultiLineStmt(stmtType node) {
        return node instanceof ClassDef || node instanceof For || node instanceof FunctionDef || node instanceof If
                || node instanceof TryExcept || node instanceof TryFinally || node instanceof While
                || node instanceof With;
    }

    protected void afterNode(SimpleNode node) throws IOException {
        if (node instanceof stmtType && !isMultiLineStmt((stmtType) node)) {
            endStatementPart(node);
        }
        writeSpecialsAfter(node);
    }

    protected void startStatementPart() {
        ids.push(doc.pushRecordChanges());
    }

    protected Tuple<ILinePart, ILinePart> endStatementPart(SimpleNode node) {
        List<ILinePart> recordChanges = doc.popRecordChanges(ids.pop());

        Tuple<ILinePart, ILinePart> lowerAndHigher = doc.getLowerAndHigerFound(recordChanges);

        if (lowerAndHigher != null) {
            doc.addStartStatementMark(lowerAndHigher.o1, node);
            doc.addEndStatementMark(lowerAndHigher.o2, node);
            return lowerAndHigher;
        }
        return null;
    }

    protected void indent(SimpleNode node, boolean requireNewLine) {
        doc.addIndent(node, requireNewLine);
    }

    protected LinePartIndentMark dedent() {
        return doc.addDedent();
    }

    protected void dedent(int emptyLinesRequiredAfterDedent) {
        doc.addDedent(emptyLinesRequiredAfterDedent);
    }

    protected SimpleNode lastNode;

    @Override
    protected Object unhandled_node(SimpleNode node) throws Exception {
        this.lastNode = node;
        return null;
    }

    @Override
    public void traverse(SimpleNode node) throws Exception {
        beforeNode(node);
        node.traverse(this);
        afterNode(node);
    }

}
