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

import java.util.Collection;
import java.util.Collections;
import java.util.EnumSet;
import java.util.List;

import org.apache.royale.compiler.common.DependencyType;
import org.apache.royale.compiler.definitions.IParameterDefinition;
import org.apache.royale.compiler.definitions.references.IReference;
import org.apache.royale.compiler.internal.definitions.DefinitionBase;
import org.apache.royale.compiler.internal.definitions.mxml.MXMLEventHandlerScope;
import org.apache.royale.compiler.internal.parsing.as.ASParser;
import org.apache.royale.compiler.internal.parsing.as.IncludeHandler;
import org.apache.royale.compiler.internal.parsing.as.OffsetLookup;
import org.apache.royale.compiler.internal.projects.RoyaleProject;
import org.apache.royale.compiler.internal.scopes.ASFileScope;
import org.apache.royale.compiler.internal.semantics.PostProcessStep;
import org.apache.royale.compiler.internal.tree.as.NodeBase;
import org.apache.royale.compiler.internal.tree.as.ScopedBlockNode;
import org.apache.royale.compiler.internal.workspaces.Workspace;
import org.apache.royale.compiler.mxml.IMXMLTagAttributeData;
import org.apache.royale.compiler.mxml.IMXMLTagData;
import org.apache.royale.compiler.mxml.IMXMLTextData;
import org.apache.royale.compiler.problems.ICompilerProblem;
import org.apache.royale.compiler.problems.MXMLEmptyEventHandlerProblem;
import org.apache.royale.compiler.scopes.IASScope;
import org.apache.royale.compiler.tree.ASTNodeID;
import org.apache.royale.compiler.tree.as.IASNode;
import org.apache.royale.compiler.tree.as.IImportNode;
import org.apache.royale.compiler.tree.as.IScopedNode;
import org.apache.royale.compiler.tree.mxml.IMXMLClassReferenceNode;
import org.apache.royale.compiler.tree.mxml.IMXMLEventSpecifierNode;
import org.apache.royale.compiler.tree.mxml.IMXMLFileNode;

/**
 * {@code MXMLEventSpecifierNode} represents an MXML event attribute or event
 * child tag.
 * <p>
 * Its child nodes are ActionScript nodes representing the statements to be
 * executed.
 * <p>
 * For example, the MXML code
 * 
 * <pre>
 * &lt;s:Button click="doThis(); doThat()"/&gt;
 * </pre>
 * 
 * or
 * 
 * <pre>
 * &lt;s:Button&gt;
 *     &lt;s:click&gt;
 *     &lt;![CDATA[
 *         doThis();
 *         doThat();
 *     ]]&gt;
 *     &lt;/s:click&gt;
 * &lt;/s:Button&gt;
 * </pre>
 * 
 * produces an AST of the form
 * 
 * <pre>
 * MXMLInstanceNode "spark.components.Button"
 *   MXMLEventSpecifierNode "click"
 *     FunctionCallNode
 *       IdentifierNode "doThis"
 *       ContainerNode
 *     FunctionCallNode
 *       IdentifierNode "doThat"
 *       ContainerNode
 * </pre>
 */
