/*
 *
 *  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
 *
 *      http://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.flex.compiler.internal.targets;

import java.io.File;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;
import java.util.Set;

import org.apache.flex.compiler.common.DependencyType;
import org.apache.flex.compiler.common.DependencyTypeSet;
import org.apache.flex.compiler.config.RSLSettings;
import org.apache.flex.compiler.exceptions.LibraryCircularDependencyException;
import org.apache.flex.compiler.internal.projects.FlexProject;
import org.apache.flex.compiler.targets.ITargetSettings;
import org.apache.flex.swc.ISWC;

/**
 * Class that contains information about RSLs used by a flex application SWF.
 * <p>
 * The information about RSLs is computed from {@link ITargetSettings} and
 * {@code FlexApplicationFrame1Info}.
 */
final class FlexRSLInfo
{
    FlexRSLInfo(FlexApplicationFrame1Info frame1Info, FlexProject flexProject, ITargetSettings targetSettings)
    {
        this.frame1Info = frame1Info;
        this.flexProject = flexProject;
        this.targetSettings = targetSettings;
        requiredRSLs = new ArrayList<RSLSettings>();
        placeholderRSLs = new ArrayList<RSLSettings>();
        
        // The required RSLs are put in "requiredRSLs", the others are put in "placeholderRSLs".
        final List<RSLSettings> cdRSLs = targetSettings.getRuntimeSharedLibraryPath();
        
        final Set<String> unusedRSLs = getUnusedRSLs(cdRSLs);
        
        for (RSLSettings rslSettings : cdRSLs)
        {
            if (unusedRSLs.contains(rslSettings.getLibraryFile().getAbsolutePath()))
                placeholderRSLs.add(rslSettings);
            else
                requiredRSLs.add(rslSettings);
        }
    }
    
    private final FlexApplicationFrame1Info frame1Info;
    private final ITargetSettings targetSettings;
    private final FlexProject flexProject;
    
    /**
     * {@link RSLSettings} for RSLs that must be loaded for the flex application
     * to load properly.
     */
    final ArrayList<RSLSettings> requiredRSLs;
    
    /**
     * {@link RSLSettings} for RSLs were specified by
     * {@link ITargetSettings#getRuntimeSharedLibraryPath()}, but that were not
     * required by the flex application and were not marked as force load by
     * {@link RSLSettings#isForceLoad()}.
     */
    final ArrayList<RSLSettings> placeholderRSLs;
    
    private Set<String> getUnusedRSLs(List<RSLSettings> cdRSLs)
    {
        if (!targetSettings.removeUnusedRuntimeSharedLibraryPaths())
            return Collections.emptySet();
        
        List<String> unusedRSLs = new LinkedList<String>(); // running list of unused rsls
        Set<String>loadedDownstreamRSLs = new HashSet<String>();  // loaded rsls downstream from first unused rsl
        boolean addDownstreamRSL = false;
        
        // Get unused RSLs and verify that there are no downstream RSLs that have
        // and inheritance dependency on them.
        for (RSLSettings info : cdRSLs)
        {
            String swcPath = info.getLibraryFile().getAbsolutePath();
            
            // skip if the associated swc is filtered from our context.
            if (isSWCFiltered(swcPath))
                continue;

            // skip loading the RSL if it does not contribute any classes to 
            // the application and it is not forced.
            if (!frame1Info.contributingSWCs.contains(swcPath) &&
                !info.isForceLoad())
            {
                unusedRSLs.add(swcPath);
                addDownstreamRSL = true;
            }
            else if (addDownstreamRSL)
            {
                loadedDownstreamRSLs.add(swcPath);
            }
        }

        if (unusedRSLs.size() == 0)
            return Collections.emptySet();
        
        Set<String> requiredInheritanceRSLs = filterBasedOnInheritanceDependencies(unusedRSLs, loadedDownstreamRSLs); 
        unusedRSLs.removeAll(requiredInheritanceRSLs);
        if (unusedRSLs.size() == 0)
            return Collections.emptySet();
        
        return new HashSet<String>(unusedRSLs);
    }
    
    /**
     * Create a set of swcs by filtering a list of swcs, choosing the swcs that
     * contain any inheritance dependencies from a set of filter swcs.
     * 
     * @param swcPaths A list of swc paths to filter against the inheritance dependencies 
     * of the swcPathFilters.
     * @param swcPathsFilter set of swc paths used as the inheritance dependendcy filter.
     * 
     * @return subset of the swcPaths that contain inheritance dependencies
     * found in the swcPathsFilter.
     */
    private Set<String> filterBasedOnInheritanceDependencies(List<String>swcPaths, 
                                                    Set<String>swcPathsFilter) 
    {
        if (swcPathsFilter.isEmpty())
            return Collections.emptySet();
        
        // Loop over the filter swcs to see if they have any inheritance 
        // dependencies on input swcs.
        Set<String> inheritanceDependencies = new HashSet<String>(swcPathsFilter.size());
        DependencyTypeSet inheritanceDependency = DependencyTypeSet.of(DependencyType.INHERITANCE);

        // get the inheritance dependencies of all filter swcs
        for (String swcPath : swcPathsFilter)
        {
            Set<String> swcDependencies;
            try
            {
                swcDependencies = flexProject.computeLibraryDependencies(new File(swcPath), inheritanceDependency);
            }
            catch (LibraryCircularDependencyException e)
            {
                // Should never get a circular dependency when asking for 
                // inheritance dependencies. RSLs could never be loaded in the
                // correct order.
                // Skip this swc and move on to the next.
                assert false : e.getMessage();
                continue;
            }
            
            inheritanceDependencies.addAll(swcDependencies);
        }

        if (inheritanceDependencies.isEmpty())
            return Collections.emptySet();

        // check to see if any of the inheritance dependencies live in the
        // input swcs.
        // move thru the list backwards to we can handle the case where a
        // newly found swc can be used to test an upstream swc.
        Set<String> results = new HashSet<String>();
        for (ListIterator<String> iter = swcPaths.listIterator(swcPaths.size()); iter.hasPrevious();)
        {
            String targetSWC = iter.previous();
            if (inheritanceDependencies.contains(targetSWC))
            {
                results.add(targetSWC);

                // add the found swc to the list of inheritance dependencies.
                if (iter.hasPrevious())
                {
                    try
                    {
                        inheritanceDependencies.addAll(
                                flexProject.computeLibraryDependencies(new File(targetSWC), 
                                        inheritanceDependency));
                    }
                    catch (LibraryCircularDependencyException e)
                    {
                        // Should never get a circular dependency when asking for 
                        // inheritance dependencies. RSLs could never be loaded in the
                        // correct order.
                        // Skip this swc and move on to the next.
                        assert false : e.getMessage();
                    }
                }
            }
        }

        return results;
    }
    
    /** 
     * Check if this SWC should be ignored because its min supported version is
     * greater than the current compatibility version.
     * 
     * @param swcPath
     * @return true if the SWC should be filtered, false otherwise.
     */
    private boolean isSWCFiltered(String swcPath)
    {
        // if compatibility-version is not set then we won't be doing any filtering.
        if (flexProject.getCompatibilityVersion() == null)
            return false;
        
        int compatibilityVersion = flexProject.getCompatibilityVersion();
        ISWC swc = flexProject.getWorkspace().getSWCManager().get(new File(swcPath));
        if (compatibilityVersion < swc.getVersion().getFlexMinSupportedVersionInt())
        {
            return true;
        }
        
        return false;
    }
}