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

import org.apache.royale.abc.semantics.Namespace;
import org.apache.royale.compiler.codegen.ISubEmitter;
import org.apache.royale.compiler.codegen.js.IJSEmitter;
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.INamespaceDefinition;
import org.apache.royale.compiler.definitions.IVariableDefinition;
import org.apache.royale.compiler.definitions.IVariableDefinition.VariableClassification;
import org.apache.royale.compiler.definitions.references.INamespaceResolvedReference;
import org.apache.royale.compiler.internal.codegen.as.ASEmitterTokens;
import org.apache.royale.compiler.internal.codegen.js.JSSubEmitter;
import org.apache.royale.compiler.internal.codegen.js.royale.JSRoyaleEmitter;
import org.apache.royale.compiler.internal.codegen.js.goog.JSGoogEmitterTokens;
import org.apache.royale.compiler.internal.codegen.js.utils.EmitterUtils;
import org.apache.royale.compiler.internal.definitions.AccessorDefinition;
import org.apache.royale.compiler.internal.definitions.FunctionDefinition;
import org.apache.royale.compiler.internal.definitions.TypeDefinitionBase;
import org.apache.royale.compiler.internal.tree.as.NonResolvingIdentifierNode;
import org.apache.royale.compiler.tree.ASTNodeID;
import org.apache.royale.compiler.tree.as.IASNode;
import org.apache.royale.compiler.tree.as.IFunctionNode;
import org.apache.royale.compiler.tree.as.IFunctionObjectNode;
import org.apache.royale.compiler.tree.as.IIdentifierNode;
import org.apache.royale.compiler.tree.as.IMemberAccessExpressionNode;
import org.apache.royale.compiler.utils.NativeUtils;

