//
//  ========================================================================
//  Copyright (c) 1995-2017 Mort Bay Consulting Pty. Ltd.
//  ------------------------------------------------------------------------
//  All rights reserved. This program and the accompanying materials
//  are made available under the terms of the Eclipse Public License v1.0
//  and Apache License v2.0 which accompanies this distribution.
//
//      The Eclipse Public License is available at
//      http://www.eclipse.org/legal/epl-v10.html
//
//      The Apache License v2.0 is available at
//      http://www.opensource.org/licenses/apache2.0.php
//
//  You may elect to redistribute this code under either of these licenses.
//  ========================================================================
//

package org.eclipse.jetty.webapp;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.function.Consumer;

import org.eclipse.jetty.util.TopologicalSort;
import org.eclipse.jetty.util.resource.Resource;

/**
 * Relative Fragment Ordering
 * <p>Uses a {@link TopologicalSort} to order the fragments.</p>
 */
public class RelativeOrdering implements Ordering
{
    protected MetaData _metaData;
    
    public RelativeOrdering (MetaData metaData)
    {
        _metaData = metaData;
    }

    @Override
    public List<Resource> order(List<Resource> jars)
    {    
        TopologicalSort<Resource> sort = new TopologicalSort<>();
        List<Resource> sorted = new ArrayList<>(jars);
        Set<Resource> others = new HashSet<>();
        Set<Resource> before_others = new HashSet<>();
        Set<Resource> after_others = new HashSet<>();
        
        // Pass 1: split the jars into 'before others', 'others' or 'after others'
        for (Resource jar : jars)
        {
            FragmentDescriptor fragment=_metaData.getFragment(jar);
            
            if (fragment == null)
                others.add(jar);
            else
            {
                switch (fragment.getOtherType())
                {
                    case None:
                        others.add(jar);
                        break;
                    case Before:
                        before_others.add(jar);
                        break;
                    case After:
                        after_others.add(jar);
                        break;
                } 
            }
        }
        
        // Pass 2: Add sort dependencies for each jar
        Set<Resource> referenced = new HashSet<>();
        for (Resource jar : jars)
        {
            FragmentDescriptor fragment=_metaData.getFragment(jar);

            if (fragment != null)
            {
                // Add each explicit 'after' ordering as a sort dependency
                // and remember that the dependency has been referenced.
                for (String name: fragment.getAfters())
                {
                    Resource after=_metaData.getJarForFragment(name);
                    sort.addDependency(jar,after);
                    referenced.add(after);
                }

                // Add each explicit 'before' ordering as a sort dependency
                // and remember that the dependency has been referenced.
                for (String name: fragment.getBefores())
                {
                    Resource before=_metaData.getJarForFragment(name);
                    sort.addDependency(before,jar);
                    referenced.add(before);
                }

                // handle the others
                switch (fragment.getOtherType())
                {
                    case None:
                        break;
                    case Before:
                        // Add a dependency on this jar from all 
                        // jars in the 'others' and 'after others' sets, but
                        // exclude any jars we have already explicitly 
                        // referenced above.
                        Consumer<Resource> add_before = other -> 
                        {
                            if (!referenced.contains(other)) 
                                sort.addDependency(other,jar);
                        };
                        others.forEach(add_before);
                        after_others.forEach(add_before);
                        break;
                        
                    case After:
                        // Add a dependency from this jar to all 
                        // jars in the 'before others' and 'others' sets, but
                        // exclude any jars we have already explicitly 
                        // referenced above.
                        Consumer<Resource> add_after = other -> 
                        {
                            if (!referenced.contains(other)) 
                                sort.addDependency(jar,other);
                        };
                        before_others.forEach(add_after);
                        others.forEach(add_after);
                        break;
                } 
            }
            referenced.clear();
        }

        // sort the jars according to the added dependencies
        sort.sort(sorted);
        
        return sorted;
    }
}