/*
 *
 *  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.royale.compiler.internal.projects;

import java.io.File;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.WeakHashMap;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

import org.apache.royale.compiler.common.DependencyType;
import org.apache.royale.compiler.common.DependencyTypeSet;
import org.apache.royale.compiler.common.Multiname;
import org.apache.royale.compiler.constants.IASLanguageConstants;
import org.apache.royale.compiler.constants.IASLanguageConstants.BuiltinType;
import org.apache.royale.compiler.definitions.IDefinition;
import org.apache.royale.compiler.definitions.IFunctionDefinition;
import org.apache.royale.compiler.definitions.INamespaceDefinition;
import org.apache.royale.compiler.definitions.ITypeDefinition;
import org.apache.royale.compiler.internal.definitions.ClassDefinition;
import org.apache.royale.compiler.internal.definitions.NamespaceDefinition;
import org.apache.royale.compiler.internal.embedding.EmbedData;
import org.apache.royale.compiler.internal.parsing.as.IProjectConfigVariables;
import org.apache.royale.compiler.internal.scopes.ASProjectScope;
import org.apache.royale.compiler.internal.scopes.ASScope;
import org.apache.royale.compiler.internal.scopes.ASScopeCache;
import org.apache.royale.compiler.internal.targets.AppSWFTarget;
import org.apache.royale.compiler.internal.targets.Target;
import org.apache.royale.compiler.internal.units.EmbedCompilationUnit;
import org.apache.royale.compiler.internal.workspaces.Workspace;
import org.apache.royale.compiler.problems.ICompilerProblem;
import org.apache.royale.compiler.problems.MissingBuiltinProblem;
import org.apache.royale.compiler.projects.ICompilerProject;
import org.apache.royale.compiler.scopes.IASScope;
import org.apache.royale.compiler.targets.ISWFTarget;
import org.apache.royale.compiler.targets.ITargetProgressMonitor;
import org.apache.royale.compiler.targets.ITargetSettings;
import org.apache.royale.compiler.tree.as.IASNode;
import org.apache.royale.compiler.units.ICompilationUnit;
import org.apache.royale.compiler.units.requests.IFileScopeRequestResult;
import org.apache.royale.compiler.units.requests.IRequest;
import org.apache.royale.utils.FilenameNormalization;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;

/**
 * Abstract class used to share implementation of some ICompilerProject methods
 * across multiple concrete implementation classes.
 */
public abstract class CompilerProject implements ICompilerProject
{
    private final ASProjectScope projectScope;
    private final Workspace workspace;
    protected Set<Target> targets;
    private final ReadWriteLock unfoundDependenciesLock;
    private final Map<String, Map<ICompilationUnit, Object>> unfoundDefinitionDependencies;
    protected Collection<ICompilerProblem> problems;
    
    /**
     * These are files that compilation units have tried to find and failed. At the moment these are
     * Embedded files and included files. Should these files be added to the Builder workspace, the project
     * will be notified and can in turn re-compiler the units that are missing these files
     */
    private final Map<String, Map<ICompilationUnit, Object>> unfoundReferencedSourceFileDependencies;
    private final Map<EmbedData, EmbedCompilationUnit> embedCompilationUnits;

    /**
     * Helper class to create new Scope Caches on the fly, when the scope cache map misses
     */
    static class ScopeCacheLoader extends CacheLoader<ASScope, ASScopeCache>
    {
        CompilerProject project;
        public ScopeCacheLoader(CompilerProject project)
        {
            this.project = project;
        }
        @Override
        public ASScopeCache load(ASScope scope)
        {
            ASScopeCache cache = new ASScopeCache(project, scope);
            project.projectScope.addScopeToCompilationUnitScopeList(scope);
            return cache;
        }
    }

    /**
     * Map that holds caches for each scope in the project - uses a Concurrent Map, with weak keys so that the caches
     * will go away once the corresponding scope has been gc'ed.  Uses soft values so the caches may be collected
     * if the VM is running out of memory
     */
    private LoadingCache<ASScope, ASScopeCache> scopeCaches = CacheBuilder.newBuilder()
        .weakKeys()
        .softValues()
        .build(new ScopeCacheLoader(this));
 
