////////////////////////////////////////////////////////////////////////////////
// checkstyle: Checks Java source code for adherence to a set of rules.
// Copyright (C) 2001-2015 the original author or authors.
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
////////////////////////////////////////////////////////////////////////////////
package edu.cmu.cs.vbc.prog.checkstyle.api;

import antlr.collections.AST;

/**
 * Contains utility methods for working on scope.
 *
 * @author Oliver Burn
 * @version 1.0
 */
public final class ScopeUtils
{
    ///CLOVER:OFF
    /** prevent instantiation */
    private ScopeUtils()
    {
    }
    ///CLOVER:ON

    /**
     * Returns the Scope specified by the modifier set.
     *
     * @param aMods root node of a modifier set
     * @return a <code>Scope</code> value
     */
    public static Scope getScopeFromMods(DetailAST aMods)
    {
        Scope retVal = Scope.PACKAGE; // default scope
        for (AST token = aMods.getFirstChild();
            token != null;
            token = token.getNextSibling())
        {
            if ("public".equals(token.getText())) {
                retVal = Scope.PUBLIC;
                break;
            }
            else if ("protected".equals(token.getText())) {
                retVal = Scope.PROTECTED;
                break;
            }
            else if ("private".equals(token.getText())) {
                retVal = Scope.PRIVATE;
                break;
            }
        }
        return retVal;
    }

    /**
     * Returns the scope of the surrounding "block".
     * @param aAST the node to return the scope for
     * @return the Scope of the surrounding block
     */
    public static Scope getSurroundingScope(DetailAST aAST)
    {
        Scope retVal = null;
        for (DetailAST token = aAST.getParent();
             token != null;
             token = token.getParent())
        {
            final int type = token.getType();
            if ((type == TokenTypes.CLASS_DEF)
                || (type == TokenTypes.INTERFACE_DEF)
                || (type == TokenTypes.ANNOTATION_DEF)
                || (type == TokenTypes.ENUM_DEF))
            {
                final DetailAST mods =
                    token.findFirstToken(TokenTypes.MODIFIERS);
                final Scope modScope = ScopeUtils.getScopeFromMods(mods);
                if ((retVal == null) || (retVal.isIn(modScope))) {
                    retVal = modScope;
                }
            }
            else if (type == TokenTypes.LITERAL_NEW) {
                retVal = Scope.ANONINNER;
                break; //because Scope.ANONINNER is not in any other Scope
            }
        }

        return retVal;
    }

    /**
     * Returns whether a node is directly contained within an interface block.
     *
     * @param aAST the node to check if directly contained within an interface
     * block
     * @return a <code>boolean</code> value
     */
    public static boolean inInterfaceBlock(DetailAST aAST)
    {
        boolean retVal = false;

        // Loop up looking for a containing interface block
        for (DetailAST token = aAST.getParent();
             token != null;
             token = token.getParent())
        {
            final int type = token.getType();
            if ((type == TokenTypes.CLASS_DEF)
                || (type == TokenTypes.ENUM_DEF)
                || (type == TokenTypes.ANNOTATION_DEF))
            {
                break; // in a class, enum or annotation
            }
            else if (type == TokenTypes.LITERAL_NEW) {
                break; // inner implementation
            }
            else if (type == TokenTypes.INTERFACE_DEF) {
                retVal = true;
                break;
            }
        }

        return retVal;
    }

    /**
     * Returns whether a node is directly contained within an annotation block.
     *
     * @param aAST the node to check if directly contained within an annotation
     * block
     * @return a <code>boolean</code> value
     */
    public static boolean inAnnotationBlock(DetailAST aAST)
    {
        boolean retVal = false;

        // Loop up looking for a containing interface block
        for (DetailAST token = aAST.getParent();
             token != null;
             token = token.getParent())
        {
            final int type = token.getType();
            if ((type == TokenTypes.CLASS_DEF)
                || (type == TokenTypes.ENUM_DEF)
                || (type == TokenTypes.INTERFACE_DEF))
            {
                break; // in a class, enum or interface
            }
            else if (type == TokenTypes.LITERAL_NEW) {
                break; // inner implementation
            }
            else if (type == TokenTypes.ANNOTATION_DEF) {
                retVal = true;
                break;
            }
        }

        return retVal;
    }

