/*
 *
 *  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.codegen.js.utils;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.royale.compiler.constants.IASKeywordConstants;
import org.apache.royale.compiler.constants.IASLanguageConstants;
import org.apache.royale.compiler.constants.IASLanguageConstants.BuiltinType;
import org.apache.royale.compiler.constants.INamespaceConstants;
import org.apache.royale.compiler.definitions.IClassDefinition;
import org.apache.royale.compiler.definitions.IDefinition;
import org.apache.royale.compiler.definitions.IFunctionDefinition;
import org.apache.royale.compiler.definitions.IFunctionDefinition.FunctionClassification;
import org.apache.royale.compiler.definitions.ITypeDefinition;
import org.apache.royale.compiler.definitions.IVariableDefinition;
import org.apache.royale.compiler.internal.codegen.js.JSSessionModel;
import org.apache.royale.compiler.internal.definitions.AccessorDefinition;
import org.apache.royale.compiler.internal.definitions.ClassDefinition;
import org.apache.royale.compiler.internal.definitions.FunctionDefinition;
import org.apache.royale.compiler.internal.definitions.InterfaceDefinition;
import org.apache.royale.compiler.internal.definitions.NamespaceDefinition.INamepaceDeclarationDirective;
import org.apache.royale.compiler.internal.definitions.ParameterDefinition;
import org.apache.royale.compiler.internal.definitions.VariableDefinition;
import org.apache.royale.compiler.internal.semantics.SemanticUtils;
import org.apache.royale.compiler.internal.tree.as.*;
import org.apache.royale.compiler.internal.tree.as.ConfigConditionBlockNode;
import org.apache.royale.compiler.internal.tree.as.ContainerNode;
import org.apache.royale.compiler.internal.tree.as.NodeBase;
import org.apache.royale.compiler.internal.tree.as.ParameterNode;
import org.apache.royale.compiler.projects.ICompilerProject;
import org.apache.royale.compiler.tree.ASTNodeID;
import org.apache.royale.compiler.tree.as.*;
import org.apache.royale.compiler.utils.NativeUtils;

/**
 * Various static methods used in shared emitter logic.
 */
public class EmitterUtils
{
    public static ITypeNode findTypeNode(IPackageNode node)
    {
        IScopedNode scope = node.getScopedNode();
        for (int i = 0; i < scope.getChildCount(); i++)
        {
            IASNode child = scope.getChild(i);
            if (child instanceof ITypeNode)
                return (ITypeNode) child;
            else if (child.getNodeID() == ASTNodeID.ConfigBlockID)
            {
            	ConfigConditionBlockNode configNode = (ConfigConditionBlockNode)child;
            	if (configNode.getChildCount() > 0)
            	{
            		child = configNode.getChild(0);
                    if (child instanceof ITypeNode)
                        return (ITypeNode) child;
            	}
            }
        }
        return null;
    }

    public static ITypeDefinition findType(Collection<IDefinition> definitions)
    {
        for (IDefinition definition : definitions)
        {
            if (definition instanceof ITypeDefinition)
                return (ITypeDefinition) definition;
        }
        return null;
    }

    public static INamepaceDeclarationDirective findNamespace(Collection<IDefinition> definitions)
    {
        for (IDefinition definition : definitions)
        {
            if (definition instanceof INamepaceDeclarationDirective)
                return (INamepaceDeclarationDirective) definition;
        }
        return null;
    }
    
    public static INamespaceNode findNamespaceNode(IPackageNode node)
    {
        IScopedNode scope = node.getScopedNode();
        for (int i = 0; i < scope.getChildCount(); i++)
        {
            IASNode child = scope.getChild(i);
            if (child instanceof INamespaceNode)
                return (INamespaceNode) child;
        }
        return null;
    }

    public static IFunctionDefinition findFunction(Collection<IDefinition> definitions)
    {
        for (IDefinition definition : definitions)
        {
            if (definition instanceof IFunctionDefinition)
                return (IFunctionDefinition) definition;
        }
        return null;
    }