    /** This thread local is to avoid every thread contending for access to the scopeCaches map, which is shared
     *  across the entire Project.
     *  When a scope cache is requested, we first check the thread local cache.  If the entry is not present in
     *  the thread local, then we hit the shared scopeCaches map.  The result from the scopeCaches map is then also
     *  cached in the thread local.  This way, most of the time, we'll find the scope cache in the thread local, and 
     *  won't have to do any locking. 
     */    
    private ThreadLocal<Map<ASScope, WeakReference<ASScopeCache>> > threadLocalScopeCache; 
    
    /**
     * Dependency graph is used to keep track of which {@link ICompilationUnit}
     * 's are in this project. The dependency graph is in this shared base class
     * instead of being in {@link ASProject} because {@link ICompilationUnit}
     * implementations will want to update the dependency graph and
     * {@link ICompilationUnit} implementations should be able to behave the
     * same irrespective of what type of {@link CompilerProject} they are in.
     */
    protected final DependencyGraph dependencyGraph;
    
    /**
     * If true, use parallel code generation of method bodies.
     */
    private boolean useParallelCodeGen;
    
    /**
     * If true, use function inlining in code generation.
     */
    private boolean enableInlining;

    private final boolean useAS3;

    /**
     * used to track config variables and return config information back to clients, such as the parser
     */
    private final ConfigManager configManager;

    public CompilerProject(Workspace workspace, boolean useAS3)
    {
        this.workspace = workspace;
        targets = new HashSet<Target>();
        unfoundDependenciesLock = new ReentrantReadWriteLock();
        unfoundDefinitionDependencies = new HashMap<String, Map<ICompilationUnit, Object>>();
        unfoundReferencedSourceFileDependencies = new HashMap<String, Map<ICompilationUnit, Object>>();
        embedCompilationUnits = new HashMap<EmbedData, EmbedCompilationUnit>();
        dependencyGraph = new DependencyGraph();
        // ** TODO Instantiate real scope object.
        projectScope = initProjectScope(this);
        this.useAS3 = useAS3;
        initThreadLocalCaches();
        configManager = new ConfigManager();
        useParallelCodeGen = false;
        enableInlining = false;
        workspace.addProject(this);
    }
    
    /**
     * Init the project scope.  Allow subclasses to contribute their own if needed.  Default will create a new {@link ASProjectScope}
     * @param project the project to build the scope from
     * @return a new {@link ASProjectScope}
     */
    protected ASProjectScope initProjectScope(CompilerProject project) {
        return new ASProjectScope(project);
    }
 
    /**
     * Init the thread local cache - will reset the thread local cache to it's initial, empty state.
     */
    private void initThreadLocalCaches()
    {
        threadLocalScopeCache = new ThreadLocal<Map<ASScope, WeakReference<ASScopeCache>>>()
        {
            @Override
            protected Map<ASScope, WeakReference<ASScopeCache>> initialValue()
            {
                // Use a WeakHashMap with WeakRefs as values - the ASScopes and ASScopeCaches
                // will be kept alive or not by the Project
                return new WeakHashMap<ASScope, WeakReference<ASScopeCache>>();
            }
        };
    }

    @Override
    public ASProjectScope getScope()
    {
        return this.projectScope;
    }

    @Override
    public Workspace getWorkspace()
    {
        return workspace;
    }

    @Override
    public List<ICompilationUnit> getReachableCompilationUnitsInSWFOrder(Collection<ICompilationUnit> roots)
    {
        return dependencyGraph.topologicalSort(roots);
    }

    /**
     * Updates the {@link ASProjectScope} to contain reference to public and
     * internal definitions in those compilation units.
     * 
     * @param units {@link ICompilationUnit}'s to add to the project.
     */
    public void updatePublicAndInternalDefinitions(Collection<ICompilationUnit> units) throws InterruptedException
    {
        if (units.isEmpty())
            return;

        ArrayList<IRequest<IFileScopeRequestResult, ICompilationUnit>> scopeRequests = new ArrayList<IRequest<IFileScopeRequestResult, ICompilationUnit>>(units.size());
        for (ICompilationUnit unit : units)
        {
            ICompilerProject unitProject = unit.getProject();
            assert (unitProject == this) || (unitProject == null) : "ICompilationUnit should be in this project or not in any project";
            if (unitProject == this)
                scopeRequests.add(unit.getFileScopeRequest());
        }

        scopeCaches.invalidateAll();
        initThreadLocalCaches();
        
        projectScope.addAllExternallyVisibleDefinitions(scopeRequests);
    }

