////////////////////////////////////////////////////////////////////////////////
// checkstyle: Checks Java source code for adherence to a set of rules.
// Copyright (C) 2001-2017 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 com.puppycrawl.tools.checkstyle.checks;

import java.util.Deque;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.Map;
import java.util.Queue;
import java.util.Set;

import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
import com.puppycrawl.tools.checkstyle.api.DetailAST;
import com.puppycrawl.tools.checkstyle.api.TokenTypes;
import com.puppycrawl.tools.checkstyle.utils.ScopeUtils;

/**
 * Abstract class for checks which need to collect information about
 * declared members/parameters/variables.
 * @deprecated Checkstyle will not support abstract checks anymore. Use
 *             {@link AbstractCheck} instead.
 * @author o_sukhodolsky
 * @noinspection AbstractClassNeverImplemented
 */
@Deprecated
public abstract class AbstractDeclarationCollector extends AbstractCheck {
    /**
     * Tree of all the parsed frames.
     */
    private Map<DetailAST, LexicalFrame> frames;

    /**
     * Frame for the currently processed AST.
     */
    private LexicalFrame current;

    @Override
    public void beginTree(DetailAST rootAST) {
        final Deque<LexicalFrame> frameStack = new LinkedList<>();
        frameStack.add(new GlobalFrame());

        frames = new HashMap<>();

        DetailAST curNode = rootAST;
        while (curNode != null) {
            collectDeclarations(frameStack, curNode);
            DetailAST toVisit = curNode.getFirstChild();
            while (curNode != null && toVisit == null) {
                endCollectingDeclarations(frameStack, curNode);
                toVisit = curNode.getNextSibling();
                if (toVisit == null) {
                    curNode = curNode.getParent();
                }
            }
            curNode = toVisit;
        }
    }

    @Override
    public void visitToken(DetailAST ast) {
        switch (ast.getType()) {
            case TokenTypes.CLASS_DEF :
            case TokenTypes.INTERFACE_DEF :
            case TokenTypes.ENUM_DEF :
            case TokenTypes.ANNOTATION_DEF :
            case TokenTypes.SLIST :
            case TokenTypes.METHOD_DEF :
            case TokenTypes.CTOR_DEF :
                current = frames.get(ast);
                break;
            default :
                // do nothing
        }
    }

    /**
     * Parse the next AST for declarations.
     *
     * @param frameStack Stack containing the FrameTree being built
     * @param ast AST to parse
     */
    private static void collectDeclarations(Deque<LexicalFrame> frameStack,
        DetailAST ast) {
        final LexicalFrame frame = frameStack.peek();
        switch (ast.getType()) {
            case TokenTypes.VARIABLE_DEF :
                collectVariableDeclarations(ast, frame);
                break;
            case TokenTypes.PARAMETER_DEF :
                final DetailAST parameterAST = ast.findFirstToken(TokenTypes.IDENT);
                frame.addName(parameterAST.getText());
                break;
            case TokenTypes.CLASS_DEF :
            case TokenTypes.INTERFACE_DEF :
            case TokenTypes.ENUM_DEF :
            case TokenTypes.ANNOTATION_DEF :
                final DetailAST classAST = ast.findFirstToken(TokenTypes.IDENT);
                frame.addName(classAST.getText());
                frameStack.addFirst(new ClassFrame(frame));
                break;
            case TokenTypes.SLIST :
                frameStack.addFirst(new BlockFrame(frame));
                break;
            case TokenTypes.METHOD_DEF :
                final String name = ast.findFirstToken(TokenTypes.IDENT).getText();
                if (frame instanceof ClassFrame) {
                    final DetailAST mods =
                            ast.findFirstToken(TokenTypes.MODIFIERS);
                    if (mods.branchContains(TokenTypes.LITERAL_STATIC)) {
                        ((ClassFrame) frame).addStaticMethod(name);
                    }
                    else {
                        ((ClassFrame) frame).addInstanceMethod(name);
                    }
                }
                frameStack.addFirst(new MethodFrame(frame));
                break;
            case TokenTypes.CTOR_DEF :
                frameStack.addFirst(new MethodFrame(frame));
                break;
            default:
                // do nothing
        }
    }

    /**
     * Collect Variable Declarations.
     * @param ast variable token
     * @param frame current frame
     */
    private static void collectVariableDeclarations(DetailAST ast, LexicalFrame frame) {
        final String name =
                ast.findFirstToken(TokenTypes.IDENT).getText();
        if (frame instanceof ClassFrame) {
            final DetailAST mods =
                    ast.findFirstToken(TokenTypes.MODIFIERS);
            if (ScopeUtils.isInInterfaceBlock(ast)
                    || mods.branchContains(TokenTypes.LITERAL_STATIC)) {
                ((ClassFrame) frame).addStaticMember(name);
            }
            else {
                ((ClassFrame) frame).addInstanceMember(name);
            }
        }
        else {
            frame.addName(name);
        }
    }

    /**
     * End parsing of the AST for declarations.
     *
     * @param frameStack Stack containing the FrameTree being built
     * @param ast AST that was parsed
     */
    private void endCollectingDeclarations(Queue<LexicalFrame> frameStack,
        DetailAST ast) {
        switch (ast.getType()) {
            case TokenTypes.CLASS_DEF :
            case TokenTypes.INTERFACE_DEF :
            case TokenTypes.ENUM_DEF :
            case TokenTypes.ANNOTATION_DEF :
            case TokenTypes.SLIST :
            case TokenTypes.METHOD_DEF :
            case TokenTypes.CTOR_DEF :
                frames.put(ast, frameStack.poll());
                break;
            default :
                // do nothing
        }
    }