    public static IFunctionNode findFunctionNode(IPackageNode node)
    {
        IScopedNode scope = node.getScopedNode();
        for (int i = 0; i < scope.getChildCount(); i++)
        {
            IASNode child = scope.getChild(i);
            if (child instanceof IFunctionNode)
                return (IFunctionNode) child;
        }
        return null;
    }

    public static IVariableNode findVariableNode(IPackageNode node)
    {
        IScopedNode scope = node.getScopedNode();
        for (int i = 0; i < scope.getChildCount(); i++)
        {
            IASNode child = scope.getChild(i);
            if (child instanceof IVariableNode)
                return (IVariableNode) child;
        }
        return null;
    }

    public static IVariableDefinition findVariable(Collection<IDefinition> definitions)
    {
        for (IDefinition definition : definitions)
        {
            if (definition instanceof IVariableDefinition)
                return (IVariableDefinition) definition;
        }
        return null;
    }

    public static ITypeDefinition getTypeDefinition(IDefinitionNode node)
    {
        ITypeNode tnode = (ITypeNode) node.getAncestorOfType(ITypeNode.class);
        if (tnode != null)
        {
            return (ITypeDefinition) tnode.getDefinition();
        }
        return null;
    }

    public static boolean isSameClass(IDefinition pdef, IDefinition thisClass,
            ICompilerProject project)
    {
        if (pdef == thisClass)
            return true;

        IDefinition cdef = ((ClassDefinition) thisClass)
                .resolveBaseClass(project);
        while (cdef != null)
        {
            // needs to be a loop
            if (cdef == pdef)
                return true;
            cdef = ((ClassDefinition) cdef).resolveBaseClass(project);
        }
        return false;
    }

    public static boolean hasSuperClass(ICompilerProject project,
            IDefinitionNode node)
    {
        IClassDefinition superClassDefinition = getSuperClassDefinition(node,
                project);
        // XXX (mschmalle) this is nulling for MXML super class, figure out why
        if (superClassDefinition == null)
            return false;
        String qname = superClassDefinition.getQualifiedName();
        return superClassDefinition != null
                && !qname.equals(IASLanguageConstants.Object);
    }

    public static boolean hasSuperCall(IScopedNode node)
    {
        for (int i = node.getChildCount() - 1; i > -1; i--)
        {
            IASNode cnode = node.getChild(i);
            if (cnode.getNodeID() == ASTNodeID.FunctionCallID
                    && cnode.getChild(0).getNodeID() == ASTNodeID.SuperID)
                return true;
        }

        return false;
    }

    public static boolean hasBody(IFunctionNode node)
    {
        IScopedNode scope = node.getScopedNode();
        return scope.getChildCount() > 0;
    }

    public static IClassDefinition getSuperClassDefinition(
            IDefinitionNode node, ICompilerProject project)
    {
        IDefinition parent = node.getDefinition().getParent();
        if (parent instanceof IClassDefinition)
        {
            IClassDefinition parentClassDef = (IClassDefinition) parent;
            IClassDefinition superClass = parentClassDef.resolveBaseClass(project);
            return superClass;
        }
        return null;
    }

    public static List<String> resolveImports(ITypeDefinition type)
    {
        ArrayList<String> list = new ArrayList<String>();
        IScopedNode scopeNode = type.getContainedScope().getScopeNode();
        if (scopeNode != null)
        {
            scopeNode.getAllImports(list);
        }
        else
        {
            // MXML
            ClassDefinition cdefinition = (ClassDefinition) type;
            String[] implicitImports = cdefinition.getImplicitImports();
            for (String imp : implicitImports)
            {
                list.add(imp);
            }
        }
        return list;
    }

    public static IClassDefinition getClassDefinition(IDefinitionNode node)
    {
        IClassNode tnode = (IClassNode) node
                .getAncestorOfType(IClassNode.class);
        return (tnode != null) ? tnode.getDefinition() : null;
    }

    public static IParameterNode getRest(IParameterNode[] nodes)
    {
        for (IParameterNode node : nodes)
        {
            if (node.isRest())
                return node;
        }

        return null;
    }