    /**
     * Adds compilation units to the project and updates the public and private
     * definitions. Eventually this method should be removed, but it is
     * convenient for testing because it allows custom created
     * {@link ICompilationUnit}'s to be injected into a project.
     * 
     * @param compilationUnits A collection of compilation units.
     */
    public void addCompilationUnitsAndUpdateDefinitions(Collection<ICompilationUnit> compilationUnits) throws InterruptedException
    {
        addCompilationUnits(compilationUnits);
        updatePublicAndInternalDefinitions(compilationUnits);
    }

    public void addEmbedCompilationUnit(EmbedCompilationUnit unit) throws InterruptedException
    {
        dependencyGraph.addEmbedCompilationUnit(unit);
        workspace.addCompilationUnit(unit);
        embedCompilationUnits.put(unit.getEmbedData(), unit);

        updatePublicAndInternalDefinitions(Collections.<ICompilationUnit>singletonList(unit));
    }

    /**
     * Add a {@link ICompilationUnit} to the project. Calling this method does not
     * trigger the {@link ICompilationUnit} to be compiled.
     * 
     * @param unit {@link ICompilationUnit} to add
     */
    public void addCompilationUnit(ICompilationUnit unit)
    {
        assert !(unit instanceof EmbedCompilationUnit) : "Embed compilation unit should be added with addEmbedCompilationUnit!";
        dependencyGraph.addCompilationUnit(unit);
        workspace.addCompilationUnit(unit);
    }

    /**
     * Add compilation units to the project. Calling this method does not
     * trigger the compilation units to be compiled.
     * 
     * @param units compilation units
     */
    public void addCompilationUnits(Collection<ICompilationUnit> units)
    {
        for (ICompilationUnit compilationUnit : units)
        {
            addCompilationUnit(compilationUnit);
        }
    }
    
    /**
     * Removes a {@link ICompilationUnit} from the project.
     * @param unit {@link ICompilationUnit} to remove.
     */
    public void removeCompilationUnit(ICompilationUnit unit)
    {
        dependencyGraph.removeCompilationUnit(unit);
        workspace.removeCompilationUnit(unit);
        unit.clearProject();

        if (unit instanceof EmbedCompilationUnit)
        {
            EmbedData embedData = ((EmbedCompilationUnit)unit).getEmbedData();
            embedCompilationUnits.remove(embedData);
        }
    }

    /**
     * Don't call this from production code. Normally library managers take care of this.
     * This function is only for unit tests.
     */
    public void unitTestingEntryPointForRemovingCompilationUnit(ICompilationUnit unit)
    {  
        removeCompilationUnits( Collections.singleton(unit) );
    }

    /**
     * Removes a set of {@link ICompilationUnit}'s from the project.
     * @param unitsToRemove {@link ICompilationUnit}'s to remove.
     */
    public void removeCompilationUnits(Collection<ICompilationUnit> unitsToRemove)
    {
        if ((unitsToRemove == null) || (unitsToRemove.size() == 0))
            return;

        // TODO pre-remove invalidation
        // Factor this into a method and un-comment at some point!
//        Set<ICompilationUnit> workSet = new HashSet<ICompilationUnit>(unitsToRemove);
//        Set<ICompilationUnit> visited = new HashSet<ICompilationUnit>(unitsToRemove.size());
//        EnumSet<DependencyGraph.DependencyType> inheritanceTypeSet =
//            EnumSet.of(DependencyGraph.DependencyType.INHERITANCE);
//        while (workSet.size() >= 0)
//        {
//            ICompilationUnit unit = workSet.iterator().next();
//            workSet.remove(unit);
//            if (!visited.contains(unit))
//            {
//                visited.add(unit);
//                unit.invalidate(DependencyGraph.DependencyType.INHERITANCE);
//                workSet.addAll(dependencyGraph.getDirectReverseDependencies(unit, inheritanceTypeSet));
//            }
//        }
        
        // TODO add code behind the invalidate all above or here to
        // invalidation name resolution caches.

        // unhook all the compilation units to remove from the project scope.
        projectScope.removeCompilationUnits(unitsToRemove);

        for (ICompilationUnit unit : unitsToRemove)
        {
            removeCompilationUnit(unit);
        }
    }

