////////////////////////////////////////////////////////////////////////////////
// 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.checks.imports;

import com.google.common.collect.Lists;

import java.util.LinkedList;
import java.util.List;

/**
 * Represents the a tree of guards for controlling whether packages are allowed
 * to be used. Each instance must have a single parent or be the root node.
 * Each instance may have zero or more children.
 *
 * @author Oliver Burn
 */
class PkgControl
{
    /** List of {@link Guard} objects to check. */
    private final LinkedList<Guard> guards = Lists.newLinkedList();
    /** List of children {@link PkgControl} objects. */
    private final List<PkgControl> children = Lists.newArrayList();
    /** The parent. Null indicates we are the root node. */
    private final PkgControl parent;
    /** The full package name for the node. */
    private final String fullPackage;

    /**
     * Construct a root node.
     * @param pkgName the name of the package.
     */
    PkgControl(final String pkgName)
    {
        assert pkgName != null;
        parent = null;
        fullPackage = pkgName;
    }

    /**
     * Construct a child node.
     * @param parent the parent node.
     * @param subPkg the sub package name.
     */
    PkgControl(final PkgControl parent, final String subPkg)
    {
        assert parent != null;
        assert subPkg != null;
        this.parent = parent;
        fullPackage = parent.getFullPackage() + "." + subPkg;
        parent.children.add(this);
    }

    /**
     * Adds a guard to the node.
     * @param thug the guard to be added.
     */
    void addGuard(final Guard thug)
    {
        guards.addFirst(thug);
    }

    /**
     * @return the full package name represented by the node.
     */
    String getFullPackage()
    {
        return fullPackage;
    }

    /**
     * Search down the tree to locate the finest match for a supplied package.
     * @param forPkg the package to search for.
     * @return the finest match, or null if no match at all.
     */
    PkgControl locateFinest(final String forPkg)
    {
        // Check if we are a match.
        // This algormithm should be improved to check for a trailing "."
        // or nothing following.
        if (!forPkg.startsWith(getFullPackage())) {
            return null;
        }

        // Check if any of the children match.
        for (PkgControl pc : children) {
            final PkgControl match = pc.locateFinest(forPkg);
            if (match != null) {
                return match;
            }
        }

        // No match so I am the best there is.
        return this;
    }

    /**
     * Returns whether a package is allowed to be used. The algorithm checks
     * with the current node for a result, and if none is found then calls
     * its parent looking for a match. This will recurse looking for match.
     * If there is no clear result then {@link AccessResult#UNKNOWN} is
     * returned.
     * @param forImport the package to check on.
     * @param inPkg the package doing the import.
     * @return an {@link AccessResult}.
     */
    AccessResult checkAccess(final String forImport, final String inPkg)
    {
        final AccessResult retVal = localCheckAccess(forImport, inPkg);
        if (retVal != AccessResult.UNKNOWN) {
            return retVal;
        }
        else if (parent == null) {
            // we are the top, so default to not allowed.
            return AccessResult.DISALLOWED;
        }

        return parent.checkAccess(forImport, inPkg);
    }

    /**
     * Checks whether any of the guards for this node control access to
     * a specified package.
     * @param forImport the package to check.
     * @param inPkg the package doing the import.
     * @return an {@link AccessResult}.
     */
    private AccessResult localCheckAccess(final String forImport,
        final String inPkg)
    {
        for (Guard g : guards) {
            // Check if a Guard is only meant to be applied locally.
            if (g.isLocalOnly() && !fullPackage.equals(inPkg)) {
                continue;
            }
            final AccessResult result = g.verifyImport(forImport);
            if (result != AccessResult.UNKNOWN) {
                return result;
            }
        }
        return AccessResult.UNKNOWN;
    }
}