    public static Map<Integer, IParameterNode> getDefaults(
            IParameterNode[] nodes)
    {
        Map<Integer, IParameterNode> result = new HashMap<Integer, IParameterNode>();
        int i = 0;
        boolean hasDefaults = false;
        for (IParameterNode node : nodes)
        {
            if (node.hasDefaultValue())
            {
                hasDefaults = true;
                result.put(i, node);
            }
            else
            {
                result.put(i, null);
            }
            i++;
        }

        if (!hasDefaults)
            return null;

        return result;
    }

    public static boolean writeThis(ICompilerProject project,
            JSSessionModel model, IIdentifierNode node)
    {
        IClassNode classNode = (IClassNode) node
                .getAncestorOfType(IClassNode.class);

        IDefinition nodeDef = node.resolve(project);

        IASNode parentNode = node.getParent();
        ASTNodeID parentNodeId = parentNode.getNodeID();

        IASNode firstChild = parentNode.getChild(0);

        final IClassDefinition thisClass = model.getCurrentClass();

        boolean identifierIsMemberAccess = parentNodeId == ASTNodeID.MemberAccessExpressionID;

        if (nodeDef instanceof ParameterDefinition)
            return false;
        if (nodeDef instanceof InterfaceDefinition)
            return false;
        if (nodeDef instanceof ClassDefinition)
            return false;
        
        if (classNode == null) // script in MXML and AS interface definitions
        {
        	if (parentNodeId == ASTNodeID.FunctionCallID && model.inE4xFilter)
        	{
        		// instance methods must be qualified with 'this'?
        		// or maybe we need to test if identifier exists on XML/XMLList
        		return false;
        	}
            if (nodeDef instanceof VariableDefinition)
            {
                IDefinition pdef = ((VariableDefinition) nodeDef).getParent();

                if (thisClass == null || !isSameClass(pdef, thisClass, project))
                    return false;

                if (identifierIsMemberAccess)
                    return node == firstChild;

                return parentNodeId == ASTNodeID.ContainerID
                        || !(parentNode instanceof ParameterNode);
            }
            else if (nodeDef instanceof AccessorDefinition)
            {
                IDefinition pdef = ((AccessorDefinition) nodeDef).getParent();

                if (thisClass == null || !isSameClass(pdef, thisClass, project))
                    return false;

                if (identifierIsMemberAccess)
                    return node == firstChild;

                return true;
            }
            else if (parentNodeId == ASTNodeID.ContainerID
                    && nodeDef instanceof FunctionDefinition)
            {
                return ((FunctionDefinition) nodeDef)
                        .getFunctionClassification() == FunctionClassification.CLASS_MEMBER; // for 'goog.bind'
            }
            else
            {
                boolean isFileOrPackageMember = false;
                boolean isLocalFunction = false;
                if(nodeDef instanceof FunctionDefinition)
                {
                    FunctionClassification classification = ((FunctionDefinition) nodeDef).getFunctionClassification();
                    if(classification == FunctionClassification.FILE_MEMBER ||
                            classification == FunctionClassification.PACKAGE_MEMBER)
                    {
                        isFileOrPackageMember = true;
                    }
                    else if (!identifierIsMemberAccess && classification == FunctionClassification.CLASS_MEMBER &&
                            isClassMember(project, nodeDef, thisClass))
                    {
                        return true;
                    }
                    else if (classification == FunctionClassification.LOCAL)
                    {
                        isLocalFunction = true;
                    }
                }
                return parentNodeId == ASTNodeID.FunctionCallID
                        && !(nodeDef instanceof AccessorDefinition)
                        && !identifierIsMemberAccess
                        && !isFileOrPackageMember
                        && !isLocalFunction;
            }
        }
        else
        {
        	if (parentNodeId == ASTNodeID.FunctionCallID && model.inE4xFilter)
        	{
        		// instance methods must be qualified with 'this'?
        		// or maybe we need to test if identifier exists on XML/XMLList
        		return false;
        	}
            if (nodeDef != null
                    && isClassMember(project, nodeDef, classNode))
            {
                if (identifierIsMemberAccess)
                {
                    return node == firstChild;
                }
                else
                {
                    boolean identifierIsLocalFunction = nodeDef instanceof FunctionDefinition
                            && !(nodeDef instanceof AccessorDefinition)
                            && ((FunctionDefinition) nodeDef)
                                    .getFunctionClassification() == IFunctionDefinition.FunctionClassification.LOCAL;

                    return !identifierIsLocalFunction;
                }
            }
        }

        return false;
    }