    @Override
    public Collection<ICompilationUnit> getCompilationUnits()
    {
        return dependencyGraph.getCompilationUnits();
    }

    @Override
    public Collection<ICompilationUnit> getCompilationUnits(String filename)
    {
        return workspace.getCompilationUnits(FilenameNormalization.normalize(filename), this);
    }

    @Override
    public Collection<ICompilationUnit> getIncludingCompilationUnits(String filename)
    {
        return workspace.getIncludingCompilationUnits(FilenameNormalization.normalize(filename), this);
    }

    public void addUnfoundDefinitionDependency(String definitionBaseName, ICompilationUnit compilationUnit)
    {
        unfoundDependenciesLock.writeLock().lock();
        try
        {
            Map<ICompilationUnit, Object> dependentUnits = unfoundDefinitionDependencies.get(definitionBaseName);
            if (dependentUnits == null)
            {
                dependentUnits = new WeakHashMap<ICompilationUnit, Object>();
                unfoundDefinitionDependencies.put(definitionBaseName, dependentUnits);
            }

            dependentUnits.put(compilationUnit, null);            
        }
        finally
        {
            unfoundDependenciesLock.writeLock().unlock();
        }
    }

    public Set<ICompilationUnit> getDependenciesOnUnfoundDefinition(String definitionBaseName)
    {
        unfoundDependenciesLock.readLock().lock();
        try
        {
            Map<ICompilationUnit, Object> dependentUnits = unfoundDefinitionDependencies.get(definitionBaseName);
            if (dependentUnits == null)
            {
                dependentUnits = Collections.emptyMap();
            }

            return dependentUnits.keySet();
        }
        finally
        {
            unfoundDependenciesLock.readLock().unlock();
        }
    }

    private void removeAnyUnfoundDefinitionDependency(ICompilationUnit compilationUnit)
    {
        for (Map<ICompilationUnit, Object> dependentUnits : unfoundDefinitionDependencies.values())
        {
            dependentUnits.remove(compilationUnit);
        }
    }

    /**
     * Notify project that a compilation unit could not find a referenced file.
     * 
     * "Referenced" here means that the compilation unit references the file in some way other than the normal
     * language level manner. For example, embedded and included files are "referenced", whereas files that
     * defined referenced types are not.
     * 
     * Calling this function will allow incremental re-compiation to be kicked off when/if the required
     * file comes into existence.
     * 
     * @param sourceFilename The full path to the required file
     * @param compilationUnit The compilation unit that requires the referenced file 
     */
    public void addUnfoundReferencedSourceFileDependency(String sourceFilename, ICompilationUnit compilationUnit)
    {
        unfoundDependenciesLock.writeLock().lock();
        try
        {
            String filenameNoPath = new File(sourceFilename).getName();
            Map<ICompilationUnit, Object> dependentUnits = unfoundReferencedSourceFileDependencies.get(filenameNoPath);
            if (dependentUnits == null)
            {
                dependentUnits = new WeakHashMap<ICompilationUnit, Object>();
                unfoundReferencedSourceFileDependencies.put(filenameNoPath, dependentUnits);
            }

            dependentUnits.put(compilationUnit, null);
        }
        finally
        {
            unfoundDependenciesLock.writeLock().unlock();
        }
    }

    /**
     * gets the set of all compilation units that have an unsatisfied reference to a given file.
     * @param sourceFilename The path to the file.
     */
    public Set<ICompilationUnit> getDependenciesOnUnfoundReferencedSourceFile(String sourceFilename)
    {
        unfoundDependenciesLock.readLock().lock();
        try
        {
            String filenameNoPath = new File(sourceFilename).getName();
            Map<ICompilationUnit, Object> dependentUnits = unfoundReferencedSourceFileDependencies.get(filenameNoPath);
            if (dependentUnits == null)
            {
                dependentUnits = Collections.emptyMap();
            }
            return dependentUnits.keySet();
        }
        finally
        {
            unfoundDependenciesLock.readLock().unlock();
        }
    }

    private void removeAnyUnfoundReferencedSourceFileDependency(ICompilationUnit compilationUnit)
    {
        for (Map<ICompilationUnit, Object> dependentUnits : unfoundReferencedSourceFileDependencies.values())
        {
            dependentUnits.remove(compilationUnit);
        }
    }

