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

import java.util.List;

import org.apache.flex.compiler.common.ISourceLocation;
import org.apache.flex.compiler.constants.IASLanguageConstants;
import org.apache.flex.compiler.definitions.IClassDefinition;
import org.apache.flex.compiler.definitions.IDefinition;
import org.apache.flex.compiler.definitions.IVariableDefinition;
import org.apache.flex.compiler.internal.definitions.ClassDefinition;
import org.apache.flex.compiler.internal.parsing.ISourceFragment;
import org.apache.flex.compiler.internal.projects.FlexProject;
import org.apache.flex.compiler.internal.tree.as.NodeBase;
import org.apache.flex.compiler.mxml.IMXMLTagData;
import org.apache.flex.compiler.mxml.IMXMLUnitData;
import org.apache.flex.compiler.tree.ASTNodeID;
import org.apache.flex.compiler.tree.as.IASNode;
import org.apache.flex.compiler.tree.mxml.IMXMLClassNode;
import org.apache.flex.compiler.tree.mxml.IMXMLDeferredInstanceNode;
import org.apache.flex.compiler.tree.mxml.IMXMLInstanceNode;

/**
 * This AST node represents the instance of
 * <code>mx.core.DeferredInstanceFromClass</code> or
 * <code>mx.core.DeferredInstanceFromFunction</code> that the compiler
 * implicitly creates as the value for a property of type
 * <code>mx.core.IDeferredInstance</code>.
 * <p>
 * An {@code IMXMLDeferredInstanceNode} has exactly one child, which can be
 * either an {@code IMXMLClassNode} (in the case of an
 * <code>DeferredInstanceFromClass</code>) or an {@code IMXMLInstanceNode} (in
 * the case of an <code>DeferredInstanceFromFunction</code)). Gordon Smith
 */
class MXMLDeferredInstanceNode extends MXMLInstanceNode implements IMXMLDeferredInstanceNode
{
    /**
     * Constructor
     * 
     * @param parent The parent node of this node, or <code>null</code> if there
     * is no parent.
     */
    MXMLDeferredInstanceNode(NodeBase parent)
    {
        super(parent);
    }

    private IMXMLInstanceNode childNode;

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

    @Override
    public IASNode getChild(int i)
    {
        return i == 0 ? childNode : null;
    }

    @Override
    public int getChildCount()
    {
        return childNode == null ? 0 : 1;
    }

    @Override
    protected void processChildTag(MXMLTreeBuilder builder, IMXMLTagData tag,
                                   IMXMLTagData childTag,
                                   MXMLNodeInfo info)
    {
        FlexProject project = builder.getProject();

        // Check whether the child tag is an instance tag.
        IDefinition definition = builder.getFileScope().resolveTagToDefinition(childTag);
        if (definition instanceof ClassDefinition)
        {
            // This node should generate an instance of mx.core.DeferredInstanceFromFunction.
            String qname = project.getDeferredInstanceFromFunctionClass();
            setClassReference(project, qname);
            builder.addExpressionDependency(qname);

            childNode = MXMLInstanceNode.createInstanceNode(
                    builder, definition.getQualifiedName(), this);
            ((MXMLInstanceNode)childNode).setClassReference(project, (ClassDefinition)definition); // TODO Move this logic to initializeFromTag().
            ((MXMLInstanceNode)childNode).initializeFromTag(builder, childTag);
        }
    }

    void initializeDefaultProperty(MXMLTreeBuilder builder, IVariableDefinition defaultPropertyDefinition,
                                   List<IMXMLUnitData> contentUnits)
    {
        FlexProject project = builder.getProject();

        assert (contentUnits.isEmpty()) ||
                (!builder.getFileScope().isScriptTag(contentUnits.get(0))) : "Script tags should not start a default property!";

        assert (contentUnits.isEmpty()) ||
                (!builder.getFileScope().isScriptTag(contentUnits.get(contentUnits.size() - 1))) : "Trailing script tags should be removed from default property content units!";

        // Set the location of the implicit deferred instance node
        // to span the tags that specify the default property value.
        setLocation(builder, contentUnits);

        setClassReference(project, project.getDeferredInstanceFromFunctionClass());

        // Determine whether the the default property is being set to a single Array tag.
        boolean isSingleArrayTag = false;
        int n = contentUnits.size();
        if (n == 1 && contentUnits.get(0) instanceof IMXMLTagData)
        {
            IMXMLTagData tag = (IMXMLTagData)contentUnits.get(0);
            IDefinition definition = builder.getFileScope().resolveTagToDefinition(tag);
            isSingleArrayTag = definition.getQualifiedName().equals(IASLanguageConstants.Array);
        }

        String instanceType = defaultPropertyDefinition.getInstanceType(project);
        if (IASLanguageConstants.Array.equals(instanceType) && !isSingleArrayTag)
        {
            // Create an implicit array node.
            childNode = new MXMLArrayNode(this);
            ((MXMLArrayNode)childNode).initializeDefaultProperty(
                    builder, defaultPropertyDefinition, contentUnits);
        }
        else if ((n == 1) && (!isSingleArrayTag))
        {
            IMXMLTagData tag = (IMXMLTagData)contentUnits.get(0);
            IDefinition type = builder.getFileScope().resolveTagToDefinition(tag);
            if (type instanceof ClassDefinition)
            {
                IClassDefinition tagClass = (IClassDefinition)type;
                MXMLInstanceNode childInstanceNode = MXMLInstanceNode.createInstanceNode(builder, type.getQualifiedName(), this);
                childInstanceNode.setClassReference(project, tagClass);
                childNode = childInstanceNode;
                childInstanceNode.initializeFromTag(builder, tag);
            }
        }
    }

    @Override
    protected void initializationComplete(MXMLTreeBuilder builder, IMXMLTagData tag,
                                          MXMLNodeInfo info)
    {
        if (childNode == null)
        {
            ISourceLocation location = info.getSourceLocation();
            if (location == null)
                location = tag.getLocationOfChildUnits();

            ISourceFragment[] sourceFragments = info.getSourceFragments();

            initializeFromFragments(builder, location, sourceFragments);
        }
    }

    public void initializeFromFragments(MXMLTreeBuilder builder,
                                        ISourceLocation location,
                                        ISourceFragment[] fragments)
    {
        // This node represents an instance of mx.core.DeferredInstanceFromClass.
        FlexProject project = builder.getProject();
        String qname = project.getDeferredInstanceFromClassClass();
        setClassReference(project, qname);

        // Add an expression dependency on that class.
        builder.addExpressionDependency(qname);

        // The source fragments are a class name (possibly a complex one
        // like Vector.<Vector.<int>>. This is the class from which
        // we're making a DeferredInstanceFromClass object.
        // Create a child MXMLClassNode from it.
        childNode = new MXMLClassNode(this);
        ((MXMLClassNode)childNode).setClassReference(project, childNode.getName()); // TODO Move this logic to initializeFromText().
        ((MXMLClassNode)childNode).initializeFromFragments(builder, location, fragments);
    }

    @Override
    public IMXMLClassNode getClassNode()
    {
        return childNode instanceof IMXMLClassNode ? (IMXMLClassNode)childNode : null;
    }

    @Override
    public IMXMLInstanceNode getInstanceNode()
    {
        // Note that IMXMLClassNode extends IMXMLInstanceNode.
        return !(childNode instanceof IMXMLClassNode) ? childNode : null;
    }
}