    public static boolean isClassMember(ICompilerProject project,
            IDefinition nodeDef, IClassNode classNode)
    {
        IDefinition parentDef = nodeDef.getParent();
    	if (nodeDef.isInternal() && (!(parentDef instanceof ClassDefinition)))
    		return false;
    	
        IClassDefinition cdef = classNode.getDefinition();
        return parentDef == cdef || (parentDef instanceof ClassDefinition && cdef.isInstanceOf((ClassDefinition)parentDef, project));
    }
    
    public static boolean isClassMember(ICompilerProject project,
            IDefinition nodeDef, IClassDefinition classDef)
    {
        IDefinition parentDef = nodeDef.getParent();
    	if (nodeDef.isInternal() && (!(parentDef instanceof ClassDefinition)))
    		return false;
    	
        return parentDef == classDef || (parentDef instanceof ClassDefinition && ((ClassDefinition)parentDef).isInstanceOf(classDef, project));
    }
    
    public static boolean writeE4xFilterNode(ICompilerProject project,
            JSSessionModel model, IExpressionNode node)
    {
    	if (!model.inE4xFilter) return false;
    	
        IDefinition nodeDef = node.resolve(project);

        IASNode parentNode = node.getParent();
//        ASTNodeID parentNodeId = parentNode.getNodeID();

        IASNode firstChild = parentNode.getChild(0);

//        final IClassDefinition thisClass = model.getCurrentClass();

//        boolean identifierIsMemberAccess = parentNodeId == ASTNodeID.MemberAccessExpressionID;

        if (parentNode instanceof IUnaryOperatorNode)
        	return false;
        if (nodeDef instanceof ParameterDefinition)
            return false;
        if (nodeDef instanceof InterfaceDefinition)
            return false;
        if (nodeDef instanceof ClassDefinition)
            return false;
        if (nodeDef instanceof VariableDefinition)
        {
        		List<IVariableNode> list = model.getVars();
        		for (IVariableNode element : list) {
        		    if(element.getQualifiedName().equals(((IIdentifierNode)node).getName()))
        		    		return false;
        		}
        }
        
        if (node == firstChild) 
        		return true;
        //support alternate ordering of checks, e.g. listItem.(3 == @id) as well as the normal listItem.(@id == 3)
        if (parentNode instanceof IBinaryOperatorNode
            && node == parentNode.getChild(1)) {
            return !(parentNode instanceof IMemberAccessExpressionNode);
        }
        
        return false;
    }
    