public class IdentifierEmitter extends JSSubEmitter implements
        ISubEmitter<IIdentifierNode>
{

    public IdentifierEmitter(IJSEmitter emitter)
    {
        super(emitter);
    }

    @Override
    public void emit(IIdentifierNode node)
    {
    	if (node instanceof NonResolvingIdentifierNode)
    	{
            startMapping(node);
    		write(node.getName());
            endMapping(node);
    		return;
    	}
        IDefinition nodeDef = ((IIdentifierNode) node).resolve(getProject());

        IASNode parentNode = node.getParent();
        ASTNodeID parentNodeId = parentNode.getNodeID();
        IASNode grandparentNode = parentNode.getParent();
        ASTNodeID grandparentNodeId = (parentNode != null) ? grandparentNode.getNodeID() : null;

        boolean identifierIsAccessorFunction = nodeDef instanceof AccessorDefinition;
        boolean identifierIsPlainFunction = nodeDef instanceof FunctionDefinition
                && !identifierIsAccessorFunction;
        boolean emitName = true;
    	JSRoyaleEmitter fjs = (JSRoyaleEmitter)getEmitter();
    	boolean isCustomNamespace = false;
    	boolean isStatic = nodeDef != null && nodeDef.isStatic();
        if (nodeDef instanceof FunctionDefinition &&
          	  fjs.isCustomNamespace((FunctionDefinition)nodeDef))
          	isCustomNamespace = true;

        if (isStatic)
        {
            String sname = nodeDef.getParent().getQualifiedName();
            if (sname.equals("Array"))
            {
            	String baseName = nodeDef.getBaseName();
            	if (baseName.equals("CASEINSENSITIVE"))
            	{
                    startMapping(parentNode);
            		write("1");
                    endMapping(parentNode);
            		return;
            	}
            	else if (baseName.equals("DESCENDING"))
            	{
                    startMapping(parentNode);
            		write("2");
                    endMapping(parentNode);
            		return;
            	}
            	else if (baseName.equals("UNIQUESORT"))
            	{
                    startMapping(parentNode);
            		write("4");
                    endMapping(parentNode);
            		return;
            	}
            	else if (baseName.equals("RETURNINDEXEDARRAY"))
            	{
                    startMapping(parentNode);
            		write("8");
                    endMapping(parentNode);
            		return;
            	}
            	else if (baseName.equals("NUMERIC"))
            	{
                    startMapping(parentNode);
            		write("16");
                    endMapping(parentNode);
            		return;
            	}
            }
            else if (sname.equals("int"))
            {
            	String baseName = nodeDef.getBaseName();
            	if (baseName.equals("MAX_VALUE"))
            	{
                    startMapping(parentNode);
            		write("2147483648");
                    endMapping(parentNode);
            		return;
            	}
            	else if (baseName.equals("MIN_VALUE"))
            	{
                    startMapping(parentNode);
            		write("-2147483648");
                    endMapping(parentNode);
            		return;
            	}
            }
            else if (sname.equals("uint"))
            {
            	String baseName = nodeDef.getBaseName();
            	if (baseName.equals("MAX_VALUE"))
            	{
                    startMapping(parentNode);
            		write("4294967295");
                    endMapping(parentNode);
            		return;
            	}
            	else if (baseName.equals("MIN_VALUE"))
            	{
                    startMapping(parentNode);
            		write("0");
                    endMapping(parentNode);
            		return;
            	}
            }
            if (sname.length() > 0)
            {
                IASNode prevSibling = parentNode.getChild(0);
                if(prevSibling == node)
                {
                    startMapping(parentNode);
                }
                else
                {
                    startMapping(prevSibling);
                }
                write(getEmitter().formatQualifiedName(sname));
                if(prevSibling != node)
                {
                    endMapping(prevSibling);
                    startMapping(parentNode, prevSibling);
                }
                if (!isCustomNamespace && (!(identifierIsAccessorFunction && isStatic)))
                	write(ASEmitterTokens.MEMBER_ACCESS);
                endMapping(parentNode);
            }
        }
        else if (!NativeUtils.isNative(node.getName()))
        {
            // an instance method not in a function call or member access
            boolean generateClosure = identifierIsPlainFunction && ((FunctionDefinition) nodeDef)
                    .getFunctionClassification() == FunctionClassification.CLASS_MEMBER &&
                    (!(parentNodeId == ASTNodeID.FunctionCallID || parentNodeId == ASTNodeID.MemberAccessExpressionID));
            if (generateClosure)
            {
                getEmitter().emitClosureStart();
            }

            boolean wroteSelf = false;
            if (EmitterUtils.writeThis(getProject(), getModel(), node))
            {
                IFunctionObjectNode functionObjectNode = (IFunctionObjectNode) node
                        .getParent().getAncestorOfType(
                                IFunctionObjectNode.class);

                IFunctionNode functionNode = (IFunctionNode) node
                .getParent().getAncestorOfType(
                        IFunctionNode.class);
                IFunctionDefinition functionDef = null;
                if (functionNode != null)
                	functionDef = functionNode.getDefinition();

                startMapping(node);
                if (functionObjectNode != null)
                {
                    write(JSGoogEmitterTokens.SELF);
                    wroteSelf = true;
                }
                else if (functionNode != null && functionDef.getFunctionClassification() == FunctionClassification.LOCAL)
                {
                    write(JSGoogEmitterTokens.SELF);
                    wroteSelf = true;
                }
                else
                    write(ASEmitterTokens.THIS);

                if (!isCustomNamespace)
                	write(ASEmitterTokens.MEMBER_ACCESS);
                endMapping(node);
            }

            if (generateClosure)
            {
                if (isCustomNamespace)
                {
                	Namespace ns = (Namespace)((INamespaceResolvedReference)((FunctionDefinition)nodeDef).getNamespaceReference()).resolveAETNamespace(getProject());
                	INamespaceDefinition nsDef = ((FunctionDefinition)nodeDef).getNamespaceReference().resolveNamespaceReference(getProject());
        			fjs.formatQualifiedName(nsDef.getQualifiedName()); // register with used names 
                	String nsName = ns.getName();
                	write("[\"" + nsName + "::" + node.getName() + "\"]");
                }
                else
                	write(node.getName());

                writeToken(ASEmitterTokens.COMMA);
                if (wroteSelf)
                    write(JSGoogEmitterTokens.SELF);
                else
                	write(ASEmitterTokens.THIS);
                getEmitter().emitClosureEnd(node, nodeDef);
                emitName = false;
            }
        }

        //IDefinition parentDef = (nodeDef != null) ? nodeDef.getParent() : null;
        //boolean isNative = (parentDef != null)
        //        && NativeUtils.isNative(parentDef.getBaseName());
        if (emitName)
        {
            if (nodeDef != null)
            {
                // this can be optimized but this way lets
                // us breakpoint on the node.getName() to make
                // sure it is ok to always use the short name in an MAE
                String qname = nodeDef.getQualifiedName();
                boolean isPackageOrFileMember = false;
                if (nodeDef instanceof IVariableDefinition)
                {
                    IVariableDefinition variable = (IVariableDefinition) nodeDef;
                    VariableClassification classification = variable.getVariableClassification();
                    if (classification == VariableClassification.PACKAGE_MEMBER ||
                            classification == VariableClassification.FILE_MEMBER)
                    {
                        isPackageOrFileMember = true;
                    }
                }
                else if (nodeDef instanceof IFunctionDefinition)
                {
                    IFunctionDefinition func = (IFunctionDefinition) nodeDef;
                    FunctionClassification classification = func.getFunctionClassification();
                    if (classification == FunctionClassification.PACKAGE_MEMBER ||
                            classification == FunctionClassification.FILE_MEMBER)
                    {
                        isPackageOrFileMember = true;
                    }
                }
                boolean needsFormattedName = false;
                if (isPackageOrFileMember && parentNodeId == ASTNodeID.MemberAccessExpressionID)
                {
                    IMemberAccessExpressionNode parentMemberAccessNode = (IMemberAccessExpressionNode) parentNode;
                    //if the package or file member isn't on the left side of a
                    //member access expression, it shouldn't be fully qualified
                    needsFormattedName = parentMemberAccessNode.getLeftOperandNode() == node;
                }
                startMapping(node);
                if (parentNodeId == ASTNodeID.MemberAccessExpressionID)
                {
                	if (needsFormattedName)
                	{
                	    write(getEmitter().formatQualifiedName(qname));
                	}
                    else if (isCustomNamespace)
                    {
                    	String ns = ((INamespaceResolvedReference)((FunctionDefinition)nodeDef).getNamespaceReference()).resolveAETNamespace(getProject()).getName();
                    	write("[\"" + ns + "::" + qname + "\"]");
                    }
                    else if (identifierIsAccessorFunction && isStatic)
                    {
                    	write("[\"" +node.getName() + "\"]");                    	
                    }
                	else
                	{
                		write(node.getName());
                	}
                }
                else if (isPackageOrFileMember)
                    write(getEmitter().formatQualifiedName(qname));
                else if (nodeDef instanceof TypeDefinitionBase)
                    write(getEmitter().formatQualifiedName(qname));
                else if (isCustomNamespace)
                {
                	String ns = ((INamespaceResolvedReference)((FunctionDefinition)nodeDef).getNamespaceReference()).resolveAETNamespace(getProject()).getName();
                	write("[\"" + ns + "::" + qname + "\"]");
                }
                else if (identifierIsAccessorFunction && isStatic)
                {
                	write("[\"" + qname + "\"]");                    	
                }
                else
                    write(qname);
                endMapping(node);
            }
            else if (grandparentNodeId == ASTNodeID.E4XFilterID &&
            		(!(parentNodeId == ASTNodeID.MemberAccessExpressionID || parentNodeId == ASTNodeID.Op_DescendantsID)))
            {
                startMapping(node);
                write("child('");
                write(node.getName());
                write("')");
                endMapping(node);            	
            }
            else
            {
                startMapping(node);
                write(node.getName());
                endMapping(node);
            }
        }
    }

}
