/*
 *
 *  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.ArrayList;
import java.util.Collection;
import java.util.EnumSet;
import java.util.List;

import org.apache.royale.compiler.problems.ICompilerProblem;
import org.apache.royale.compiler.problems.MXMLDualContentProblem;
import org.apache.royale.compiler.problems.UnexpectedExceptionProblem;
import org.apache.royale.compiler.filespecs.IFileSpecification;
import org.apache.royale.compiler.internal.mxml.MXMLDialect;
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.CompilerProject;
import org.apache.royale.compiler.internal.projects.RoyaleProject;
import org.apache.royale.compiler.internal.scopes.ASScope;
import org.apache.royale.compiler.internal.scopes.MXMLFileScope;
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.IMXMLLanguageConstants;
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.mxml.IMXMLUnitData;
import org.apache.royale.compiler.tree.ASTNodeID;
import org.apache.royale.compiler.tree.as.IASNode;
import org.apache.royale.compiler.tree.mxml.IMXMLScriptNode;

import static org.apache.royale.compiler.mxml.IMXMLLanguageConstants.*;

class MXMLScriptNode extends MXMLNodeBase implements IMXMLScriptNode
{
    /**
     * Constructor
     * 
     * @param parent The parent node of this node, or <code>null</code> if there
     * is no parent.
     */
    MXMLScriptNode(NodeBase parent)
    {
        super(parent);
    }

    /**
     * The ActionScript nodes representing the code inside the script tag.
     */
    private IASNode[] asNodes;

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

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

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

    @Override
    public String getName()
    {
        return IMXMLLanguageConstants.SCRIPT;
    }

    @Override
    public IASNode[] getASNodes()
    {
        if (asNodes == null)
            asNodes = new MXMLScriptNode[0];
        return asNodes;
    }

    @Override
    protected MXMLNodeInfo createNodeInfo(MXMLTreeBuilder builder)
    {
        return new MXMLNodeInfo(builder);
    }

    @Override
    protected void processTagSpecificAttribute(MXMLTreeBuilder builder, IMXMLTagData tag,
                                               IMXMLTagAttributeData attribute,
                                               MXMLNodeInfo info)
    {
        if (attribute.isSpecialAttribute(ATTRIBUTE_SOURCE))
        {
            MXMLClassDefinitionNode classNode = getContainingClassNode();
            ASScope classScope = (ASScope)classNode.getClassDefinition().getContainedScope();

            // Resolve the attribute value to a normalized path.
            // Doing so makes this compilation unit dependent on that file.
            String sourcePath = resolveSourceAttributePath(builder, attribute, info);
            if (sourcePath != null)
            {
                RoyaleProject project = builder.getProject();
                Workspace workspace = builder.getWorkspace();
                Collection<ICompilerProblem> problems = builder.getProblems();
                IFileSpecification sourceFileSpec = workspace.getFileSpecification(sourcePath);
                String scriptText = builder.readExternalFile(attribute, sourcePath);

                if (scriptText != null)
                {
                    final IncludeHandler includeHandler = new IncludeHandler(builder.getFileSpecificationGetter());
                    includeHandler.setProjectAndCompilationUnit(project, builder.getCompilationUnit());
                    includeHandler.enterFile(sourcePath);
                    final MXMLFileScope fileScope = builder.getFileScope();
                    final OffsetLookup offsetLookup = fileScope.getOffsetLookup();
                    assert offsetLookup != null : "Expected OffsetLookup on FileScope.";
                    final int[] absoluteOffset = offsetLookup.getAbsoluteOffset(sourcePath, 0);

                    final ScopedBlockNode fragment = ASParser.parseFragment2(
                            scriptText,
                            sourceFileSpec.getPath(),
                            absoluteOffset[0],
                            0,
                            0,
                            problems,
                            workspace,
                            builder.getFileNode(),
                            classScope,
                            project.getProjectConfigVariables(),
                            EnumSet.of(PostProcessStep.CALCULATE_OFFSETS, PostProcessStep.RECONNECT_DEFINITIONS),
                            true /* follow includes */,
                            includeHandler);

                    builder.getFileNode().updateIncludeTreeLastModified(includeHandler.getLastModified());

                    // Make the statements inside the script tag the children of this node.
                    int n = fragment.getChildCount();
                    asNodes = new IASNode[n];
                    for (int i = 0; i < n; i++)
                    {
                        IASNode child = fragment.getChild(i);
                        asNodes[i] = child;
                        ((NodeBase)child).setParent(this);
                    }
                }
            }
        }
        else
        {
            super.processTagSpecificAttribute(builder, tag, attribute, info);
        }
    }

    @Override
    protected void processChildNonWhitespaceUnit(MXMLTreeBuilder builder, IMXMLTagData tag,
                                                 IMXMLTextData text,
                                                 MXMLNodeInfo info)
    {
        info.hasDualContent = true;
    }

    private MXMLClassDefinitionNode getContainingClassNode()
    {
        MXMLClassDefinitionNode result = (MXMLClassDefinitionNode)getAncestorOfType(MXMLClassDefinitionNode.class);
        assert result != null;
        return result;
    }

    @Override
    protected void initializationComplete(MXMLTreeBuilder builder, IMXMLTagData tag,
                                          MXMLNodeInfo info)
    {
        super.initializationComplete(builder, tag, info);

        if (info.hasSourceAttribute && info.hasDualContent)
        {
            ICompilerProblem problem = new MXMLDualContentProblem(tag, tag.getShortName());
            builder.addProblem(problem);
            return;
        }

        final MXMLDialect mxmlDialect = builder.getMXMLDialect();
        final String sourcePath = tag.getParent().getFileSpecification().getPath();
        final MXMLClassDefinitionNode classNode = getContainingClassNode();
        final ASScope classScope = (ASScope)classNode.getClassDefinition().getContainedScope();
        final CompilerProject project = builder.getProject();
        final OffsetLookup offsetLookup = classScope.getFileScope().getOffsetLookup();

        setSourcePath(sourcePath);
        try
        {
            // parse inline ActionScript
            final List<ScopedBlockNode> scriptNodes = new ArrayList<ScopedBlockNode>();
            for (IMXMLUnitData unit = tag.getFirstChildUnit(); unit != null; unit = unit.getNextSiblingUnit())
            {
                if (unit instanceof IMXMLTextData)
                {
                    final IMXMLTextData mxmlTextData = (IMXMLTextData)unit;
                    String text = mxmlTextData.getCompilableText();
                    if (!mxmlDialect.isWhitespace(text))
                    {
                        // local offset at the end of the containing open script tag
                        assert offsetLookup != null : "Expected OffsetLookup on FileScope.";
                        final int localOffset = mxmlTextData.getParentUnitData().getAbsoluteEnd();
                        final int[] absoluteOffsets = offsetLookup.getAbsoluteOffset(sourcePath, localOffset);
                        final int absoluteOffset = absoluteOffsets[0];

                        // create an include handler and mock its state as if it is
                        // before parsing for scope building
                        final IncludeHandler includeHandler = IncludeHandler.createForASTBuilding(
                                builder.getFileSpecificationGetter(),
                                sourcePath,
                                localOffset,
                                absoluteOffset);

                        // parse and build AST
                        final EnumSet<PostProcessStep> postProcess = EnumSet.of(
                                PostProcessStep.CALCULATE_OFFSETS,
                                PostProcessStep.RECONNECT_DEFINITIONS);
                        final ScopedBlockNode node = ASParser.parseInlineScript(
                                builder.getFileNode(),
                                mxmlTextData,
                                builder.getProblems(),
                                classScope,
                                project.getProjectConfigVariables(),
                                includeHandler,
                                postProcess);
                        MXMLFileNode filenode = builder.getFileNode();
                        filenode.updateIncludeTreeLastModified(includeHandler.getLastModified());
                        assert node != null : "Expected node from ASParser.getScopesFromInlineScript().";
                        scriptNodes.add(node);
                    }
                }
            }

            // Make the statements inside the script tag the children of this node.
            for (ScopedBlockNode script : scriptNodes)
            {
                int n = script.getChildCount();
                asNodes = new IASNode[n];
                for (int i = 0; i < n; i++)
                {
                    IASNode child = script.getChild(i);
                    asNodes[i] = child;
                    ((NodeBase)child).setParent(this);
                }
            }
        }
        catch (Exception e)
        {
            builder.addProblem(new UnexpectedExceptionProblem(e));
        }
    }
}