    /**
     * Returns whether a node is directly contained within an interface or
     * annotation block.
     *
     * @param aAST the node to check if directly contained within an interface
     * or annotation block
     * @return a <code>boolean</code> value
     */
    public static boolean inInterfaceOrAnnotationBlock(DetailAST aAST)
    {
        return inInterfaceBlock(aAST) || inAnnotationBlock(aAST);
    }

    /**
     * Returns whether a node is directly contained within an enum block.
     *
     * @param aAST the node to check if directly contained within an enum
     * block
     * @return a <code>boolean</code> value
     */
    public static boolean inEnumBlock(DetailAST aAST)
    {
        boolean retVal = false;

        // Loop up looking for a containing interface block
        for (DetailAST token = aAST.getParent();
             token != null;
             token = token.getParent())
        {
            final int type = token.getType();
            if ((type == TokenTypes.INTERFACE_DEF)
                || (type == TokenTypes.ANNOTATION_DEF)
                || (type == TokenTypes.CLASS_DEF))
            {
                break; // in an interface, annotation or class
            }
            else if (type == TokenTypes.LITERAL_NEW) {
                break; // inner implementation, enums can't be inner classes
            }
            else if (type == TokenTypes.ENUM_DEF) {
                retVal = true;
                break;
            }
        }

        return retVal;
    }

    /**
     * Returns whether the scope of a node is restricted to a code block.
     * A code block is a method or constructor body, or a initialiser block.
     *
     * @param aAST the node to check
     * @return a <code>boolean</code> value
     */
    public static boolean inCodeBlock(DetailAST aAST)
    {
        boolean retVal = false;

        // Loop up looking for a containing code block
        for (DetailAST token = aAST.getParent();
             token != null;
             token = token.getParent())
        {
            final int type = token.getType();
            if ((type == TokenTypes.METHOD_DEF)
                || (type == TokenTypes.CTOR_DEF)
                || (type == TokenTypes.INSTANCE_INIT)
                || (type == TokenTypes.STATIC_INIT))
            {
                retVal = true;
                break;
            }
        }

        return retVal;
    }

    /**
     * Returns whether a node is contained in the outer most type block.
     *
     * @param aAST the node to check
     * @return a <code>boolean</code> value
     */
    public static boolean isOuterMostType(DetailAST aAST)
    {
        boolean retVal = true;
        for (DetailAST parent = aAST.getParent();
             parent != null;
             parent = parent.getParent())
        {
            if ((parent.getType() == TokenTypes.CLASS_DEF)
                || (parent.getType() == TokenTypes.INTERFACE_DEF)
                || (parent.getType() == TokenTypes.ANNOTATION_DEF)
                || (parent.getType() == TokenTypes.ENUM_DEF))
            {
                retVal = false;
                break;
            }
        }

        return retVal;
    }

    /**
     * Determines whether a node is a local variable definition.
     * I.e. if it is declared in a code block, a for initializer,
     * or a catch parameter.
     * @param aAST the node to check.
     * @return whether aAST is a local variable definition.
     */
    public static boolean isLocalVariableDef(DetailAST aAST)
    {
        // variable declaration?
        if (aAST.getType() == TokenTypes.VARIABLE_DEF) {
            final DetailAST parent = aAST.getParent();
            if (parent != null) {
                final int type = parent.getType();
                return (type == TokenTypes.SLIST)
                    || (type == TokenTypes.FOR_INIT)
                    || (type == TokenTypes.FOR_EACH_CLAUSE);
            }
        }
        // catch parameter?
        else if (aAST.getType() == TokenTypes.PARAMETER_DEF) {
            final DetailAST parent = aAST.getParent();
            if (parent != null) {
                return (parent.getType() == TokenTypes.LITERAL_CATCH);
            }
        }
        return false;
    }
}
