/*
 *  Licensed to the Apache Software Foundation (ASF) under one or more
 *  contributor license agreements.  See the NOTICE file distributed with
 *  this work for additional information regarding copyright ownership.
 *  The ASF licenses this file to You under the Apache License, Version 2.0
 *  (the "License"); you may not use this file except in compliance with
 *  the License.  You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 *
 */
package org.apache.ivy.core.sort;

import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
import org.apache.ivy.core.resolve.IvyNode;
import org.apache.ivy.plugins.circular.CircularDependencyException;
import org.apache.ivy.plugins.circular.CircularDependencyStrategy;
import org.apache.ivy.plugins.circular.IgnoreCircularDependencyStrategy;
import org.apache.ivy.plugins.version.VersionMatcher;
import org.apache.ivy.util.Checks;

public class SortEngine {

    private SortEngineSettings settings;

    public SortEngine(SortEngineSettings settings) {
        if (settings == null) {
            throw new NullPointerException("SortEngine.settings can not be null");
        }
        this.settings = settings;
    }

    /**
     * Same as {@link #sortModuleDescriptors(Collection, SortOptions)} but for <code>IvyNode</code>
     * s.
     *
     * @param nodes
     *            a Collection of nodes to sort
     * @param options
     *            Options to use to sort the nodes.
     * @return a List of sorted IvyNode
     * @throws CircularDependencyException
     *             if a circular dependency exists and circular dependency strategy decide to throw
     *             an exception
     */
    public List<IvyNode> sortNodes(Collection<IvyNode> nodes, SortOptions options) {
        /*
         * here we want to use the sort algorithm which work on module descriptors : so we first put
         * dependencies on a map from descriptors to dependency, then we sort the keySet (i.e. a
         * collection of descriptors), then we replace in the sorted list each descriptor by the
         * corresponding dependency
         */

        Map<ModuleDescriptor, List<IvyNode>> dependenciesMap = new LinkedHashMap<>();
        List<IvyNode> nulls = new ArrayList<>();
        for (IvyNode node : nodes) {
            if (node.getDescriptor() == null) {
                nulls.add(node);
            } else {
                List<IvyNode> n = dependenciesMap.get(node.getDescriptor());
                if (n == null) {
                    n = new ArrayList<>();
                    dependenciesMap.put(node.getDescriptor(), n);
                }
                n.add(node);
            }
        }
        List<ModuleDescriptor> list = sortModuleDescriptors(dependenciesMap.keySet(), options);
        final double adjustFactor = 1.3;
        List<IvyNode> ret = new ArrayList<>(
                (int) (list.size() * adjustFactor + nulls.size()));
        // attempt to adjust the size to avoid too much list resizing
        for (ModuleDescriptor md : list) {
            List<IvyNode> n = dependenciesMap.get(md);
            ret.addAll(n);
        }
        ret.addAll(0, nulls);
        return ret;
    }

    /**
     * Sorts the given ModuleDescriptors from the less dependent to the more dependent. This sort
     * ensures that a ModuleDescriptor is always found in the list before all ModuleDescriptors
     * depending directly on it.
     *
     * @param moduleDescriptors
     *            a Collection of ModuleDescriptor to sort
     * @param options
     *            Options to use to sort the descriptors.
     * @return a List of sorted ModuleDescriptors
     * @throws CircularDependencyException
     *             if a circular dependency exists and circular dependency strategy decide to throw
     *             an exception
     */
    public List<ModuleDescriptor> sortModuleDescriptors(
            Collection<ModuleDescriptor> moduleDescriptors, SortOptions options)
            throws CircularDependencyException {
        Checks.checkNotNull(options, "options");
        ModuleDescriptorSorter sorter = new ModuleDescriptorSorter(moduleDescriptors,
                getVersionMatcher(), options.getNonMatchingVersionReporter(),
                options.isUseCircularDependencyStrategy() ? getCircularStrategy()
                        : IgnoreCircularDependencyStrategy.getInstance());
        return sorter.sortModuleDescriptors();
    }

    protected CircularDependencyStrategy getCircularStrategy() {
        return settings.getCircularDependencyStrategy();
    }

    protected VersionMatcher getVersionMatcher() {
        return settings.getVersionMatcher();
    }

}
