/**
 * 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.
 */
/*
 * Created on Dec 21, 2004
 *
 * @author Fabio Zadrozny
 */
package org.python.pydev.editor.codecompletion.revisited.visitors;

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

import org.python.pydev.core.FullRepIterable;
import org.python.pydev.core.ICompletionState;
import org.python.pydev.core.IPythonNature;
import org.python.pydev.core.IToken;
import org.python.pydev.editor.codecompletion.revisited.modules.SourceToken;
import org.python.pydev.parser.jython.SimpleNode;
import org.python.pydev.parser.jython.ast.Import;
import org.python.pydev.parser.jython.ast.ImportFrom;
import org.python.pydev.parser.jython.ast.NameTok;
import org.python.pydev.parser.jython.ast.Str;
import org.python.pydev.parser.jython.ast.VisitorBase;
import org.python.pydev.parser.jython.ast.aliasType;
import org.python.pydev.parser.visitors.NodeUtils;

/**
 * @author Fabio Zadrozny
 */
public abstract class AbstractVisitor extends VisitorBase {

    /**
     * The constants below may be combined for a single request
     */
    public static final int GLOBAL_TOKENS = 1;

    public static final int WILD_MODULES = 2;

    public static final int ALIAS_MODULES = 4;

    public static final int MODULE_DOCSTRING = 8;

    /**
     * This constant cannot be combined with any of the others
     */
    public static final int INNER_DEFS = 16;

    protected final List<IToken> tokens = new ArrayList<IToken>();

    /**
     * Module being visited.
     */
    protected String moduleName;

    protected final IPythonNature nature;

    public AbstractVisitor(IPythonNature nature) {
        this.nature = nature;
    }

    /**
     * Adds a token with a docstring.
     *
     * @param node
     */
    protected SourceToken addToken(SimpleNode node) {
        //add the token
        SourceToken t = makeToken(node, moduleName, nature);
        this.tokens.add(t);
        return t;
    }

    public static SourceToken makeToken(SimpleNode node, String moduleName, IPythonNature nature) {
        return new SourceToken(node, NodeUtils.getRepresentationString(node), NodeUtils.getNodeArgs(node),
                NodeUtils.getNodeDocString(node), moduleName, nature);
    }

    public static SourceToken makeToken(SimpleNode node, String rep, String moduleName, IPythonNature nature) {
        return new SourceToken(node, rep, NodeUtils.getNodeArgs(node), NodeUtils.getNodeDocString(node), moduleName,
                nature);
    }

    /**
     * same as make token, but returns the full representation for a token, instead of just a 'partial' name
     */
    public static SourceToken makeFullNameToken(SimpleNode node, String moduleName, IPythonNature nature) {
        return new SourceToken(node, NodeUtils.getFullRepresentationString(node), NodeUtils.getNodeArgs(node),
                NodeUtils.getNodeDocString(node), moduleName, nature);
    }

    /**
     * This function creates source tokens from a wild import node.
     *
     * @param node the import node
     * @param tokens OUT used to add the source token
     * @param moduleName the module name
     *
     * @return the tokens list passed in or the created one if it was null
     */
    public static IToken makeWildImportToken(ImportFrom node, List<IToken> tokens, String moduleName,
            IPythonNature nature) {
        if (tokens == null) {
            tokens = new ArrayList<IToken>();
        }
        SourceToken sourceToken = null;
        if (isWildImport(node)) {
            sourceToken = new SourceToken(node, ((NameTok) node.module).id, "", "", moduleName, nature);
            tokens.add(sourceToken);
        }
        return sourceToken;
    }

    public static List<IToken> makeImportToken(SimpleNode node, List<IToken> tokens, String moduleName,
            boolean allowForMultiple, IPythonNature nature) {
        if (node instanceof Import) {
            return makeImportToken((Import) node, tokens, moduleName, allowForMultiple, nature);
        }
        if (node instanceof ImportFrom) {
            ImportFrom i = (ImportFrom) node;
            if (isWildImport(i)) {
                makeWildImportToken(i, tokens, moduleName, nature);
                return tokens;
            }
            return makeImportToken((ImportFrom) node, tokens, moduleName, allowForMultiple, nature);
        }

        throw new RuntimeException("Unable to create token for the passed import (" + node + ")");
    }

    /**
     * This function creates source tokens from an import node.
     *
     * @param node the import node
     * @param moduleName the module name where this token was found
     * @param tokens OUT used to add the source tokens (may create many from a single import)
     * @param allowForMultiple is used to indicate if an import in the format import os.path should generate one token for os
     * and another for os.path or just one for both with os.path
     *
     * @return the tokens list passed in or the created one if it was null
     */
    public static List<IToken> makeImportToken(Import node, List<IToken> tokens, String moduleName,
            boolean allowForMultiple, IPythonNature nature) {
        aliasType[] names = node.names;
        return makeImportToken(node, tokens, names, moduleName, "", nature);
    }