   /* public static ArrayList<String> amendE4XFilterComparison(ICompilerProject project,
                                                     JSSessionModel model, IBinaryOperatorNode node, boolean isLeft) {
        if (!model.inE4xFilter) return null;
        IExpressionNode leftNode = node.getLeftOperandNode();
        IDefinition left = null;
        IDefinition right = null;
        boolean leftNodeMember = false;
        boolean leftNodeQName = false;
        boolean rightNodeMember = false;
        boolean rightNodeQName = false;
        if (writeE4xFilterNode(project, model, leftNode)) {
            leftNodeMember = true;
            if (leftNode  instanceof IFunctionCallNode &&
                    ((IFunctionCallNode) leftNode).getNameNode() != null){
                leftNodeQName = ((IFunctionCallNode) leftNode).getNameNode().equals("name");
            }
        } else {
            left = node.getLeftOperandNode().resolveType(project);
        }
        IExpressionNode rightNode = node.getRightOperandNode();
        if (writeE4xFilterNode(project, model, rightNode)) {
            rightNodeMember = true;
            if (rightNode  instanceof IFunctionCallNode &&
                    ((IFunctionCallNode) rightNode).getNameNode() != null){
                rightNodeQName = ((IFunctionCallNode) rightNode).getNameNode().equals("name");
            }
        } else {
            right = node.getRightOperandNode().resolveType(project);
        }
        if (!leftNodeMember && !rightNodeMember) return null;
        ArrayList<String> s = null;
        if (leftNodeMember) {
            //left side is a node.something() function
            if (right!= null && leftNodeQName) {
                //node.name() == right
                if (right.getQualifiedName().equals("String")) {
                    if (isLeft) {
                        s=new ArrayList<String>();
                        s.add(".toString()");
                    } //else make no change for right side
                } else if (rightNodeQName || right.getQualifiedName().equals("QName")) {
                    //use qname.equals(otherQName)
                    if (isLeft) {
                        s.add(".equals(");
                    } else {
                        s.add(")"); //close the equals
                    }
                }
            }
        }
        if (s == null && rightNodeMember) {
            //right side is a node.something() function that has not already been addressed
            if (left!= null && rightNodeQName) {
                //left == node.name()
                if (left.getQualifiedName().equals("String")) {
                    if (!isLeft) {
                        //node.name().toString()
                        s=new ArrayList<String>();
                        s.add(".toString()");
                    } //else make no change for right side
                } else if (rightNodeQName || right.getQualifiedName().equals("QName")) {
                    //use qname.equals(otherQName)
                    if (isLeft) {
                        s.add(".equals(");
                    } else {
                        s.add(")"); //close the equals
                    }
                }
            }
        }
       
        
        return s;
    }*/

    public static boolean isScalar(IExpressionNode node)
    {
    	ASTNodeID id = node.getNodeID();
        if (id == ASTNodeID.LiteralBooleanID ||
        		id == ASTNodeID.LiteralIntegerID ||
        		id == ASTNodeID.LiteralIntegerZeroID ||
        		id == ASTNodeID.LiteralDoubleID ||
        		id == ASTNodeID.LiteralNullID ||
        		id == ASTNodeID.LiteralNumberID ||
        		id == ASTNodeID.LiteralRegexID ||
        		id == ASTNodeID.LiteralStringID ||
        		id == ASTNodeID.LiteralUintID)
        	return true;
        if (id == ASTNodeID.IdentifierID)
        {
        	IIdentifierNode idnode = (IIdentifierNode)node;
        	String idname = idnode.getName();
        	if (idname.equals(NativeUtils.NativeASType.Infinity.name()) ||
        		idname.equals(NativeUtils.NativeASType.undefined.name()) ||
        		idname.equals(NativeUtils.NativeASType.NaN.name()))
        		return true;
        }
        // special case -Infinity
        if (id == ASTNodeID.Op_SubtractID &&
        		node.getChildCount() == 1)
        {
        	IASNode child = node.getChild(0);
        	if (child.getNodeID() == ASTNodeID.IdentifierID)
        	{
            	IIdentifierNode idnode = (IIdentifierNode)child;
            	String idname = idnode.getName();
            	if (idname.equals(NativeUtils.NativeASType.Infinity.name()))
            		return true;        		
        	}
        }
        return false;
    }

    // return true if the node is an expression that may not work
    // as the initial value of a static var at
    // static initialization time.  Such as a function call to
    // another static method in the class.
    // Non-static initializers have different rules: even simple object
    // and arrays need to be created for each instance, but for statics
    // simple objects and arras are ok.
    public static boolean needsStaticInitializer(String node, String className)
    {
    	return node.contains(className);
    }

    public static IContainerNode insertArgumentsBefore(IContainerNode argumentsNode, IASNode... nodes)
    {
        int originalLength = argumentsNode.getChildCount();
        int extraLength = nodes.length;
        ContainerNode result = new ContainerNode(originalLength + extraLength);
        result.setSourcePath(argumentsNode.getSourcePath());
        result.span(argumentsNode);
        result.setParent((NodeBase) argumentsNode.getParent());
        for (int i = 0; i < extraLength; i++)
        {
            NodeBase node = (NodeBase) nodes[i];
            node.setSourcePath(argumentsNode.getSourcePath());
            result.addItem(node);
        }
        for (int i = 0; i < originalLength; i++)
        {
            result.addItem((NodeBase) argumentsNode.getChild(i));
        }
        return result;
    }

