/**
 * Copyright (c) 2005-2012 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.grammarcommon;

import java.util.ArrayList;
import java.util.Iterator;

import org.python.pydev.parser.jython.ISpecialStr;
import org.python.pydev.parser.jython.ParseException;
import org.python.pydev.parser.jython.SimpleNode;
import org.python.pydev.parser.jython.SpecialStr;
import org.python.pydev.parser.jython.ast.ClassDef;
import org.python.pydev.parser.jython.ast.Comprehension;
import org.python.pydev.parser.jython.ast.Dict;
import org.python.pydev.parser.jython.ast.Expr;
import org.python.pydev.parser.jython.ast.FunctionDef;
import org.python.pydev.parser.jython.ast.ListComp;
import org.python.pydev.parser.jython.ast.Name;
import org.python.pydev.parser.jython.ast.NameTok;
import org.python.pydev.parser.jython.ast.Pass;
import org.python.pydev.parser.jython.ast.Suite;
import org.python.pydev.parser.jython.ast.Tuple;
import org.python.pydev.parser.jython.ast.Yield;
import org.python.pydev.parser.jython.ast.aliasType;
import org.python.pydev.parser.jython.ast.comprehensionType;
import org.python.pydev.parser.jython.ast.decoratorsType;
import org.python.pydev.parser.jython.ast.exprType;
import org.python.pydev.parser.jython.ast.keywordType;
import org.python.pydev.parser.jython.ast.stmtType;

/**
 * Provides a bunch of helpers useful when creating a tree builder.
 * 
 * @author Fabio
 */
public abstract class AbstractTreeBuilderHelpers implements ITreeBuilder, ITreeConstants {

    protected final JJTPythonGrammarState stack;
    protected CtxVisitor ctx;
    protected SimpleNode lastPop;

    public AbstractTreeBuilderHelpers(JJTPythonGrammarState stack) {
        this.stack = stack;
        this.ctx = new CtxVisitor(stack);
    }

    protected final stmtType[] makeStmts(int l) throws ParseException {
        stmtType[] stmts = new stmtType[l];
        for (int i = l - 1; i >= 0; i--) {
            SimpleNode node = stack.popNode();
            try {
                stmts[i] = (stmtType) node;
            } catch (ClassCastException e) {
                if (node instanceof Yield) {
                    stmts[i] = new Expr((Yield) node); //recover from it with a valid node!
                    String msg = "Error. Found yield out of scope.";
                    final ParseException e2 = new ParseException(msg, node);
                    this.stack.getGrammar().addAndReport(e2, msg);
                } else {
                    recoverFromClassCastException(node, e);
                    stmts[i] = new Pass(); //recover from it with a valid node!
                }
            }
        }
        return stmts;
    }

    protected final void recoverFromClassCastException(SimpleNode node, ClassCastException e) throws ParseException {
        //something invalid happened (but let's keep it alive, just adding it as a parse error!
        String msg = "ClassCastException: was: " + e.getMessage() + " (" + node
                + "). Expected: stmtType";
        final ParseException e2 = new ParseException(msg, node);
        this.stack.getGrammar().addAndReport(e2, msg);
    }

    protected final stmtType[] popSuite() {
        return getBodyAndSpecials();
    }

    protected final exprType[] makeExprs() {
        SimpleNode commaNode = null;
        if (stack.nodeArity() > 0 && stack.peekNode().getId() == JJTCOMMA) {
            commaNode = stack.popNode();
        }
        int arity = stack.nodeArity();
        exprType[] exprs = makeExprs(arity);
        if (commaNode != null && arity > 0) {
            exprs[arity - 1].addSpecial(new SpecialStr(",", commaNode.beginLine, commaNode.beginColumn), true);
        }
        return exprs;
    }

