/*
 *
 *  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.units;


import static com.google.common.collect.Collections2.transform;

import java.util.Collection;
import java.util.HashSet;
import org.apache.flex.compiler.css.ICSSDocument;
import org.apache.flex.compiler.filespecs.IFileSpecification;
import org.apache.flex.compiler.internal.as.codegen.CodeGeneratorManager;
import org.apache.flex.compiler.internal.definitions.ClassDefinition;
import org.apache.flex.compiler.internal.parsing.as.OffsetCue;
import org.apache.flex.compiler.internal.parsing.as.OffsetLookup;
import org.apache.flex.compiler.internal.parsing.mxml.MXMLScopeBuilder;
import org.apache.flex.compiler.internal.projects.CompilerProject;
import org.apache.flex.compiler.internal.projects.DefinitionPriority;
import org.apache.flex.compiler.internal.projects.FlexProject;
import org.apache.flex.compiler.internal.scopes.MXMLFileScope;
import org.apache.flex.compiler.internal.scopes.TypeScope;
import org.apache.flex.compiler.internal.tree.mxml.MXMLDocumentNode;
import org.apache.flex.compiler.internal.tree.mxml.MXMLFileNode;
import org.apache.flex.compiler.internal.tree.mxml.MXMLTreeBuilder;
import org.apache.flex.compiler.internal.units.requests.ASFileScopeRequestResult;
import org.apache.flex.compiler.internal.units.requests.SWFTagsRequestResult;
import org.apache.flex.compiler.internal.units.requests.SyntaxTreeRequestResult;
import org.apache.flex.compiler.mxml.IMXMLData;
import org.apache.flex.compiler.mxml.IMXMLDataManager;
import org.apache.flex.compiler.problems.ICompilerProblem;
import org.apache.flex.compiler.problems.UnexpectedExceptionProblem;
import org.apache.flex.compiler.tree.mxml.IMXMLFileNode;
import org.apache.flex.compiler.tree.mxml.IMXMLStyleNode;
import org.apache.flex.compiler.units.requests.IABCBytesRequestResult;
import org.apache.flex.compiler.units.requests.IFileScopeRequestResult;
import org.apache.flex.compiler.units.requests.IOutgoingDependenciesRequestResult;
import org.apache.flex.compiler.units.requests.ISWFTagsRequestResult;
import org.apache.flex.compiler.units.requests.ISyntaxTreeRequestResult;
import com.google.common.base.Function;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;

public class MXMLCompilationUnit extends CompilationUnitBase
{
    public MXMLCompilationUnit(CompilerProject project, String path,
                               DefinitionPriority.BasePriority basePriority,
                               int order,
                               String qname)
    {
        super(project, path, basePriority, qname);
        this.qname = qname;
        ((DefinitionPriority)getDefinitionPriority()).setOrder(order);
    }

    // The fully-qualified name of the one externally-visible definition
    // expected to be found in this compilation unit, or null if none is expected.
    // This qname is determined from the name of the file
    // and the file's location relative to the source path.
    private final String qname;

    @Override
    public UnitType getCompilationUnitType()
    {
        return UnitType.MXML_UNIT;
    }

    @Override
    protected ISyntaxTreeRequestResult handleSyntaxTreeRequest() throws InterruptedException
    {
        // Fulfill other requests before profiling this request.       
        final IFileScopeRequestResult fileScopeRequestResult = getFileScopeRequest().get();
        final MXMLFileScope fileScope = (MXMLFileScope)fileScopeRequestResult.getScopes()[0];
        
        startProfile(Operation.GET_SYNTAX_TREE);
        
        final IMXMLData mxmlData = getMXMLData();
        
        final Collection<ICompilerProblem> problems = new HashSet<ICompilerProblem>();
        
        // Create an MXMLTreeBuilder to store all the contextual information
        // that we need to build an MXML tree.
        final MXMLTreeBuilder builder =
            new MXMLTreeBuilder(this, getFileSpecificationGetter(),  qname, mxmlData, fileScope, problems);

        // Use the MXMLTreeBuilder to build an MXMLFileNode (the root of an MXML AST)
        // from the MXMLData (the MXML DOM) and the MXMLFileScope.
        final IMXMLFileNode fileNode = builder.build();

        try
        {
            // TODO This belongs in MXMLDocumentNode.
            MXMLDocumentNode documentNode = (MXMLDocumentNode)fileNode.getDocumentNode();
            if (documentNode != null)
            {
                ClassDefinition mainClassDefinition = fileScope.getMainClassDefinition();
                if (mainClassDefinition != null)
                {
                    TypeScope mainClassScope = (TypeScope)mainClassDefinition.getContainedScope();
                    documentNode.setScope(mainClassScope);
                }
            }
            
            // Start CSS semantic analysis. 
            final Function<IMXMLStyleNode, ICSSDocument> parseMXMLStyleNode = new Function<IMXMLStyleNode, ICSSDocument>()
            {
                @Override
                public ICSSDocument apply(IMXMLStyleNode mxmlStyleNode)
                {
                    // This method will trigger the CSS parser to parse the CSS fragment.
                    return mxmlStyleNode.getCSSDocument(problems);
                }
            };
            final Collection<ICSSDocument> cssDocumentList =
                    transform(fileNode.getStyleNodes(), parseMXMLStyleNode);

            // This method will resolve dependencies introduced by the CSS fragment, and add the
            // dependee's to the dependency graph. This is done at the last step in MXML tree 
            // building phase. 
            // - It can't be done in MXML semantic analysis, because MXML code generation doesn't
            //   depend on MXML semantic analysis; 
            // - It can't be done in MXML code generation either, because the "problems" in that phase are 
            //   generated from inside ABCGenerator.
            updateStyleCompilationUnitDependencies(
                    fileNode.getCSSCompilationSession(), 
                    fileScope,
                    cssDocumentList, 
                    problems);
            
            fileNode.getCSSCompilationSession().cssDocuments.addAll(cssDocumentList);
        }
        catch (Exception e)
        {
            //something went wrong, so log it.  
            problems.add(new UnexpectedExceptionProblem(e));
        }
        finally
        {
            stopProfile(Operation.GET_SYNTAX_TREE);
        }

        getProject().addToASTCache(fileNode);
        return new SyntaxTreeRequestResult(fileNode, ImmutableSet.<String>copyOf(fileScope.getSourceDependencies()), fileNode.getIncludeTreeLastModified(), problems);
    }

    @Override
    protected IFileScopeRequestResult handleFileScopeRequest() throws InterruptedException
    {
        startProfile(Operation.GET_FILESCOPE);
        try
        {
            final IMXMLData mxmlData = getMXMLData();
                    
            final MXMLScopeBuilder scopeBuilder = new MXMLScopeBuilder(this, getFileSpecificationGetter(), mxmlData, qname, getAbsoluteFilename());
            MXMLFileScope fileScope = scopeBuilder.build();
            final ImmutableList<OffsetCue> offsetCueList = scopeBuilder.getIncludeHandler().getOffsetCueList();
            final OffsetLookup offsetLookup = new OffsetLookup(offsetCueList);
            fileScope.setOffsetLookup(offsetLookup);
            final Collection<ICompilerProblem> problemCollection = scopeBuilder.getProblems();
            final IFileSpecification rootFileSpec = getRootFileSpecification();

            getProject().getWorkspace().addIncludedFilesToCompilationUnit(this, fileScope.getSourceDependencies());

            return new ASFileScopeRequestResult(getDefinitionPromises(), getDefinitionPriority(), problemCollection, fileScope, rootFileSpec);
        }
        finally
        {
            stopProfile(Operation.GET_FILESCOPE);
        }
    }
    
    @Override
    protected IABCBytesRequestResult handleABCBytesRequest() throws InterruptedException
    {
        // Fulfill other requests before profiling this request.
        final ISyntaxTreeRequestResult syntaxTreeRequestResult = getSyntaxTreeRequest().get();
        final MXMLFileNode fileNode = (MXMLFileNode)syntaxTreeRequestResult.getAST();
        final CompilerProject project = getProject();
        
        startProfile(Operation.GET_ABC_BYTES);
        try
        {
            IABCBytesRequestResult result = CodeGeneratorManager.getCodeGenerator().generate(
                project.getWorkspace().getExecutorService(),
                project.getUseParallelCodeGeneration(),
                getFilenameNoPath(),
                fileNode,
                getProject(),
                isInvisible(),
                getEncodedDebugFiles());
            return result;
        }
        finally
        {
            stopProfile(Operation.GET_ABC_BYTES);
        }
    }

    @Override
    protected ISWFTagsRequestResult handleSWFTagsRequest() throws InterruptedException
    {
        // Fulfill other requests before profiling this request.
        final IABCBytesRequestResult abc = getABCBytesRequest().get();

        startProfile(Operation.GET_SWF_TAGS);

        try
        {
            return new SWFTagsRequestResult(abc.getABCBytes(), qname, abc.getEmbeds());
        }
        finally
        {
            stopProfile(Operation.GET_SWF_TAGS);
        }
    }

    @Override
    protected IOutgoingDependenciesRequestResult handleOutgoingDependenciesRequest () throws InterruptedException
    {
        // Fulfill other requests before profiling this request.
        final ISyntaxTreeRequestResult syntaxTreeRequestResult = getSyntaxTreeRequest().get();
        final MXMLFileNode fileNode = (MXMLFileNode)syntaxTreeRequestResult.getAST();

        startParsingImports(fileNode);

        startProfile(Operation.GET_SEMANTIC_PROBLEMS);
        try
        {
            /* do the codegen now, because we don't discover SDK databinding dependencies
             * until codegen. Long term we probably want to go in this direction anyway,
             * since semantic analysis and codegen may get folded together for other reasons.
             */
            getABCBytesRequest().get(); 
            Collection<ICompilerProblem> problems = new HashSet<ICompilerProblem>();

            updateEmbedCompilationUnitDependencies(fileNode.getEmbedNodes(), problems);
            
            getABCBytesRequest().get();

            // Resolve all references to definitions.
            //fileNode.resolveRefs(problems, getProject());

            return new IOutgoingDependenciesRequestResult()
            {
                @Override
                public ICompilerProblem[] getProblems()
                {
                    return IOutgoingDependenciesRequestResult.NO_PROBLEMS;
                }
            };
        }
        finally
        {
            stopProfile(Operation.GET_SEMANTIC_PROBLEMS);
        }
    }
    
    private IMXMLData getMXMLData()
    {
        // Get the DOM-like MXMLData for the file.
        // If its not already in the Workspace's MXMLDataManager,
        // the MXML file will be parsed.
        final IMXMLDataManager mxmlDataManager = getProject().getWorkspace().getMXMLDataManager();
        final IFileSpecification rootFileSpec = getRootFileSpecification();
        final IMXMLData mxmlData = mxmlDataManager.get(rootFileSpec);
        return mxmlData;
    }
    
    @Override
    public FlexProject getProject()
    {
        return (FlexProject)super.getProject();
    }
}