    public static IContainerNode insertArgumentsAfter(IContainerNode argumentsNode, IASNode... nodes)
    {
        int originalLength = argumentsNode.getChildCount();
        int extraLength = nodes.length;
        ContainerNode result = new ContainerNode(originalLength + extraLength);
        result.setSourcePath(argumentsNode.getSourcePath());
        result.span(argumentsNode);
        result.setParent((NodeBase) argumentsNode.getParent());
        for (int i = 0; i < originalLength; i++)
        {
            result.addItem((NodeBase) argumentsNode.getChild(i));
        }
        for (int i = 0; i < extraLength; i++)
        {
            NodeBase node = (NodeBase) nodes[i];
            node.setSourcePath(argumentsNode.getSourcePath());
            result.addItem(node);
        }
        return result;
    }

    public static IContainerNode insertArgumentsAt(IContainerNode argumentsNode, int index, IASNode... nodes)
    {
        int originalLength = argumentsNode.getChildCount();
        int extraLength = nodes.length;
        ContainerNode result = new ContainerNode(originalLength + extraLength);
        result.setSourcePath(argumentsNode.getSourcePath());
        result.span(argumentsNode);
        result.setParent((NodeBase) argumentsNode.getParent());
        for (int i = 0; i < originalLength; i++)
        {
            if(i < index)
            {
                result.addItem((NodeBase) argumentsNode.getChild(i));
            }
            else
            {
            	if (i == index)
                {
                    for (IASNode node : nodes)
                    {
                    	NodeBase n = (NodeBase) node;
                    	n.setSourcePath(argumentsNode.getSourcePath());
                    	result.addItem(n);
                    }
                }
                result.addItem((NodeBase) argumentsNode.getChild(i));
            }
        }
        return result;
    }

    public static boolean isImplicit(IContainerNode node)
    {
        return node.getContainerType() == IContainerNode.ContainerType.IMPLICIT
                || node.getContainerType() == IContainerNode.ContainerType.SYNTHESIZED;
    }

