/*
 *
 *  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 java.util.Collection;
import java.util.Collections;
import java.util.LinkedList;

import org.apache.flex.abc.ABCConstants;
import org.apache.flex.abc.ABCEmitter;
import org.apache.flex.abc.instructionlist.InstructionList;
import org.apache.flex.abc.semantics.Name;
import org.apache.flex.compiler.common.DependencyType;
import org.apache.flex.compiler.common.Multiname;
import org.apache.flex.compiler.definitions.IDefinition;
import org.apache.flex.compiler.definitions.INamespaceDefinition;
import org.apache.flex.compiler.definitions.references.INamespaceReference;
import org.apache.flex.compiler.definitions.references.IResolvedQualifiersReference;
import org.apache.flex.compiler.definitions.references.ReferenceFactory;
import org.apache.flex.compiler.internal.abc.ClassGeneratorHelper;
import org.apache.flex.compiler.internal.definitions.ClassDefinition;
import org.apache.flex.compiler.internal.embedding.EmbedData;
import org.apache.flex.compiler.internal.projects.CompilerProject;
import org.apache.flex.compiler.internal.projects.FlexProject;
import org.apache.flex.compiler.internal.projects.DefinitionPriority.BasePriority;
import org.apache.flex.compiler.internal.scopes.ASFileScope;
import org.apache.flex.compiler.internal.scopes.ASProjectScope;
import org.apache.flex.compiler.internal.units.requests.ABCBytesRequestResult;
import org.apache.flex.compiler.internal.units.requests.FileScopeRequestResultBase;
import org.apache.flex.compiler.internal.units.requests.SWFTagsRequestResult;
import org.apache.flex.compiler.internal.units.requests.SyntaxTreeRequestResult;
import org.apache.flex.compiler.problems.ICompilerProblem;
import org.apache.flex.compiler.problems.InternalCompilerProblem;
import org.apache.flex.compiler.scopes.IASScope;
import org.apache.flex.compiler.units.ICompilationUnit;
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.collect.ImmutableSet;
import com.google.common.collect.Iterables;

/**
 * The main compilation unit for a Resource module project. It can compile one
 * or more ResourceBundle files into a class to load them at runtime. This
 * compilation unit type is created in order to setup the correct dependencies
 * in the target.
 */
public class ResourceModuleCompilationUnit extends CompilationUnitBase
{
    /**
     * Qualified name for the module processed by this compilation unit
     */
    private final String qname;
    
    /**
     * List of resource bundle compilation units to include in the generated module
     */
    private final Collection<ICompilationUnit> resourceBundleCompUnits;

    /**
     * Create a {@code ResourceModuleCompilationUnit}.
     * 
     * @param project owner project.
     * @param resourceBundleCompUnits list of resource bundle compilation units
     * to include in this resource module
     * @param basePriority base priority.
     */
    public ResourceModuleCompilationUnit(CompilerProject project, String qname, 
            Collection<ICompilationUnit> resourceBundleCompUnits, BasePriority basePriority)
    {
        super(project, null, basePriority, qname);
        this.resourceBundleCompUnits = resourceBundleCompUnits;
        this.qname = qname;
    }

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

    /**
     * {@code ResourceModuleCompilationUnit} does not provide an AST.
     */
    @Override
    protected ISyntaxTreeRequestResult handleSyntaxTreeRequest() throws InterruptedException
    {
        return new SyntaxTreeRequestResult(-1, Collections.<ICompilerProblem>emptyList());
    }

    /**
     * Synthesize a file scope with a public definition of module main class.
     */
    @Override
    protected IFileScopeRequestResult handleFileScopeRequest() throws InterruptedException
    {
        startProfile(Operation.GET_FILESCOPE);
        
        try 
        {
            final ASFileScope fileScope = new ASFileScope(getProject().getWorkspace(), null);

            Multiname mname = Multiname.crackDottedQName(getProject(), qname);
            INamespaceDefinition packageNS = Iterables.getOnlyElement(mname.getNamespaceSet());

            final ClassDefinition classDefinition = new ClassDefinition(mname.getBaseName(), 
                    (INamespaceReference)packageNS);
            fileScope.addDefinition(classDefinition);

            return new FileScopeRequestResultBase(Collections.<ICompilerProblem> emptySet(), 
                    ImmutableSet.<IASScope> of(fileScope));
        } 
        finally 
        {
            stopProfile(Operation.GET_FILESCOPE);
        }
    }
    