    protected final SimpleNode makeTuple(SimpleNode n) throws ParseException {
        try {
            boolean endsWithComma = false;
            //There's a detail with tuples: if we have a tuple with a single element and it doesn't end with a comma,
            //it's not actually treated as a tuple, but as its only item.
            if (stack.nodeArity() > 0 && stack.peekNode().getId() == JJTCOMMA) {
                endsWithComma = true;
            }
            if (!endsWithComma && stack.nodeArity() == 1) {
                java.util.List<Object> tokenSourceSpecialTokensList = this.stack.getGrammar()
                        .getTokenSourceSpecialTokensList();
                for (Object object : tokenSourceSpecialTokensList) {
                    if (object instanceof Object[]) {
                        Object[] objects = (Object[]) object;
                        object = objects[0];
                    }
                    if (object instanceof ISpecialStr) {
                        ISpecialStr specialStr = (ISpecialStr) object;
                        if (specialStr.toString().equals(",")) {
                            endsWithComma = true;
                            break;
                        }
                    }
                }
            }

            final exprType[] exp = makeExprs();
            Tuple t = new Tuple(exp, Tuple.Load, endsWithComma);
            addSpecialsAndClearOriginal(n, t);
            return t;
        } catch (ClassCastException e) {
            if (e.getMessage().equals(ExtraArgValue.class.getName())) {
                this.stack.getGrammar().addAndReport(
                        new ParseException("Token: '*' is not expected inside tuples.", lastPop),
                        "Treated class cast exception on tuple");
            }
            this.stack.getGrammar().addAndReport(new ParseException("Syntax error while detecting tuple.", lastPop),
                    "Treated class cast exception on tuple");

            while (stack.nodeArity() > 0) {
                //clear whatever we had in this construct...
                stack.popNode();
            }

            //recover properly!
            return new Tuple(new exprType[0], Tuple.Load, false);

        }
    }

    protected final exprType[] makeExprs(int l) {
        exprType[] exprs = new exprType[l];
        for (int i = l - 1; i >= 0; i--) {
            lastPop = stack.popNode();
            exprs[i] = (exprType) lastPop;
        }
        return exprs;
    }

    protected final NameTok makeName(int ctx) throws ParseException {
        SimpleNode popNode = stack.popNode();
        if (!(popNode instanceof Name)) {
            this.stack.getGrammar().addAndReport(
                    new ParseException("Syntax error. Expected Name, found: " + popNode.getClass().getName(), popNode),
                    "Treated class cast exception making name");
            popNode = new Name("invalid", ctx, false);
        }

        Name name = (Name) popNode;
        return makeName(ctx, name);
    }

    protected final NameTok makeName(int ctx, Name name) {
        NameTok n = new NameTok(name.id, ctx);
        n.beginColumn = name.beginColumn;
        n.beginLine = name.beginLine;
        addSpecials(name, n);
        //we have to create it because it could be that specials are added later
        //(so, the instance must be already created even if not used)
        name.specialsBefore = n.getSpecialsBefore();
        name.specialsAfter = n.getSpecialsAfter();
        return n;
    }

    protected final NameTok[] makeIdentifiers(int ctx) throws ParseException {
        int l = stack.nodeArity();
        return makeIdentifiers(ctx, l);
    }

    protected final NameTok[] makeIdentifiers(int ctx, int arity) throws ParseException {
        NameTok[] ids = new NameTok[arity];
        for (int i = arity - 1; i >= 0; i--) {
            ids[i] = makeName(ctx);
        }
        return ids;
    }

    protected final Suite popSuiteAndSuiteType() {
        Suite s = (Suite) stack.popNode();
        Suite orelseSuite = (Suite) stack.popNode();
        orelseSuite.body = s.body;
        addSpecialsAndClearOriginal(s, orelseSuite);
        return orelseSuite;
    }

    protected final void addSpecialsAndClearOriginal(SimpleNode from, SimpleNode to) {
        addSpecials(from, to);
        if (from.specialsBefore != null) {
            from.specialsBefore.clear();
        }
        if (from.specialsAfter != null) {
            from.specialsAfter.clear();
        }
    }

    protected final void addSpecials(SimpleNode from, SimpleNode to) {
        if (from.specialsBefore != null && from.specialsBefore.size() > 0) {
            to.getSpecialsBefore().addAll(from.specialsBefore);
        }
        if (from.specialsAfter != null && from.specialsAfter.size() > 0) {
            to.getSpecialsAfter().addAll(from.specialsAfter);
        }
    }

    protected final void addSpecialsBefore(SimpleNode from, SimpleNode to) {
        if (from.specialsBefore != null && from.specialsBefore.size() > 0) {
            to.getSpecialsBefore().addAll(from.specialsBefore);
        }
        if (from.specialsAfter != null && from.specialsAfter.size() > 0) {
            to.getSpecialsBefore().addAll(from.specialsAfter);
        }
    }

    protected final void setParentForFuncOrClass(stmtType[] body, stmtType classDef) {
        for (stmtType b : body) {
            if (b instanceof ClassDef) {
                ((ClassDef) b).parent = classDef;
            } else if (b instanceof FunctionDef) {
                ((FunctionDef) b).parent = classDef;
            }
        }
    }