    public static boolean needsDefaultValue(IVariableNode node, boolean defaultInitializers, ICompilerProject project)
    {
        if (node == null)
        {
            return false;
        }
        if (node instanceof IParameterNode)
        {
            return false;
        }
        if (node instanceof IAccessorNode)
        {
            return false;
        }
        IExpressionNode assignedValueNode = node.getAssignedValueNode();
        if (assignedValueNode != null)
        {
            //already has an assigned value, so it doesn't need to be
            //hoisted
            return false;
        }
        IASNode parentNode = node.getParent();
        if (parentNode instanceof IVariableExpressionNode)
        {
            //ignore for-in loops
            return false;
        }
        IExpressionNode variableTypeNode = node.getVariableTypeNode();
        if (variableTypeNode == null)
        {
            return false;
        }
        IDefinition varTypeDef = variableTypeNode.resolve(project);
        if (varTypeDef == null)
        {
            return false;
        }
        if (IASLanguageConstants.ANY_TYPE.equals(varTypeDef.getQualifiedName()))
        {
            return false;
        }
        if (project.getBuiltinType(BuiltinType.ANY_TYPE).equals(varTypeDef)
                || project.getBuiltinType(BuiltinType.ANY_TYPE).equals(varTypeDef)
                || project.getBuiltinType(BuiltinType.ANY_TYPE).equals(varTypeDef))
        {
            return false;
        }
        if (project.getBuiltinType(BuiltinType.INT).equals(varTypeDef)
                || project.getBuiltinType(BuiltinType.UINT).equals(varTypeDef))
        {
            //always true, regardless of -js-default-initializers
            return true;
        }
        return defaultInitializers;
    }
    
    
    /**
     * resolveType on an XML expression returns null
     * (see IdentiferNode.resolveType).
     * So, we have to walk the tree ourselves and resolve
     * individual pieces.
     * @param obj
     * @return
     */
    public static boolean isXMLish(IExpressionNode obj, ICompilerProject project )
    {
        // See if the left side is XML or XMLList
        IDefinition leftDef = obj.resolveType(project);
        if (leftDef == null && obj.getNodeID() == ASTNodeID.MemberAccessExpressionID)
        {
            return isXMLish(((MemberAccessExpressionNode)obj).getLeftOperandNode(), project);
        }
        else if (leftDef != null && leftDef.getBaseName().equals("*") && obj instanceof DynamicAccessNode) {
            return isXMLish(((DynamicAccessNode)obj).getLeftOperandNode(), project);
        }
        return SemanticUtils.isXMLish(leftDef, project);
    }
    
    
    /**
     * resolveType on an XML expression returns null
     * (see IdentiferNode.resolveType).
     * So, we have to walk the tree ourselves and resolve
     * individual pieces.
     * We want to know not just whether the node is of type XML,
     * but whether it is a property of a property of type XML.
     * For example, this.foo might be XML or XMLList, but since
     * 'this' isn't also XML, we return false.  That's because
     * assignment to this.foo shouldn't use setChild() but
     * just do an assignment.
     * @param obj
     * @return
     */
    public static boolean isXMLList(IMemberAccessExpressionNode obj, ICompilerProject project)
    {
        IExpressionNode leftNode = obj.getLeftOperandNode();
        IExpressionNode rightNode = obj.getRightOperandNode();
        ASTNodeID rightID = rightNode.getNodeID();
        if (rightID == ASTNodeID.IdentifierID || (rightID == ASTNodeID.NamespaceAccessExpressionID && rightNode.getChild(1).getNodeID() == ASTNodeID.IdentifierID))
        {
            IDefinition rightDef = rightNode.resolveType(project);
            if (rightDef != null)
            {
                if (SemanticUtils.isXMLish(rightDef, project))
                {
                    return isLeftNodeXMLish(leftNode, project);
                }
                return false;
            }
            return isLeftNodeXMLish(leftNode, project);
        }
        else if (rightID == ASTNodeID.Op_AtID)
            return true;
        else if (rightNode instanceof IDynamicAccessNode && ((IDynamicAccessNode) rightNode).getLeftOperandNode().getNodeID() == ASTNodeID.Op_AtID)
            return true;
        return false;
    }

    public static boolean isLeftNodeXMLList(IExpressionNode leftNode, ICompilerProject project) {
        boolean isXMLList = false;
        if (isLeftNodeXMLish(leftNode, project)) {
            //it is not XMLList if it is a DynamicAccessNode with numeric index.
            //this is limited analysis, because ["0"] would also be the same as [0], but perhaps best we can do without more runtime support
            if (leftNode instanceof IDynamicAccessNode) { //DynamicAccessNode
                IExpressionNode dynAccess = ((IDynamicAccessNode) leftNode).getRightOperandNode();
                IDefinition accessDef = dynAccess.resolveType(project);
                if (SemanticUtils.isNumericType(accessDef, project)) {
                    //assume we are XML, not XMLList
                    isXMLList = false;
                }
            } else
                isXMLList = true;

        }
        return isXMLList;
    }