    /**
     * The same as above but with ImportFrom
     */
    public static List<IToken> makeImportToken(ImportFrom node, List<IToken> tokens, String moduleName,
            boolean allowForMultiple, IPythonNature nature) {
        aliasType[] names = node.names;
        String importName = ((NameTok) node.module).id;

        return makeImportToken(node, tokens, names, moduleName, importName, nature);
    }

    /**
     * This class is the same as a regular source token, just used to know that this
     * is a token that was created to identify a part of an import declaration.
     *
     * E.g.:
     *
     * import os.path
     *
     * Will create an 'os' part -- which is leaked to the namespace (but we must
     * identify that because we don't want to report import redefinitions nor unused
     * variables for those).
     *
     * See: https://sourceforge.net/tracker/index.php?func=detail&aid=2879058&group_id=85796&atid=577329
     * and  https://sourceforge.net/tracker/index.php?func=detail&aid=2008026&group_id=85796&atid=577329
     */
    public static class ImportPartSourceToken extends SourceToken {

        private static final long serialVersionUID = 1L;

        public ImportPartSourceToken(SimpleNode node, String rep, String doc, String args, String parentPackage,
                String originalRep, boolean originalHasRep, IPythonNature nature) {
            super(node, rep, doc, args, parentPackage, originalRep, originalHasRep, nature);
        }
    }

    /**
     * The same as above
     */
    private static List<IToken> makeImportToken(SimpleNode node, List<IToken> tokens, aliasType[] names, String module,
            String initialImportName, IPythonNature nature) {
        if (tokens == null) {
            tokens = new ArrayList<IToken>();
        }

        if (initialImportName.length() > 0) {
            initialImportName = initialImportName + ".";
        }

        for (int i = 0; i < names.length; i++) {
            aliasType aliasType = names[i];

            String name = null;
            String original = ((NameTok) aliasType.name).id;

            if (aliasType.asname != null) {
                name = ((NameTok) aliasType.asname).id;
            }

            if (name == null) {
                FullRepIterable iterator = new FullRepIterable(original);
                Iterator<String> it = iterator.iterator();
                while (it.hasNext()) {
                    String rep = it.next();
                    SourceToken sourceToken;
                    if (it.hasNext()) {
                        sourceToken = new ImportPartSourceToken(node, rep, "", "", module, initialImportName + rep,
                                true, nature);

                    } else {
                        sourceToken = new SourceToken(node, rep, "", "", module, initialImportName + rep, true, nature);
                    }
                    tokens.add(sourceToken);
                }
            } else {
                SourceToken sourceToken = new SourceToken(node, name, "", "", module, initialImportName + original,
                        false, nature);
                tokens.add(sourceToken);
            }

        }
        return tokens;
    }

    public static boolean isString(SimpleNode ast) {
        if (ast instanceof Str) {
            return true;
        }
        return false;
    }

    /**
     * @param node the node to analyze
     * @return whether it is a wild import
     */
    public static boolean isWildImport(ImportFrom node) {
        return node.names.length == 0;
    }

    /**
     * @param node the node to analyze
     * @return whether it is an alias import
     */
    public static boolean isAliasImport(ImportFrom node) {
        return node.names.length > 0;
    }

    public List<IToken> getTokens() {
        return this.tokens;
    }

    /**
     * This method transverses the ast and returns a list of found tokens.
     *
     * @param ast
     * @param which
     * @param state
     * @param name
     * @param onlyAllowTokensIn__all__: only used when checking global tokens: if true, if a token named __all__ is available,
     * only the classes that have strings that match in __all__ are available.
     * @return
     * @throws Exception
     */
    public static List<IToken> getTokens(SimpleNode ast, int which, String moduleName, ICompletionState state,
            boolean onlyAllowTokensIn__all__, IPythonNature nature) {
        AbstractVisitor modelVisitor;
        if (which == INNER_DEFS) {
            modelVisitor = new InnerModelVisitor(moduleName, state, nature);
        } else {
            modelVisitor = new GlobalModelVisitor(which, moduleName, onlyAllowTokensIn__all__, nature);
        }

        if (ast != null) {
            try {
                ast.accept(modelVisitor);
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
            modelVisitor.finishVisit();
            return modelVisitor.tokens;
        } else {
            return new ArrayList<IToken>();
        }
    }

    /**
     * This method traverses the ast and returns a model visitor that has the list of found tokens (and other related info, such as __all__, etc.)
     */
    public static GlobalModelVisitor getGlobalModuleVisitorWithTokens(SimpleNode ast, int which, String moduleName,
            ICompletionState state, boolean onlyAllowTokensIn__all__, IPythonNature nature) {
        if (which == INNER_DEFS) {
            throw new RuntimeException("Only globals for getting the GlobalModelVisitor");
        }
        GlobalModelVisitor modelVisitor = new GlobalModelVisitor(which, moduleName, onlyAllowTokensIn__all__,
                nature);

        if (ast != null) {
            try {
                ast.accept(modelVisitor);
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
            modelVisitor.finishVisit();
            return modelVisitor;
        } else {
            return modelVisitor;
        }
    }

    /**
     * This method is available so that subclasses can do some post-processing before the tokens are actually
     * returned.
     */
    protected void finishVisit() {
        /**Empty**/
    }

}