    /**
     * Check if given name is a name for class field in current environment.
     * @param name a name to check
     * @return true is the given name is name of member.
     */
    protected final boolean isClassField(String name) {
        final LexicalFrame frame = findFrame(name);
        return frame instanceof ClassFrame
                && ((ClassFrame) frame).hasInstanceMember(name);
    }

    /**
     * Check if given name is a name for class method in current environment.
     * @param name a name to check
     * @return true is the given name is name of method.
     */
    protected final boolean isClassMethod(String name) {
        final LexicalFrame frame = findFrame(name);
        return frame instanceof ClassFrame
                && ((ClassFrame) frame).hasInstanceMethod(name);
    }

    /**
     * Find frame containing declaration.
     * @param name name of the declaration to find
     * @return LexicalFrame containing declaration or null
     */
    private LexicalFrame findFrame(String name) {
        if (current == null) {
            return null;
        }
        else {
            return current.getIfContains(name);
        }
    }

    /**
     * A declaration frame.
     * @author Stephen Bloch
     */
    private static class LexicalFrame {
        /** Set of name of variables declared in this frame. */
        private final Set<String> varNames;
        /**
         * Parent frame.
         */
        private final LexicalFrame parent;

        /**
         * Constructor -- invokable only via super() from subclasses.
         *
         * @param parent parent frame
         */
        protected LexicalFrame(LexicalFrame parent) {
            this.parent = parent;
            varNames = new HashSet<>();
        }

        /** Add a name to the frame.
         * @param nameToAdd the name we're adding
         */
        private void addName(String nameToAdd) {
            varNames.add(nameToAdd);
        }

        /** Check whether the frame contains a given name.
         * @param nameToFind the name we're looking for
         * @return whether it was found
         */
        protected boolean contains(String nameToFind) {
            return varNames.contains(nameToFind);
        }

        /** Check whether the frame contains a given name.
         * @param nameToFind the name we're looking for
         * @return whether it was found
         */
        private LexicalFrame getIfContains(String nameToFind) {
            LexicalFrame frame = null;

            if (contains(nameToFind)) {
                frame = this;
            }
            else if (parent != null) {
                frame = parent.getIfContains(nameToFind);
            }
            return frame;
        }
    }

    /**
     * The global frame; should hold only class names.
     * @author Stephen Bloch
     */
    private static class GlobalFrame extends LexicalFrame {

        /**
         * Constructor for the root of the FrameTree.
         */
        protected GlobalFrame() {
            super(null);
        }
    }

    /**
     * A frame initiated at method definition; holds parameter names.
     * @author Stephen Bloch
     */
    private static class MethodFrame extends LexicalFrame {
        /**
         * Creates method frame.
         * @param parent parent frame
         */
        protected MethodFrame(LexicalFrame parent) {
            super(parent);
        }
    }

    /**
     * A frame initiated at class definition; holds instance variable
     * names.  For the present, I'm not worried about other class names,
     * method names, etc.
     * @author Stephen Bloch
     */
    private static class ClassFrame extends LexicalFrame {
        /** Set of name of instance members declared in this frame. */
        private final Set<String> instanceMembers;
        /** Set of name of instance methods declared in this frame. */
        private final Set<String> instanceMethods;
        /** Set of name of variables declared in this frame. */
        private final Set<String> staticMembers;
        /** Set of name of static methods declared in this frame. */
        private final Set<String> staticMethods;

        /**
         * Creates new instance of ClassFrame.
         * @param parent parent frame
         */
        ClassFrame(LexicalFrame parent) {
            super(parent);
            instanceMembers = new HashSet<>();
            instanceMethods = new HashSet<>();
            staticMembers = new HashSet<>();
            staticMethods = new HashSet<>();
        }

        /**
         * Adds static member's name.
         * @param name a name of static member of the class
         */
        public void addStaticMember(final String name) {
            staticMembers.add(name);
        }

        /**
         * Adds static method's name.
         * @param name a name of static method of the class
         */
        public void addStaticMethod(final String name) {
            staticMethods.add(name);
        }

        /**
         * Adds instance member's name.
         * @param name a name of instance member of the class
         */
        public void addInstanceMember(final String name) {
            instanceMembers.add(name);
        }

        /**
         * Adds instance method's name.
         * @param name a name of instance method of the class
         */
        public void addInstanceMethod(final String name) {
            instanceMethods.add(name);
        }

        /**
         * Checks if a given name is a known instance member of the class.
         * @param name a name to check
         * @return true is the given name is a name of a known
         *         instance member of the class
         */
        public boolean hasInstanceMember(final String name) {
            return instanceMembers.contains(name);
        }

        /**
         * Checks if a given name is a known instance method of the class.
         * @param name a name to check
         * @return true is the given name is a name of a known
         *         instance method of the class
         */
        public boolean hasInstanceMethod(final String name) {
            return instanceMethods.contains(name);
        }

        @Override
        protected boolean contains(String nameToFind) {
            return super.contains(nameToFind)
                    || instanceMembers.contains(nameToFind)
                    || instanceMethods.contains(nameToFind)
                    || staticMembers.contains(nameToFind)
                    || staticMethods.contains(nameToFind);
        }
    }

    /**
     * A frame initiated on entering a statement list; holds local variable
     * names.  For the present, I'm not worried about other class names,
     * method names, etc.
     * @author Stephen Bloch
     */
    private static class BlockFrame extends LexicalFrame {

        /**
         * Creates block frame.
         * @param parent parent frame
         */
        protected BlockFrame(LexicalFrame parent) {
            super(parent);
        }
    }
}