    public static boolean isLeftNodeXML(IExpressionNode leftNode, ICompilerProject project) {
        boolean isXML = false;
        if (isLeftNodeXMLish(leftNode, project)) {
            //it is not XMLList if it is a DynamicAccessNode with numeric index.
            //this is limited analysis, because ["0"] would also be the same as [0], but perhaps best we can do without more runtime support
            if (leftNode instanceof IDynamicAccessNode) { //DynamicAccessNode
                IExpressionNode dynAccess = ((IDynamicAccessNode) leftNode).getRightOperandNode();
                IDefinition accessDef = dynAccess.resolveType(project);
                if (SemanticUtils.isNumericType(accessDef, project)) {
                    //assume we are XML, not XMLList
                    isXML = true;
                }
            } else
                isXML = false;

        }
        return isXML;
    }
    
    
    public static boolean isLeftNodeXMLish(IExpressionNode leftNode, ICompilerProject project)
    {
        ASTNodeID leftID = leftNode.getNodeID();
        if (leftID == ASTNodeID.IdentifierID)
        {
            IDefinition leftDef = leftNode.resolveType(project);
            if (leftDef != null)
                return SemanticUtils.isXMLish(leftDef, project);
        }
        else if (leftID == ASTNodeID.MemberAccessExpressionID || leftID == ASTNodeID.Op_DescendantsID)
        {
            MemberAccessExpressionNode maen = (MemberAccessExpressionNode)leftNode;
            IExpressionNode rightNode = maen.getRightOperandNode();
            ASTNodeID rightID = rightNode.getNodeID();
            if (rightID == ASTNodeID.IdentifierID)
            {
                IDefinition rightDef = rightNode.resolveType(project);
                if (rightDef != null && rightDef != project.getBuiltinType(BuiltinType.ANY_TYPE))
                {
                    return SemanticUtils.isXMLish(rightDef, project);
                }
            }
            leftNode = maen.getLeftOperandNode();
            return isLeftNodeXMLish(leftNode, project);
        }
        else if (leftID == ASTNodeID.FunctionCallID)
        {
            FunctionCallNode fcn = (FunctionCallNode)leftNode;
            String fname = fcn.getFunctionName();
            if (fname.equals("XML") || fname.equals("XMLList"))
                return true;
        }
        else if (leftID == ASTNodeID.Op_AsID)
        {
            BinaryOperatorAsNode boan = (BinaryOperatorAsNode)leftNode;
            String fname = ((IdentifierNode)boan.getChild(1)).getName();
            if (fname.equals("XML") || fname.equals("XMLList"))
                return true;
        }
        else if (leftID == ASTNodeID.ArrayIndexExpressionID)
        {
            leftNode = (IExpressionNode)(leftNode.getChild(0));
            IDefinition leftDef = leftNode.resolveType(project);
            if (leftDef != null)
                return SemanticUtils.isXMLish(leftDef, project);
            
        }
        else if (leftID == ASTNodeID.E4XFilterID)
            return true;
        return false;
    }
    
    public static boolean needsXMLQNameArgumentsPatch(IFunctionCallNode node, ICompilerProject project) {
        if (node.getNameNode() instanceof MemberAccessExpressionNode
        && ((MemberAccessExpressionNode)node.getNameNode()).getRightOperandNode() instanceof IdentifierNode) {
            String methodName = ((IdentifierNode)((MemberAccessExpressionNode)node.getNameNode()).getRightOperandNode()).getName();
            if ("child".equals(methodName) || "descendants".equals(methodName) || "attribute".equals(methodName)) {
                //double check it is not a method with the same name on a non-XMLish class
                IASNode leftNode = ((MemberAccessExpressionNode)node.getNameNode()).getLeftOperandNode();
                boolean isXML = leftNode instanceof MemberAccessExpressionNode
                        && isLeftNodeXMLish((MemberAccessExpressionNode) leftNode, project);
                if (!isXML) {
                    isXML = leftNode instanceof IExpressionNode && isXMLish((IExpressionNode)leftNode, project);
                }
                if (isXML) {
                    //check argumentsNode
                    if (node.getArgumentsNode().getChildCount() == 1) {
                        IDefinition def = node.getArgumentNodes()[0].resolveType(project);
                        if (def != null && def.getQualifiedName().equals("QName")) {
                            return true;
                        }
                    }
                }
            }
        }
        return false;
    }
    
    public static void createDefaultNamespaceArg(ContainerNode argsNode, int position, IExpressionNode defaultNamespace) {
        argsNode.addChild((NodeBase) defaultNamespace, position);
        ((NodeBase) defaultNamespace).setParent(argsNode);
    }
    
    public static boolean isCustomNamespace(String ns) {
        if (ns != null)
        {
            return (!(  ns.equals(IASKeywordConstants.PRIVATE) ||
                        ns.equals(IASKeywordConstants.PROTECTED) ||
                        ns.equals(IASKeywordConstants.INTERNAL) ||
                        ns.equals(INamespaceConstants.AS3URI) ||
                        ns.equals(IASKeywordConstants.PUBLIC)
                    ));
        }
        return false;
    }

}