    public void removeAnyUnfoundDependencies(ICompilationUnit compilationUnit)
    {
        // we could probably get away without locking when a remove is done, as
        // this is only called from a call to clean() on a compilation unit, and
        // that should only happen in a single thread.  but let's just play it safe
        // and lock anyway in case someone in future calls this method from somewhere else.
        unfoundDependenciesLock.writeLock().lock();
        try
        {
            removeAnyUnfoundDefinitionDependency(compilationUnit);
            removeAnyUnfoundReferencedSourceFileDependency(compilationUnit);
        }
        finally
        {
            unfoundDependenciesLock.writeLock().unlock();
        }
    }

    public Set<ICompilationUnit> getDependenciesOnDefinition(String definitionBaseName)
    {
        Set<ICompilationUnit> dependentUnits = new HashSet<ICompilationUnit>();
        Set<ICompilationUnit> definingUnits = projectScope.getCompilationUnitsByDefinitionName(definitionBaseName);
        DependencyTypeSet dependencyTypes = DependencyTypeSet.allOf();
        for (ICompilationUnit definingUnit : definingUnits)
        {
            dependentUnits.addAll(dependencyGraph.getDirectReverseDependencies(definingUnit, dependencyTypes));
        }

        return dependentUnits;
    }

    @Override
    public void clean()
    {
        Map<ICompilerProject, Set<ICompilationUnit>> cusToUpdate = new HashMap<ICompilerProject, Set<ICompilationUnit>>();

        workspace.startIdleState();
        try
        {
            Collection<ICompilationUnit> compilationUnits = getCompilationUnits();

            for (ICompilationUnit compilationUnit : compilationUnits)
            {
                compilationUnit.clean(null, cusToUpdate, true);
            }

            scopeCaches.invalidateAll();
            initThreadLocalCaches();
        }
        finally
        {
            workspace.endIdleState(cusToUpdate);
        }
    }

    @Override
    public void delete()
    {
        Collection<ICompilationUnit> compilationUnits = getCompilationUnits();
        for (ICompilationUnit compilationUnit : compilationUnits)
        {
            workspace.removeCompilationUnit(compilationUnit);
        }

        workspace.deleteProject(this);
    }

    @Override
    public ISWFTarget createSWFTarget(ITargetSettings targetSettings, ITargetProgressMonitor progressMonitor) throws InterruptedException
    {
        return new AppSWFTarget(this, targetSettings, progressMonitor);
    }
    
    /**
     * Adds a dependency from one {@link ICompilationUnit} to another
     * {@link ICompilationUnit}.
     * 
     * @param from {@link ICompilationUnit} that depends on "to".
     * @param to {@link ICompilationUnit} that is depended on by "from".
     * @param dt The types of dependencies to add.
     * @param qname The qname of the target dependency added
     */
    public void addDependency(ICompilationUnit from, ICompilationUnit to, DependencyTypeSet dt, String qname)
    {
        dependencyGraph.addDependency(from, to, dt, qname);
    }

    /**
     * Adds a dependency from one {@link ICompilationUnit} to another
     * {@link ICompilationUnit}.
     * 
     * @param from {@link ICompilationUnit} that depends on "to".
     * @param to {@link ICompilationUnit} that is depended on by "from".
     * @param dt The type of dependency to add.
     * @param qname The qname of the target dependency added
     */
    public void addDependency(ICompilationUnit from, ICompilationUnit to, DependencyType dt, String qname)
    {
        dependencyGraph.addDependency(from, to, dt, qname);
    }

    /**
     * Adds an anonymous dependency from one {@link ICompilationUnit} to another
     * {@link ICompilationUnit}. This dependency will not be used in the link report.
     * 
     * @param from {@link ICompilationUnit} that depends on "to".
     * @param to {@link ICompilationUnit} that is depended on by "from".
     * @param dt The type of dependency to add.
     */
    public void addDependency(ICompilationUnit from, ICompilationUnit to, DependencyType dt)
    {
        dependencyGraph.addDependency(from, to, dt);
    }
    
    /**
     * Removes the outgoing dependencies of a collection of {@link ICompilationUnit}.
     * 
     * @param units {@link ICompilationUnit} to remove dependencies from.
     */
    public void removeDependencies(Collection<ICompilationUnit> units)
    {
        for (ICompilationUnit unit : units)
            dependencyGraph.removeDependencies(unit);
    }
    