    /**
     * Manually add dependencies to all the classes/interfaces used in the module class 
     * that will be generated for this compilation unit.
     */
    @Override
    protected IOutgoingDependenciesRequestResult handleOutgoingDependenciesRequest () throws InterruptedException
    {
        startProfile(Operation.GET_SEMANTIC_PROBLEMS);
        
        final Collection<ICompilerProblem> problems = Collections.emptyList();
        try
        {
            final FlexProject flexProject = (FlexProject)getProject();
            
            //flex.compiler.support.ResourceModuleBase
            ASProjectScope scope = flexProject.getScope();
            IDefinition def = scope.findDefinitionByName(flexProject.getResourceModuleBaseClass());
            ICompilationUnit resourceModuleBaseCompUnit = scope.getCompilationUnitForDefinition(def);
            flexProject.addDependency(this, resourceModuleBaseCompUnit, DependencyType.INHERITANCE,
                    def.getQualifiedName());
            
            //Add dependency to all the resource bundle compilation units we want to 
            //include in the resource module SWF
            for(ICompilationUnit compUnit : resourceBundleCompUnits)
            {
                flexProject.addDependency(this, compUnit, DependencyType.EXPRESSION);
            }
        }
        catch (Exception t)
        {
            problems.add(new InternalCompilerProblem(t));
        }
        finally
        {
            stopProfile(Operation.GET_SEMANTIC_PROBLEMS);
        }

        return new IOutgoingDependenciesRequestResult()
        {
            @Override
            public ICompilerProblem[] getProblems()
            {
                return problems.toArray(new ICompilerProblem[0]);
            }
        };
    }

    /**
     * Get the ABC byte code generated from this module class. The generated class looks like the following:
     * 
     * --------------------------
     *   package
     *   {
     *       import flex.compiler.support.ResourceModuleBase;
     *       
     *       public class GeneratedResourceModule extends ResourceModuleBase
     *       {
     *           public function GeneratedResourceModule()
     *           {
     *               super(["en_US$core_properties", "en_US$effects_properties"]);
     *           }
     *       }
     *   }
     * -------------------------------------
     */
    @Override
    protected IABCBytesRequestResult handleABCBytesRequest() throws InterruptedException
    {
        startProfile(Operation.GET_ABC_BYTES);
        try
        {
            final FlexProject flexProject = (FlexProject)getProject();
            final Collection<ICompilerProblem> problems = new LinkedList<ICompilerProblem>();
            final ABCEmitter emitter = new ABCEmitter();
            
            byte[] generatedBytes = null;
            try
            {
                //this class extends "flex.compiler.support.ResourceModuleBase"
                IResolvedQualifiersReference resourceModuleBaseRef = ReferenceFactory.packageQualifiedReference(
                        flexProject.getWorkspace(), flexProject.getResourceModuleBaseClass());
                
                //Create constructor instruction list
                InstructionList constructorInstructionList = new InstructionList();
                constructorInstructionList.addInstruction(ABCConstants.OP_getlocal0);
                constructorInstructionList.addInstruction(ABCConstants.OP_pushscope);  
                
                int resourceBundleCount = 0;
                for(ICompilationUnit compUnit : resourceBundleCompUnits)
                {
                    for(IDefinition def : compUnit.getDefinitionPromises())
                    {
                        constructorInstructionList.addInstruction(ABCConstants.OP_pushstring, def.getQualifiedName());
                        resourceBundleCount++;
                    }
                }
                
                constructorInstructionList.addInstruction(ABCConstants.OP_newarray, resourceBundleCount);
                constructorInstructionList.addInstruction(ABCConstants.OP_constructsuper, 1);
                constructorInstructionList.addInstruction(ABCConstants.OP_returnvoid);
                
                ClassGeneratorHelper classGen = new ClassGeneratorHelper(flexProject, emitter,
                        new Name(qname),
                        (ClassDefinition)resourceModuleBaseRef.resolve(flexProject),
                        Collections.<Name> emptyList(), 
                        constructorInstructionList);

                classGen.finishScript();
                generatedBytes = emitter.emit();
            }
            catch (Exception ex)
            {
                problems.add(new InternalCompilerProblem(ex));
            }

            return new ABCBytesRequestResult(generatedBytes, problems.toArray(new ICompilerProblem[0]), Collections.<EmbedData>emptySet());
        }
        finally
        {
            stopProfile(Operation.GET_ABC_BYTES);
        }
    }

    @Override
    protected ISWFTagsRequestResult handleSWFTagsRequest() throws InterruptedException
    {
        startProfile(Operation.GET_SWF_TAGS);        
        try 
        {
            final IABCBytesRequestResult abcResult = getABCBytesRequest().get();

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

}
