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

import java.util.Deque;
import java.util.LinkedList;

/**
 * Represents a tree of import rules for controlling whether packages or
 * classes are allowed to be used. Each instance must have a single parent or
 * be the root node.
 */
abstract class AbstractImportControl {

    /** List of {@link AbstractImportRule} objects to check. */
    private final Deque<AbstractImportRule> rules = new LinkedList<>();
    /** The parent. Null indicates we are the root node. */
    private final AbstractImportControl parent;
    /** Strategy in a case if matching allow/disallow rule was not found. */
    private final MismatchStrategy strategyOnMismatch;

    /**
     * Construct a child node.
     * @param parent the parent node.
     * @param strategyOnMismatch strategy in a case if matching allow/disallow rule was not found.
     */
    protected AbstractImportControl(AbstractImportControl parent,
            MismatchStrategy strategyOnMismatch) {
        this.parent = parent;
        this.strategyOnMismatch = strategyOnMismatch;
    }

    /**
     * Search down the tree to locate the finest match for a supplied package.
     * @param forPkg the package to search for.
     * @param forFileName the file name to search for.
     * @return the finest match, or null if no match at all.
     */
    public abstract AbstractImportControl locateFinest(String forPkg, String forFileName);

    /**
     * Check for equality of this with pkg.
     * @param pkg the package to compare with.
     * @param fileName the file name to compare with.
     * @return if it matches.
     */
    protected abstract boolean matchesExactly(String pkg, String fileName);

    /**
     * Adds an {@link AbstractImportRule} to the node.
     * @param rule the rule to be added.
     */
    protected void addImportRule(AbstractImportRule rule) {
        rules.addLast(rule);
    }

    /**
     * Returns whether a package or class is allowed to be imported.
     * 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 inPkg the package doing the import.
     * @param inFileName the file name doing the import.
     * @param forImport the import to check on.
     * @return an {@link AccessResult}.
     */
    public AccessResult checkAccess(String inPkg, String inFileName, String forImport) {
        final AccessResult result;
        final AccessResult returnValue = localCheckAccess(inPkg, inFileName, forImport);
        if (returnValue != AccessResult.UNKNOWN) {
            result = returnValue;
        }
        else if (parent == null) {
            if (strategyOnMismatch == MismatchStrategy.ALLOWED) {
                result = AccessResult.ALLOWED;
            }
            else {
                result = AccessResult.DISALLOWED;
            }
        }
        else {
            if (strategyOnMismatch == MismatchStrategy.ALLOWED) {
                result = AccessResult.ALLOWED;
            }
            else if (strategyOnMismatch == MismatchStrategy.DISALLOWED) {
                result = AccessResult.DISALLOWED;
            }
            else {
                result = parent.checkAccess(inPkg, inFileName, forImport);
            }
        }
        return result;
    }

    /**
     * Checks whether any of the rules for this node control access to
     * a specified package or file.
     * @param inPkg the package doing the import.
     * @param inFileName the file name doing the import.
     * @param forImport the import to check on.
     * @return an {@link AccessResult}.
     */
    private AccessResult localCheckAccess(String inPkg, String inFileName, String forImport) {
        AccessResult localCheckAccessResult = AccessResult.UNKNOWN;
        for (AbstractImportRule importRule : rules) {
            // Check if an import rule is only meant to be applied locally.
            if (!importRule.isLocalOnly() || matchesExactly(inPkg, inFileName)) {
                final AccessResult result = importRule.verifyImport(forImport);
                if (result != AccessResult.UNKNOWN) {
                    localCheckAccessResult = result;
                    break;
                }
            }
        }
        return localCheckAccessResult;
    }

}