    /**
     * Gets the set of dependencies referenced by the specified
     * {@link ICompilationUnit}. in the absence of wierd things like
     * defaults/theme css, this is just the set of {@link ICompilationUnit}'s
     * that define definitions directly referenced by the specified
     * {@link ICompilationUnit}.
     * <p>
     * This method can be overridden by subclasses to add additional
     * dependencies that are not in the dependency graph.
     * 
     * @param cu {@link ICompilationUnit} to retrieve dependencies for.
     * @return A new Set of {@link ICompilationUnit}'s that the specified
     * {@link ICompilationUnit} directly depends on.
     */
    public Set<ICompilationUnit> getDependencies(ICompilationUnit cu) throws InterruptedException
    {
        // The process of finding semantic problems updates the dependency graph
        // so wait for semantic analysis pass to finish.
        // TODO possible revisit how we wait for the dependency graph to be updated
        // or at least better document it in ICompilationUnit.
        cu.getOutgoingDependenciesRequest().get();
        return dependencyGraph.getDirectDependencies(cu);
    }
    
    /**
     * Gets project level {@link ICompilerProblem}'s target are not specific to any
     * one target created by the project.
     */
    public abstract void collectProblems(Collection<ICompilerProblem> problems);


    /**
     * Gets the project level {@link ICompilerProblem}'s that come from parsing the
     * config options, from the command line, or a configuration file
     * @param problems  the Collection to add the config problems to.
     */
    protected void collectConfigProblems(Collection<ICompilerProblem> problems)
    {
        problems.addAll(configManager.getProjectConfig(this).getProblems());
    }

    /**
     * Get the cache for a particular scope
     * @param scope     the scope you want the cache for.
     * @return          the ASScopeCache for the scope
     */
    public ASScopeCache getCacheForScope(ASScope scope)
    {
        ASScopeCache scopeCache = null;

        // First check and see if we have the result cached in the thread local cache
        Map<ASScope, WeakReference<ASScopeCache>> cache = threadLocalScopeCache.get();
        WeakReference<ASScopeCache> ref = cache.get(scope);
        scopeCache = ref != null ? ref.get() : null;
        
        if( scopeCache == null )
        {
            // Didn't find it in the thread local, hit the shared cache, and cache those results
            // in the thread local so that we won't have to hit the shared cache next time.
            scopeCache = scopeCaches.getUnchecked(scope);
            cache.put(scope, new WeakReference<ASScopeCache>(scopeCache));
        }
        
        return scopeCache;
    }

    /**
     * Clears all the {@link ASScopeCache}s associated with the specified
     * {@link ICompilationUnit} and removes all the {@link IASScope}s build by
     * the {@link ICompilationUnit} from the list of scopes associated with the
     * {@link ICompilationUnit}. This method should only be called when a
     * {@link ICompilationUnit} is being removed from a project.
     * 
     * @param compilationUnit {@link ICompilationUnit} whose scope caches should
     * be cleared and whose scope list should be emptied.
     */
    public void clearScopeCacheForCompilationUnit(ICompilationUnit compilationUnit)
    {
        Collection<IASScope> relatedScopes = projectScope.clearCompilationUnitScopeList(compilationUnit);
        if (relatedScopes == null)
            return;
        resetScopeCaches(relatedScopes);
    }
    
    /**
     * Resets all the {@link ASScopeCache}s associated with the specified
     * {@link ICompilationUnit}.
     * 
     * @param compilationUnit {@link ICompilationUnit} whose scope caches should
     * be cleared
     */
    public void resetScopeCacheForCompilationUnit(ICompilationUnit compilationUnit)
    {
        Collection<IASScope> relatedScopes = projectScope.getCompilationUnitScopeList(compilationUnit);
        assert relatedScopes != null;
        if (relatedScopes.isEmpty())
            return;
        
        resetScopeCaches(relatedScopes);
    }
    
    private void resetScopeCaches(Iterable<IASScope> scopes)
    {
        assert scopes != null;
        for (IASScope scope : scopes)
        {
            scopeCaches.invalidate(scope);
        }
        initThreadLocalCaches();
    }

    public void addGlobalUsedNamespacesToNamespaceSet(Set<INamespaceDefinition> nsSet)
    {
        nsSet.add(NamespaceDefinition.getPublicNamespaceDefinition());
        if (this.useAS3)
            nsSet.add(NamespaceDefinition.getAS3NamespaceDefinition());
    }