class MXMLEventSpecifierNode extends MXMLSpecifierNodeBase
        implements IMXMLEventSpecifierNode, IScopedNode
{
    private static NodeBase[] NO_AS_NODES = new NodeBase[0];
    
    /**
     * Constructor
     * 
     * @param parent The parent node of this node, or <code>null</code> if there
     * is no parent.
     */
    MXMLEventSpecifierNode(NodeBase parent)
    {
        super(parent);

        // Create a handler scope to hold the 'event' parameter.
        // Although it is attached to this node and not to the symbol table,
        // it points upward to the class scope in the symbol table.
        scope = new MXMLEventHandlerScope(this);
    }

    /**
     * The child nodes, representing the ActionScript statements to be executed
     * to handle the event.
     */
    private NodeBase[] asNodes = NO_AS_NODES;

    /**
     * The scope contained by the autogenerated event handler method. The
     * following eventParameter definition lives in this scope. Since this scope
     * is only needed to process this AST, it is attached to the AST and not
     * part of the symbol table. However, its containedScope points to the class
     * scope in the symbol table.
     */
    private final MXMLEventHandlerScope scope;

    /**
     * This override handles an event attribute such as
     * click="doThis(); doThat()".
     */
    @Override
    protected void initializeFromAttribute(MXMLTreeBuilder builder,
                                           IMXMLTagAttributeData attribute,
                                           MXMLNodeInfo info)
    {
        super.initializeFromAttribute(builder, attribute, info);

        final RoyaleProject project = builder.getProject();
        final Workspace workspace = builder.getWorkspace();
        final Collection<ICompilerProblem> problems = builder.getProblems();

        setSourcePath(attribute.getParent().getParent().getFileSpecification().getPath());

        final int startOffsetLocal = attribute.getValueStart();
        final ASFileScope fileScope = builder.getFileScope();
        final OffsetLookup offsetLookup = fileScope.getOffsetLookup();
        assert offsetLookup != null : "Expected OffsetLookup on FileScope.";
        final String filePath = getContainingFilePath();
        final int[] absoluteOffsets =
                offsetLookup.getAbsoluteOffset(filePath, startOffsetLocal);
        final int startOffsetAbsolute = absoluteOffsets[0];
        final IncludeHandler includeHandler =
                IncludeHandler.createForASTBuilding(builder.getFileSpecificationGetter(), filePath, startOffsetLocal, startOffsetAbsolute);
        includeHandler.setProjectAndCompilationUnit(project, builder.getCompilationUnit());
        String scriptFragment = attribute.getRawValue();
        if (scriptFragment == null)
        {
            // an exception will be thrown if the string is null
            // the missing value will result in a compile error elsewhere
            scriptFragment = "";
        }
        if (scriptFragment.isEmpty())
        {
            MXMLEmptyEventHandlerProblem problem = new MXMLEmptyEventHandlerProblem(attribute);
            problems.add(problem);
        }
        final ScopedBlockNode node = ASParser.parseFragment2(
                scriptFragment,
                filePath,
                0,
                attribute.getValueLine(),
                attribute.getValueColumn(),
                problems,
                workspace,
                builder.getFileNode(),
                scope,
                project.getProjectConfigVariables(),
                EnumSet.of(PostProcessStep.CALCULATE_OFFSETS, PostProcessStep.POPULATE_SCOPE),
                true, //follow includes 
                includeHandler);
        builder.getFileNode().updateIncludeTreeLastModified(includeHandler.getLastModified());
        processHandlerCode(builder, Collections.singletonList(node));
    }

    /**
     * This override is called on each non-whitespace unit of text inside an
     * event tag, such as <click>doThis(); <!-- comment --> doThat();</click>
     * which must compile as if it had been written <click>doThis();
     * doThat();</click> All the text units will be processed later in
     * initializationComplete().
     */
    @Override
    protected void processChildNonWhitespaceUnit(MXMLTreeBuilder builder, IMXMLTagData tag,
                                                 IMXMLTextData text,
                                                 MXMLNodeInfo info)
    {
        // Don't report non-whitespace as a problem.
    }

    /**
     * This override is called on an event tag such as <click>doThis(); <!--
     * comment --> doThat();</click>. It concatenates all the text units to get
     * "doThis();  doThat();" and compiles that as the event handler.
     */
    @Override
    protected void initializationComplete(MXMLTreeBuilder builder, IMXMLTagData tag,
                                          MXMLNodeInfo info)
    {
        String path = builder.getPath();
        IMXMLFileNode fileNode = builder.getFileNode();
        processHandlerCode(builder, processUnitAsAS(
                builder, tag, path, scope, PostProcessStep.POPULATE_SCOPE, fileNode));
    }

    /**
     * Helper method used by the two protected versions of initialize().
     * 
     * @param scripts the {@link ScopedBlockNode} node that makes up the content
     */
    private void processHandlerCode(MXMLTreeBuilder builder, List<ScopedBlockNode> scripts)
    {
        // Populate this handler scope with a definition for the 'event' parameter.
        // The type of this parameter is determined by the [Event] metadata.
        // For example, if the event is
        // [Event(name="click", type="flash.events.MouseEvent")]
        // then the type is flash.events.MouseEvent.
        IReference typeRef = ((DefinitionBase)getDefinition()).getTypeReference();
        scope.buildEventParameter(typeRef);

        // Add an expression dependency on the event type.
        // It doesn't need to be a signature dependency
        // because autogenerated event handlers are inaccessible;
        // they're either in a special private MXML namespace
        // or they're public but have an 'illegal' name.
        typeRef.resolve(builder.getProject(), scope, DependencyType.EXPRESSION, true);

        // Make the statements inside the event handler the children of this node.
        for (ScopedBlockNode script : scripts)
        {
            int n = script.getChildCount();
            asNodes = new NodeBase[n];
            for (int i = 0; i < n; i++)
            {
                NodeBase child = (NodeBase)script.getChild(i);
                asNodes[i] = child;
                child.setParent(this);
            }
        }
    }

    @Override
    public ASTNodeID getNodeID()
    {
        return ASTNodeID.MXMLEventSpecifierID;
    }

    @Override
    public IASNode getChild(int i)
    {
        return asNodes != null ? asNodes[i] : null;
    }

    @Override
    public int getChildCount()
    {
        return asNodes != null ? asNodes.length : 0;
    }

    @Override
    public IASNode[] getASNodes()
    {
        return asNodes;
    }

    @Override
    public IASScope getScope()
    {
        return scope;
    }

    @Override
    public void getAllImports(Collection<String> imports)
    {
        ((MXMLClassDefinitionNode)getClassDefinitionNode()).getAllImports(imports);
    }

    @Override
    public void getAllImportNodes(Collection<IImportNode> imports)
    {
        ((MXMLClassDefinitionNode)getClassDefinitionNode()).getAllImportNodes(imports);
    }

    @Override
    public IParameterDefinition getEventParameterDefinition()
    {
        final IParameterDefinition result = scope.getEventParameterDefinition();
        assert result != null : "Even parameter definition should be built before it is accessed";
        return result;
    }

    @Override
    public boolean needsPublicHandler()
    {
        // The autogenerated event handler method must be public
        // if it will be referenced in a UIComponentDescriptor.
        return ((IMXMLClassReferenceNode)getParent()).needsDescriptor();
    }
}