    /**
     * @param suite
     * @return
     */
    protected final stmtType[] getBodyAndSpecials() {
        final Suite suite = (Suite) stack.popNode();
        stmtType[] body = suite.body;
        if (body == null) {
            //This can happen when we have errors in the grammar.
            body = new stmtType[0];
        }
        if (body.length > 0) {
            //Check size (this can happen when parsing wrong grammar files)
            if (suite.specialsBefore != null && suite.specialsBefore.size() > 0) {
                body[0].getSpecialsBefore().addAll(suite.specialsBefore);
            }

            if (suite.specialsAfter != null && suite.specialsAfter.size() > 0) {
                body[body.length - 1].getSpecialsAfter().addAll(suite.specialsAfter);
            }
        }
        return body;
    }

    protected final SimpleNode makeDecorator(java.util.List<SimpleNode> nodes) {
        exprType starargs = null;
        exprType kwargs = null;

        exprType func = null;
        ArrayList<SimpleNode> keywordsl = new ArrayList<SimpleNode>();
        ArrayList<SimpleNode> argsl = new ArrayList<SimpleNode>();
        for (Iterator<SimpleNode> iter = nodes.iterator(); iter.hasNext();) {
            SimpleNode node = iter.next();

            if (node.getId() == JJTEXTRAKEYWORDVALUELIST) {
                final ExtraArgValue extraArg = (ExtraArgValue) node;
                kwargs = (extraArg).value;
                this.addSpecialsAndClearOriginal(extraArg, kwargs);
                extraArg.specialsBefore = kwargs.getSpecialsBefore();
                extraArg.specialsAfter = kwargs.getSpecialsAfter();

            } else if (node.getId() == JJTEXTRAARGVALUELIST) {
                final ExtraArgValue extraArg = (ExtraArgValue) node;
                starargs = extraArg.value;
                this.addSpecialsAndClearOriginal(extraArg, starargs);
                extraArg.specialsBefore = starargs.getSpecialsBefore();
                extraArg.specialsAfter = starargs.getSpecialsAfter();

            } else if (node instanceof keywordType) {
                keywordType keyword = (keywordType) node;
                if (starargs == null) {
                    keyword.afterstarargs = true;
                }
                keywordsl.add(keyword);

            } else if (isArg(node)) {
                //default
                argsl.add(node);

            } else if (node instanceof Comprehension) {
                argsl.add(new ListComp((exprType) iter.next(), new comprehensionType[] { (comprehensionType) node },
                        ListComp.EmptyCtx));

            } else if (node instanceof ComprehensionCollection) {
                //list comp (2 nodes: comp type and the elt -- what does elt mean by the way?) 
                argsl.add(new ListComp((exprType) iter.next(), ((ComprehensionCollection) node).getGenerators(),
                        ListComp.EmptyCtx));

            } else if (node instanceof decoratorsType) {
                func = (exprType) stack.popNode();//the func is the last thing in the stack
                decoratorsType d = (decoratorsType) node;
                d.func = func;
                d.args = argsl.toArray(new exprType[0]);
                d.keywords = keywordsl.toArray(new keywordType[0]);
                d.starargs = starargs;
                d.kwargs = kwargs;
                return d;

            } else {
                argsl.add(node);
            }

        }
        throw new RuntimeException("Something wrong happened while making the decorators...");

    }

    protected final aliasType[] makeAliases(int l) {
        aliasType[] aliases = new aliasType[l];
        for (int i = l - 1; i >= 0; i--) {
            aliases[i] = (aliasType) stack.popNode();
        }
        return aliases;
    }

    protected final boolean isArg(SimpleNode n) {
        return n instanceof ExtraArg || n instanceof DefaultArg || n instanceof keywordType;
    }

    protected final SimpleNode defaultCreateDictionary(int arity) {
        boolean isDictComplete = arity % 2 == 0;

        int l = arity / 2;
        exprType[] keys;
        if (isDictComplete) {
            keys = new exprType[l];
        } else {
            keys = new exprType[l + 1]; //we have 1 additional entry in the keys (parse error actually, but let's recover at this point!)
        }
        exprType[] vals = new exprType[l];
        for (int i = l - 1; i >= 0; i--) {
            vals[i] = (exprType) stack.popNode();
            keys[i] = (exprType) stack.popNode();
        }
        if (!isDictComplete) {
            keys[keys.length - 1] = (exprType) stack.popNode();
        }
        return new Dict(keys, vals);
    }

}