    /**
     * Called by
     * {@link Workspace#fileAdded(org.apache.royale.compiler.filespecs.IFileSpecification)}
     * for each project in the workspace. Each subclass of this class must
     * decide when an added file is interesting or should be ignored.
     * 
     * @param addedFile File that was added.
     * @return true if any new compilation units were created as a result of
     * adding the file, false otherwise.
     */
    public abstract boolean handleAddedFile(File addedFile);

    /**
     * Return the compilation unit related this EmbedData, or it's equivalent.  If
     * there is no equiv., null will be returned
     * @param data The embedding data.
     * @return related EmbedCompilationUnit.  Can be null
     */
    public EmbedCompilationUnit getCompilationUnit(EmbedData data)
    {
        return embedCompilationUnits.get(data);
    }
    
    public final DependencyGraph getDependencyGraph()
    {
        return dependencyGraph;
	}
    /**
     * Sets project config variables for use on this project
     * @see org.apache.royale.compiler.internal.projects.ConfigManager#addConfigVariables(Map)
     * @param map the mapping of config names to their expressions
     */
    public void addConfigVariables(Map<String, String> map) {
        configManager.addConfigVariables(map);
        // TODO: in future we could be smarter about incrementally invalidating
        // the project based on what the config variables affect, but for now
        // just blow away the whole project.
        clean();
    }
    
    /**
     * Sets project config variable for use on this project
     * @see org.apache.royale.compiler.internal.projects.ConfigManager#addConfigVariables(Map)
     * @param namespace the config namespace
     * @param expression the config expression for the namespace 
     */
    public void addConfigVariable(String namespace, String expression) {
        configManager.addConfigVariable(namespace, expression);
        // TODO: in future we could be smarter about incrementally invalidating
        // the project based on what the config variables affect, but for now
        // just blow away the whole project.
        clean();
    }
    
    /**
     * Returns an {@link IProjectConfigVariables} struct that contains all the config data for this project
     * @return an {@link IProjectConfigVariables} object, never null
     */
    public IProjectConfigVariables getProjectConfigVariables() {
        return configManager.getProjectConfig(this);
    }

    @Override
    public IDefinition resolveQNameToDefinition(final String qName)
    {
        assert qName != null : "qName can't be null.";
        final Multiname multiname = Multiname.crackDottedQName(this, qName);
        return getScope().findDefinitionByName(multiname, false);
    }
    
    @Override
    public ICompilationUnit resolveQNameToCompilationUnit(final String qName)
    {
        IDefinition definition = resolveQNameToDefinition(qName);
        if (definition == null)
            return null;

        return projectScope.getCompilationUnitForDefinition(definition);        
    }

    // List of Builtin types that must be present for compilation
    // to succeed.
    private static final BuiltinType[] REQUIRED_BUILTINS = {
        BuiltinType.OBJECT,
        BuiltinType.ARRAY,
        BuiltinType.STRING,
        BuiltinType.ANY_TYPE,
        BuiltinType.VOID,
        BuiltinType.NUMBER,
        BuiltinType.INT,
        BuiltinType.UINT
    };
    
    /**
     * Check for fatal problems, that would prevent us from successfully compiling.
     * Currently this just checks that all of the builtin types are present.
     */
    public Collection<ICompilerProblem> getFatalProblems()
    {
        Collection<ICompilerProblem> fatalProblems = null;

        for(IASLanguageConstants.BuiltinType builtinType : REQUIRED_BUILTINS )
        {
            if (getBuiltinType(builtinType) == null)
            {
                if (fatalProblems == null)
                    fatalProblems = new ArrayList<ICompilerProblem>();

                fatalProblems.add(new MissingBuiltinProblem(builtinType.getName()));
                break;
            }
        }
        return fatalProblems == null ? Collections.<ICompilerProblem>emptyList() : fatalProblems;
    }

    @Override
    public ITypeDefinition getBuiltinType(IASLanguageConstants.BuiltinType type)
    {
        switch( type )
        {
            case OBJECT:
                return projectScope.getObjectDefinition();
            case STRING:
                return projectScope.getStringDefinition();
            case ARRAY:
                return projectScope.getArrayDefinition();
            case XML:
                return projectScope.getXMLDefinition();
            case XMLLIST:
                return projectScope.getXMLListDefinition();
            case BOOLEAN:
                return projectScope.getBooleanDefinition();
            case INT:
                return projectScope.getIntDefinition();
            case UINT:
                return projectScope.getUIntDefinition();
            case NUMBER:
                return projectScope.getNumberDefinition();
            case CLASS:
                return projectScope.getClassDefinition();
            case FUNCTION:
                return projectScope.getFunctionDefinition();
            case NAMESPACE:
                return projectScope.getNamespaceDefinition();
            case VECTOR:
                return projectScope.getVectorDefinition();
            case NULL:
                return ClassDefinition.getNullClassDefinition();
            case VOID:
                return ClassDefinition.getVoidClassDefinition();
            case Undefined:
                return ClassDefinition.getUndefinedClassDefinition();
            case ANY_TYPE:
                return ClassDefinition.getAnyTypeClassDefinition();
        }

        String name = type.getName();
        
        ITypeDefinition definition = (ITypeDefinition)getScope().findDefinitionByName(name);

        // Don't have playerglobal.abc/swc with float yet.
        assert definition != null
                : "Error, builtin type '"+ type.getName()+ "' can't be found!";

        return definition;
    }
    
    @Override
    public IDefinition getUndefinedValue()
    {
        return projectScope.getUndefinedValueDefinition();
    }
    
    /**
     * Gets a boolean that indicates whether or not parallel code generation of method bodies is enabled.
     * @return true if method bodies should be generated in parallel, false otherwise.
     */
    public boolean getUseParallelCodeGeneration()
    {
        return useParallelCodeGen;
    }
    
    @Override
    public void setUseParallelCodeGeneration(boolean useParallelCodeGeneration)
    {
        this.useParallelCodeGen = useParallelCodeGeneration;
    }
    
    @Override
    public Set<ICompilationUnit> getDirectDependencies(ICompilationUnit cu)
    {
        return dependencyGraph.getDirectDependencies(cu);
    }

    @Override
    public Set<ICompilationUnit> getDirectReverseDependencies(ICompilationUnit cu, DependencyTypeSet types)
    {
        return dependencyGraph.getDirectReverseDependencies(cu, types);
    }

    @Override
    public boolean isInliningEnabled()
    {
        return enableInlining;
    }

    /**
     * Set whether or not function inlining is enabled.
     * 
     * @param enableInlining true to enable inlining.
     */
    public void setEnableInlining(boolean enableInlining)
    {
        this.enableInlining = enableInlining;
        clean();
    }
    
    /**
     * Add AST to cache.  By default, not added to any cache.
     * 
     * @param ast The AST.
     */
    public void addToASTCache(IASNode ast)
    {
    }

    /**
     * Override this to permit package aliasing on imports and elsewhere
     * 
     * @param packageName The package name
     */
    public String getActualPackageName(String packageName)
    {
        return packageName;
    }
    
    /**
     * Override this to disambiguate between two ambiguous definitinos
     * 
     * @param scope The current scope.
     * @param name Definition name.
     * @param def1 One possibility.
     * @param def2 The other possibility.
     * @return null if still ambiguous or else def1 or def2.
     */
    public IDefinition doubleCheckAmbiguousDefinition(IASScope scope, String name, IDefinition def1, IDefinition def2)
    {
        return null;
    }

    /**
     * @return collection of compiler problems.
     */
    public Collection<ICompilerProblem> getProblems()
    {
        return problems;
    }

    /**
     * collection of compiler problems.
     */
    public void setProblems(Collection<ICompilerProblem> problems)
    {
        this.problems = problems;
    }
    
    public boolean isCompatibleOverrideReturnType(IFunctionDefinition func, ITypeDefinition overrideDefinition, ITypeDefinition baseDefinition)
    {
        return (baseDefinition == overrideDefinition);
    }
    
    public boolean isValidTypeConversion(IASNode node, IDefinition actualDefinition, IDefinition expectedDefinition, IFunctionDefinition func)
    {
        return false;
    }

	public boolean isCompatibleOverrideParameterType(IFunctionDefinition func,
			ITypeDefinition overrideDefinition, ITypeDefinition baseDefinition,
			int i) {
        return (baseDefinition == overrideDefinition);
	}

}
