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

import java.io.FilterWriter;
import java.util.ArrayList;
import java.util.List;

import org.apache.royale.compiler.codegen.IASGlobalFunctionConstants;
import org.apache.royale.compiler.codegen.IDocEmitter;
import org.apache.royale.compiler.codegen.IEmitter;
import org.apache.royale.compiler.codegen.ISubEmitter;
import org.apache.royale.compiler.codegen.js.IJSEmitter;
import org.apache.royale.compiler.codegen.js.IMappingEmitter;
import org.apache.royale.compiler.common.ISourceLocation;
import org.apache.royale.compiler.constants.IASLanguageConstants;
import org.apache.royale.compiler.constants.IMetaAttributeConstants;
import org.apache.royale.compiler.constants.IASLanguageConstants.BuiltinType;
import org.apache.royale.compiler.definitions.IAppliedVectorDefinition;
import org.apache.royale.compiler.definitions.IClassDefinition;
import org.apache.royale.compiler.definitions.IDefinition;
import org.apache.royale.compiler.definitions.metadata.IMetaTag;
import org.apache.royale.compiler.definitions.metadata.IMetaTagAttribute;
import org.apache.royale.compiler.internal.codegen.as.ASEmitter;
import org.apache.royale.compiler.internal.codegen.as.ASEmitterTokens;
import org.apache.royale.compiler.internal.codegen.js.jx.BlockCloseEmitter;
import org.apache.royale.compiler.internal.codegen.js.jx.BlockOpenEmitter;
import org.apache.royale.compiler.internal.codegen.js.jx.CatchEmitter;
import org.apache.royale.compiler.internal.codegen.js.jx.DoWhileLoopEmitter;
import org.apache.royale.compiler.internal.codegen.js.jx.DynamicAccessEmitter;
import org.apache.royale.compiler.internal.codegen.js.jx.ForLoopEmitter;
import org.apache.royale.compiler.internal.codegen.js.jx.FunctionCallArgumentsEmitter;
import org.apache.royale.compiler.internal.codegen.js.jx.IfEmitter;
import org.apache.royale.compiler.internal.codegen.js.jx.IterationFlowEmitter;
import org.apache.royale.compiler.internal.codegen.js.jx.LanguageIdentifierEmitter;
import org.apache.royale.compiler.internal.codegen.js.jx.LiteralContainerEmitter;
import org.apache.royale.compiler.internal.codegen.js.jx.MemberKeywordEmitter;
import org.apache.royale.compiler.internal.codegen.js.jx.NumericLiteralEmitter;
import org.apache.royale.compiler.internal.codegen.js.jx.ObjectLiteralValuePairEmitter;
import org.apache.royale.compiler.internal.codegen.js.jx.ParameterEmitter;
import org.apache.royale.compiler.internal.codegen.js.jx.ParametersEmitter;
import org.apache.royale.compiler.internal.codegen.js.jx.ReturnEmitter;
import org.apache.royale.compiler.internal.codegen.js.jx.SourceMapDirectiveEmitter;
import org.apache.royale.compiler.internal.codegen.js.jx.StatementEmitter;
import org.apache.royale.compiler.internal.codegen.js.jx.SwitchEmitter;
import org.apache.royale.compiler.internal.codegen.js.jx.TernaryOperatorEmitter;
import org.apache.royale.compiler.internal.codegen.js.jx.ThrowEmitter;
import org.apache.royale.compiler.internal.codegen.js.jx.TryEmitter;
import org.apache.royale.compiler.internal.codegen.js.jx.UnaryOperatorEmitter;
import org.apache.royale.compiler.internal.codegen.js.jx.WhileLoopEmitter;
import org.apache.royale.compiler.internal.codegen.js.jx.WithEmitter;
import org.apache.royale.compiler.internal.codegen.js.royale.JSRoyaleDocEmitter;
import org.apache.royale.compiler.internal.codegen.js.royale.JSRoyaleEmitterTokens;
import org.apache.royale.compiler.internal.codegen.js.utils.EmitterUtils;
import org.apache.royale.compiler.internal.definitions.ParameterDefinition;
import org.apache.royale.compiler.internal.projects.RoyaleJSProject;
import org.apache.royale.compiler.internal.semantics.SemanticUtils;
import org.apache.royale.compiler.internal.tree.as.*;
import org.apache.royale.compiler.projects.ICompilerProject;
import org.apache.royale.compiler.tree.as.*;

import com.google.debugging.sourcemap.FilePosition;

import org.apache.royale.compiler.internal.codegen.as.ASEmitterTokens;
import org.apache.royale.compiler.utils.DefinitionUtils;
import org.apache.royale.compiler.utils.NativeUtils;

/**
 * @author Michael Schmalle
 */
public class JSEmitter extends ASEmitter implements IJSEmitter
{
    private JSSessionModel model;
    
    public ISubEmitter<IContainerNode> blockOpenEmitter;
    public ISubEmitter<IContainerNode> blockCloseEmitter;
    public ISubEmitter<INumericLiteralNode> numericLiteralEmitter;
    public ISubEmitter<IContainerNode> parametersEmitter;
    public ISubEmitter<IParameterNode> parameterEmitter;
    public ISubEmitter<IContainerNode> functionCallArgumentsEmitter;
    public ISubEmitter<ILiteralContainerNode> literalContainerEmitter;
    public ISubEmitter<IObjectLiteralValuePairNode> objectLiteralValuePairEmitter;
    public ISubEmitter<IReturnNode> returnEmitter;
    public ISubEmitter<IDynamicAccessNode> dynamicAccessEmitter;
    public ISubEmitter<IUnaryOperatorNode> unaryOperatorEmitter;
    public ISubEmitter<ITernaryOperatorNode> ternaryOperatorEmitter;
    public ISubEmitter<IDefinitionNode> memberKeywordEmitter;
    public ISubEmitter<IIfNode> ifEmitter;
    public ISubEmitter<ISwitchNode> switchEmitter;
    public ISubEmitter<IWhileLoopNode> whileLoopEmitter;
    public ISubEmitter<IWhileLoopNode> doWhileLoopEmitter;
    public ISubEmitter<IForLoopNode> forLoopEmitter;
    public ISubEmitter<IIterationFlowNode> interationFlowEmitter;
    public ISubEmitter<ITryNode> tryEmitter;
    public ISubEmitter<ICatchNode> catchEmitter;
    public ISubEmitter<IThrowNode> throwEmitter;
    public ISubEmitter<IWithNode> withEmitter;
    public ISubEmitter<IASNode> statementEmitter;
    public ISubEmitter<ILanguageIdentifierNode> languageIdentifierEmitter;
    public SourceMapDirectiveEmitter sourceMapDirectiveEmitter;
    
    @Override
    public JSSessionModel getModel()
    {
        return model;
    }
    
    private SourceMapMapping lastMapping;
    
    private List<SourceMapMapping> sourceMapMappings;
    
    public List<SourceMapMapping> getSourceMapMappings()
    {
        return sourceMapMappings;
    }

    public JSEmitter(FilterWriter out)
    {
        super(out);
        
        model = new JSSessionModel();
        sourceMapMappings = new ArrayList<SourceMapMapping>();

        blockOpenEmitter = new BlockOpenEmitter(this);
        blockCloseEmitter = new BlockCloseEmitter(this);
        numericLiteralEmitter = new NumericLiteralEmitter(this);
        parametersEmitter = new ParametersEmitter(this);
        parameterEmitter = new ParameterEmitter(this);
        functionCallArgumentsEmitter = new FunctionCallArgumentsEmitter(this);
        literalContainerEmitter = new LiteralContainerEmitter(this);
        objectLiteralValuePairEmitter = new ObjectLiteralValuePairEmitter(this);
        returnEmitter = new ReturnEmitter(this);
        dynamicAccessEmitter = new DynamicAccessEmitter(this);
        unaryOperatorEmitter = new UnaryOperatorEmitter(this);
        ternaryOperatorEmitter = new TernaryOperatorEmitter(this);
        memberKeywordEmitter = new MemberKeywordEmitter(this);
        ifEmitter = new IfEmitter(this);
        switchEmitter = new SwitchEmitter(this);
        whileLoopEmitter = new WhileLoopEmitter(this);
        doWhileLoopEmitter = new DoWhileLoopEmitter(this);
        forLoopEmitter = new ForLoopEmitter(this);
        interationFlowEmitter = new IterationFlowEmitter(this);
        tryEmitter = new TryEmitter(this);
        catchEmitter = new CatchEmitter(this);
        throwEmitter = new ThrowEmitter(this);
        withEmitter = new WithEmitter(this);
        statementEmitter = new StatementEmitter(this);
        languageIdentifierEmitter = new LanguageIdentifierEmitter(this);
        sourceMapDirectiveEmitter = new SourceMapDirectiveEmitter(this);
    }

    @Override
    public String formatQualifiedName(String name)
    {
        return name;
    }
    
    @Override
    public void emitLocalNamedFunction(IFunctionNode node)
    {
        startMapping(node);
        FunctionNode fnode = (FunctionNode)node;
        write(ASEmitterTokens.FUNCTION);
        write(ASEmitterTokens.SPACE);
        write(fnode.getName());
        endMapping(node);
        emitParameters(fnode.getParametersContainerNode());
        emitFunctionScope(fnode.getScopedNode());
    }
    
    @Override
    public void emitFunctionObject(IFunctionObjectNode node)
    {
        startMapping(node);
        FunctionNode fnode = node.getFunctionNode();
        write(ASEmitterTokens.FUNCTION);
        String name = fnode.getName();
        //may have a name, or may be anonymous
        if(name.length() > 0)
        {
            write(ASEmitterTokens.SPACE);
            write(name);
        }
        endMapping(node);
        emitParameters(fnode.getParametersContainerNode());
        emitFunctionScope(fnode.getScopedNode());
    }

    public void emitClosureStart()
    {
    
    }

    public void emitClosureEnd(IASNode node, IDefinition nodeDef)
    {
    
    }
    
    public void emitSourceMapDirective(ITypeNode node)
    {
        sourceMapDirectiveEmitter.isExterns = getModel().isExterns;
        sourceMapDirectiveEmitter.emit(node);
    }

    public void emitParameters(IContainerNode node)
    {
        parametersEmitter.emit(node);
    }

    @Override
    public void emitParameter(IParameterNode node)
    {
        parameterEmitter.emit(node);
    }

    @Override
    public void emitArguments(IContainerNode node)
    {
        functionCallArgumentsEmitter.emit(node);
    }

    @Override
    public void emitNumericLiteral(INumericLiteralNode node)
    {
        numericLiteralEmitter.emit(node);
    }

    @Override
    public void emitLiteralContainer(ILiteralContainerNode node)
    {
        literalContainerEmitter.emit(node);
    }

    @Override
    public void emitObjectLiteralValuePair(IObjectLiteralValuePairNode node)
    {
        objectLiteralValuePairEmitter.emit(node);
    }

    @Override
    public void emitTry(ITryNode node)
    {
        tryEmitter.emit(node);
    }

    @Override
    public void emitCatch(ICatchNode node)
    {
        catchEmitter.emit(node);
    }

    @Override
    public void emitWith(IWithNode node)
    {
        withEmitter.emit(node);
    }

    @Override
    public void emitThrow(IThrowNode node)
    {
        throwEmitter.emit(node);
    }

    @Override
    public void emitReturn(IReturnNode node)
    {
        returnEmitter.emit(node);
    }

    @Override
    public void emitTypedExpression(ITypedExpressionNode node)
    {
        write(JSEmitterTokens.ARRAY);
    }

    @Override
    public void emitDynamicAccess(IDynamicAccessNode node)
    {
        dynamicAccessEmitter.emit(node);
    }

    @Override
    public void emitMemberKeyword(IDefinitionNode node)
    {
        memberKeywordEmitter.emit(node);
    }

    @Override
    public void emitUnaryOperator(IUnaryOperatorNode node)
    {
        unaryOperatorEmitter.emit(node);
    }

    @Override
    public void emitTernaryOperator(ITernaryOperatorNode node)
    {
        ternaryOperatorEmitter.emit(node);
    }

    @Override
    public void emitLanguageIdentifier(ILanguageIdentifierNode node)
    {
        languageIdentifierEmitter.emit(node);
    }

    @Override
    public void emitStatement(IASNode node)
    {
        statementEmitter.emit(node);
    }

    @Override
    public void emitIf(IIfNode node)
    {
        ifEmitter.emit(node);
    }

    @Override
    public void emitSwitch(ISwitchNode node)
    {
        switchEmitter.emit(node);
    }

    @Override
    public void emitImport(IImportNode node)
    {
        // do nothing
    }

    @Override
    public void emitWhileLoop(IWhileLoopNode node)
    {
        whileLoopEmitter.emit(node);
    }

    @Override
    public void emitDoLoop(IWhileLoopNode node)
    {
        doWhileLoopEmitter.emit(node);
    }

    @Override
    public void emitForLoop(IForLoopNode node)
    {
        forLoopEmitter.emit(node);
    }

    @Override
    public void emitIterationFlow(IIterationFlowNode node)
    {
        interationFlowEmitter.emit(node);
    }

    @Override
    public void emitBlockOpen(IContainerNode node)
    {
        blockOpenEmitter.emit(node);
    }

    @Override
    public void emitBlockClose(IContainerNode node)
    {
        blockCloseEmitter.emit(node);
    }

    public void startMapping(ISourceLocation node)
    {
        startMapping(node, node.getLine(), node.getColumn());
    }

    public void startMapping(ISourceLocation node, String symbolName)
    {
        startMapping(node, symbolName, node.getLine(), node.getColumn());
    }
    
    public void startMapping(ISourceLocation node, int line, int column)
    {
        startMapping(node, null, line, column);
    }

    public void startMapping(ISourceLocation node, ISourceLocation afterNode)
    {
        startMapping(node, afterNode.getEndLine(), afterNode.getEndColumn());
    }
    
    public void startMapping(ISourceLocation node, String symbolName, int line, int column)
    {
        String sourcePath = node.getSourcePath();
        if (sourcePath == null)
        {
            //if the source path is null, this node may have been generated by
            //the compiler automatically. for example, an untyped variable will
            //have a node for the * type.
            if (node instanceof IASNode)
            {
                IASNode parentNode = ((IASNode) node).getParent();
                if (parentNode != null)
                {
                    //try the parent node
                    startMapping(parentNode, symbolName, line, column);
                    return;
                }
            }
        }

        startMapping(sourcePath, symbolName, line, column);
    }
    
    public void startMapping(String sourcePath, String symbolName, int line, int column)
    {
        if (isBufferWrite())
        {
            return;
        }

        IEmitter parentEmitter = getParentEmitter();
        if (parentEmitter != null && parentEmitter instanceof IMappingEmitter)
        {
            IMappingEmitter mappingParent = (IMappingEmitter) parentEmitter;
            mappingParent.startMapping(sourcePath, symbolName, line, column);
            return;
        }
        if (lastMapping != null)
        {
            FilePosition sourceStartPosition = lastMapping.sourceStartPosition;
            throw new IllegalStateException("Cannot start new mapping when another mapping is already started. "
                    + "Previous mapping at Line " + sourceStartPosition.getLine()
                    + " and Column " + sourceStartPosition.getColumn()
                    + " in file " + lastMapping.sourcePath);
        }
        
        //prefer forward slash
        sourcePath = sourcePath.replace('\\', '/');

        SourceMapMapping mapping = new SourceMapMapping();
        mapping.sourcePath = sourcePath;
        mapping.name = symbolName;
        mapping.sourceStartPosition = new FilePosition(line, column);
        mapping.destStartPosition = new FilePosition(getCurrentLine(), getCurrentColumn());
        lastMapping = mapping;
    }

    public void endMapping(ISourceLocation node)
    {
        if (isBufferWrite())
        {
            return;
        }
        IEmitter parentEmitter = getParentEmitter();
        if (parentEmitter != null && parentEmitter instanceof IMappingEmitter)
        {
            IMappingEmitter mappingParent = (IMappingEmitter) parentEmitter;
            mappingParent.endMapping(node);
            return;
        }
        if (lastMapping == null)
        {
            throw new IllegalStateException("Cannot end mapping when a mapping has not been started");
        }

        lastMapping.destEndPosition = new FilePosition(getCurrentLine(), getCurrentColumn());
        sourceMapMappings.add(lastMapping);
        lastMapping = null;
    }

    /**
     * Adjusts the line numbers saved in the source map when a line should be
     * added during post processing.
     *
     * @param lineIndex
     */
    protected void addLineToMappings(int lineIndex)
    {
        for (SourceMapMapping mapping : sourceMapMappings)
        {
            FilePosition destStartPosition = mapping.destStartPosition;
            int startLine = destStartPosition.getLine();
            if(startLine > lineIndex)
            {
                mapping.destStartPosition = new FilePosition(startLine + 1, destStartPosition.getColumn());
                FilePosition destEndPosition = mapping.destEndPosition;
                mapping.destEndPosition = new FilePosition(destEndPosition.getLine() + 1, destEndPosition.getColumn());
            }
        }
    }

    /**
     * Adjusts the line numbers saved in the source map when a line should be
     * removed during post processing.
     *
     * @param lineIndex
     */
    protected void removeLineFromMappings(int lineIndex)
    {
        for (SourceMapMapping mapping : sourceMapMappings)
        {
            FilePosition destStartPosition = mapping.destStartPosition;
            int startLine = destStartPosition.getLine();
            if(startLine > lineIndex)
            {
                mapping.destStartPosition = new FilePosition(startLine - 1, destStartPosition.getColumn());
                FilePosition destEndPosition = mapping.destEndPosition;
                mapping.destEndPosition = new FilePosition(destEndPosition.getLine() - 1, destEndPosition.getColumn());
            }
        }
    }

	@Override
	public String formatPrivateName(String className, String name) {
		// TODO Auto-generated method stub
		return className.replace(".", "_") + "_" + name;
	}

    public String formatPrivateName(String className, String name, Boolean nameFirst) {
        if (nameFirst) {
            return name + "_" +className.replace(".", "_");
        } else {
            return formatPrivateName(className, name);
        }
    }

    public void emitAssignmentCoercion(IExpressionNode assignedNode, IDefinition definition)
    {
        IDefinition assignedDef = null;
        IDefinition assignedTypeDef = null;
        ICompilerProject project = getWalker().getProject();
        boolean isXML = false;
        if (assignedNode != null)
        {
            assignedDef = assignedNode.resolve(project);
            assignedTypeDef = assignedNode.resolveType(project);
            if (assignedTypeDef == null || project.getBuiltinType(BuiltinType.ANY_TYPE).equals(assignedTypeDef))
            {
                IDefinition resolvedXMLDef = SemanticUtils.resolveXML(assignedNode, project);
                if (resolvedXMLDef != null)
                {
                    assignedDef = resolvedXMLDef;
                    assignedTypeDef = SemanticUtils.resolveTypeXML(assignedNode, project);
                    isXML = true;
                }
            } else if (SemanticUtils.isXMLish(assignedTypeDef, project)) {
                isXML = true;
            }
        }
		String coercionStart = null;
        String coercionEnd = null;
        boolean avoidCoercion = false;
		if (project.getBuiltinType(BuiltinType.INT).equals(definition))
		{
			boolean needsCoercion = false;
			if (assignedNode instanceof INumericLiteralNode)
			{
				INumericLiteralNode numericLiteral = (INumericLiteralNode) assignedNode;
                INumericLiteralNode.INumericValue numericValue = numericLiteral.getNumericValue();
                startMapping(assignedNode);
                if(numericValue.toString().startsWith("0x"))
                {
                    //for readability, keep the same formatting
                    write("0x" + Integer.toHexString(numericValue.toInt32()));
                }
                else
                {
                    write(Integer.toString(numericValue.toInt32()));
                }
                endMapping(assignedNode);
                return;
			}
			else if(assignedNode instanceof BinaryOperatorAsNode)
            {
                needsCoercion = true;
            }
			else if(!project.getBuiltinType(BuiltinType.INT).equals(assignedTypeDef))
			{
				needsCoercion = true;
			}
			if (needsCoercion)
			{
				coercionStart = "(";
				coercionEnd = ") >> 0";
			}
		}
		else if (project.getBuiltinType(BuiltinType.UINT).equals(definition))
		{
			boolean needsCoercion = false;
			if (assignedNode instanceof INumericLiteralNode)
			{
                INumericLiteralNode numericLiteral = (INumericLiteralNode) assignedNode;
                INumericLiteralNode.INumericValue numericValue = numericLiteral.getNumericValue();
                startMapping(assignedNode);
                if(numericValue.toString().startsWith("0x"))
                {
                    //for readability, keep the same formatting
                    write("0x" + Long.toHexString(numericValue.toUint32()));
                }
                else
                {
                    write(Long.toString(numericValue.toUint32()));
                }
                endMapping(assignedNode);
                return;
			}
            else if(assignedNode instanceof BinaryOperatorAsNode)
            {
                needsCoercion = true;
            }
			else if(!project.getBuiltinType(BuiltinType.UINT).equals(assignedTypeDef))
			{
				needsCoercion = true;
			}
			if (needsCoercion)
			{
				coercionStart = "(";
				coercionEnd = ") >>> 0";
            }
        }
        else if (project.getBuiltinType(BuiltinType.NUMBER).equals(definition)
                && !project.getBuiltinType(BuiltinType.NUMBER).equals(assignedTypeDef)
                && !project.getBuiltinType(BuiltinType.INT).equals(assignedTypeDef)
                && !project.getBuiltinType(BuiltinType.UINT).equals(assignedTypeDef))
        {
			boolean needsCoercion = true;
            if (assignedNode instanceof IDynamicAccessNode)
            {
                IDynamicAccessNode dynamicAccess = (IDynamicAccessNode) assignedNode;
                IDefinition dynamicAccessIndexDef = dynamicAccess.getRightOperandNode().resolveType(project);
                if (project.getBuiltinType(BuiltinType.NUMBER).equals(dynamicAccessIndexDef))
                {
                    IDefinition leftDef = dynamicAccess.getLeftOperandNode().resolveType(project);
                    if (leftDef != null) {
                        IMetaTag[] metas = leftDef.getAllMetaTags();
                        for (IMetaTag meta : metas)
                        {
                            if (meta.getTagName().equals(IMetaAttributeConstants.ATTRIBUTE_ARRAYELEMENTTYPE))
                            {
                                IMetaTagAttribute[] attrs = meta.getAllAttributes();
                                for (IMetaTagAttribute attr : attrs)
                                {
                                    String t = attr.getValue();
                                    if (t.equals(IASLanguageConstants.Number))
                                    {
                                        needsCoercion = false;
                                        //explicitly prevent other coercion detection rules from picking this up
                                        avoidCoercion = true;
                                    }
                                }
                            }
                        }
                    }
                }
            }
            if (needsCoercion)
            {
                coercionStart = "Number(";
            }
        }
        else if (project.getBuiltinType(BuiltinType.BOOLEAN).equals(definition)
                && !project.getBuiltinType(BuiltinType.BOOLEAN).equals(assignedTypeDef))
        {
            if (project.getBuiltinType(BuiltinType.NULL).equals(assignedTypeDef)
                    || (assignedDef != null && assignedDef.getQualifiedName().equals(IASLanguageConstants.UNDEFINED)))
            {
                //null and undefined are coerced to false
                startMapping(assignedNode);
                write(IASLanguageConstants.FALSE);
                endMapping(assignedNode);
                return;
            }
			if (assignedNode instanceof INumericLiteralNode)
			{
                INumericLiteralNode numericLiteral = (INumericLiteralNode) assignedNode;
                INumericLiteralNode.INumericValue numericValue = numericLiteral.getNumericValue();
                //zero is coerced to false, and everything else is true
                String booleanValue = numericValue.toNumber() == 0.0
                        ? IASLanguageConstants.FALSE
                        : IASLanguageConstants.TRUE;
                startMapping(assignedNode);
                write(booleanValue);
                endMapping(assignedNode);
                return;
			}
            coercionStart = "!!(";
        }
        else if (project.getBuiltinType(BuiltinType.STRING).equals(definition)
                && !project.getBuiltinType(BuiltinType.STRING).equals(assignedTypeDef)
                && !project.getBuiltinType(BuiltinType.NULL).equals(assignedTypeDef)
                && !(project.getBuiltinType(BuiltinType.ANY_TYPE).equals(assignedTypeDef)
                        && SemanticUtils.isToStringFunctionCall(assignedNode, project)))
        {
            if(assignedDef != null && assignedDef.getQualifiedName().equals(IASLanguageConstants.UNDEFINED))
            {
                //undefined is coerced to null
                startMapping(assignedNode);
                write(IASLanguageConstants.NULL);
                endMapping(assignedNode);
                return;
            }
            boolean emitStringCoercion = true;
            IDocEmitter docEmitter = getDocEmitter();
            if (docEmitter instanceof JSRoyaleDocEmitter)
            {
                JSRoyaleDocEmitter royaleDocEmitter = (JSRoyaleDocEmitter) docEmitter;
                emitStringCoercion = royaleDocEmitter.emitStringConversions;
            }
            if (emitStringCoercion)
            {
                if (isXML) {
                    if (EmitterUtils.xmlRequiresNullCheck((NodeBase) assignedNode, project)) {
                        //if it could be a null reference use the XMLList.coerce_string method, which retains null
                        coercionStart = "XMLList.coerce_string(";
                    } else {
                        coercionStart = "String(";
                    }
                }
                else coercionStart = "org.apache.royale.utils.Language.string(";
            }
        }
        if ( assignedDef != null
                && assignedDef instanceof IAppliedVectorDefinition
                && assignedNode instanceof TypedExpressionNode) {
            //assign a Vector class as the assigned value, e.g. var c:Class = Vector.<int>
            if (project instanceof RoyaleJSProject
                    && ((RoyaleJSProject)project).config.getJsVectorEmulationClass() != null) {
                startMapping(assignedNode);
                write(((RoyaleJSProject)project).config.getJsVectorEmulationClass());
                endMapping(assignedNode);
            } else {
                startMapping(assignedNode);
                write(JSRoyaleEmitterTokens.SYNTH_VECTOR);
                write(ASEmitterTokens.PAREN_OPEN);
                write(ASEmitterTokens.SINGLE_QUOTE);
                //the element type of the Vector:
                write(formatQualifiedName(((TypedExpressionNode)assignedNode).getTypeNode().resolve(project).getQualifiedName()));
                write(ASEmitterTokens.SINGLE_QUOTE);
                write(ASEmitterTokens.PAREN_CLOSE);
                endMapping(assignedNode);
                if (project instanceof RoyaleJSProject)
                    ((RoyaleJSProject)project).needLanguage = true;
                getModel().needLanguage = true;
            }
            return;
        }
        if (assignedDef instanceof IClassDefinition
                && assignedNode instanceof IdentifierNode
                && ((IdentifierNode)assignedNode).getName().equals(IASGlobalFunctionConstants.Vector)
                && project instanceof RoyaleJSProject
                && ((RoyaleJSProject)project).config.getJsVectorEmulationClass() == null   ){
            startMapping(assignedNode);
            write(JSRoyaleEmitterTokens.SYNTH_VECTOR);
            write(ASEmitterTokens.PAREN_OPEN);
            //null to signify not a valid constructor
            write(ASEmitterTokens.NULL);
            write(ASEmitterTokens.PAREN_CLOSE);
            endMapping(assignedNode);
            if (project instanceof RoyaleJSProject)
                ((RoyaleJSProject)project).needLanguage = true;
            getModel().needLanguage = true;
            return;
        }
        //special case for 'rewritten' multicatch support - we avoid coercion because it is assured via an injected 'is' check immediately prior to declaration/assignment
        if (DefinitionUtils.isRewrittenMultiCatchParam(assignedDef)) {
            avoidCoercion = true;
        }
        if (coercionStart == null
                && !avoidCoercion
                && assignedTypeDef !=null
                && definition !=null
                && (project.getBuiltinType(BuiltinType.ANY_TYPE).equals(assignedTypeDef)
                || project.getBuiltinType(BuiltinType.OBJECT).equals(assignedTypeDef))
                && !(project.getBuiltinType(BuiltinType.ANY_TYPE).equals(definition)
                        || project.getBuiltinType(BuiltinType.OBJECT).equals(definition))) {
            //catch leftovers: remaining implicit coercion of loosely typed assigned values to strongly typed context
            //assignment to Class definitions is excluded because there is no 'Class' type in JS
            //Possibility: 'Class' could be implemented as a synthType
            boolean needsCoercion = ((RoyaleJSProject)project).config.getJsComplexImplicitCoercions();
    
            IDocEmitter docEmitter = getDocEmitter();
            if (docEmitter instanceof JSRoyaleDocEmitter)
            {
                JSRoyaleDocEmitter royaleDocEmitter = (JSRoyaleDocEmitter) docEmitter;
                //check for local toggle
                if (needsCoercion) needsCoercion = !(royaleDocEmitter.getLocalSettingAsBoolean(
                        JSRoyaleEmitterTokens.SUPPRESS_COMPLEX_IMPLICIT_COERCION, false));
                else {
                    if (royaleDocEmitter.hasLocalSetting(JSRoyaleEmitterTokens.SUPPRESS_COMPLEX_IMPLICIT_COERCION.getToken())) {
                        needsCoercion = !(royaleDocEmitter.getLocalSettingAsBoolean(
                                JSRoyaleEmitterTokens.SUPPRESS_COMPLEX_IMPLICIT_COERCION, false));
                    }
                }
                if (needsCoercion) {
                    //check for individual specified suppression
                    
                    String definitionName = definition.getQualifiedName();
                    //for Vectors, use the unqualified name to match the source code
                    if (NativeUtils.isVector(definitionName)) {
                        definitionName = definition.getBaseName();
                    }
                    
                    if (royaleDocEmitter.getLocalSettingIncludesString(
                            JSRoyaleEmitterTokens.SUPPRESS_COMPLEX_IMPLICIT_COERCION,
                            definitionName))
                    {
                        needsCoercion = false;
                    }
                    
                }
            }
            
            //Avoid specific compile-time 'fake' class(es)
            if (needsCoercion && definition.getQualifiedName().equals("org.apache.royale.core.WrappedHTMLElement")) {
                //*actual* coercion fails here, because this is not actually instantiated, it is
                //simply a type definition representing the 'wrapped' (or tagged) HTMLElement
                needsCoercion = false;
            }
            
            //Avoid XML/XMLList:
            if (needsCoercion && project.getBuiltinType(BuiltinType.XML) != null) {
                if (project.getBuiltinType(BuiltinType.XML).equals(definition)
                        || project.getBuiltinType(BuiltinType.XMLLIST).equals(definition)) {
                    //XML/XMLList has complex output and would need more work
                    needsCoercion = false;
                }
            }
            
            //avoid scenario with ArrayElementType specified as metadata definition type - assume it is 'typed'
            if (needsCoercion && assignedNode instanceof IDynamicAccessNode)
            {
                IDynamicAccessNode dynamicAccess = (IDynamicAccessNode) assignedNode;
                IDefinition dynamicAccessIndexDef = dynamicAccess.getRightOperandNode().resolveType(project);
                if (project.getBuiltinType(BuiltinType.NUMBER).equals(dynamicAccessIndexDef))
                {
                    IDefinition leftDef = dynamicAccess.getLeftOperandNode().resolveType(project);
                    if (leftDef != null) {
                        IMetaTag[] metas = leftDef.getAllMetaTags();
                        for (IMetaTag meta : metas)
                        {
                            if (meta.getTagName().equals(IMetaAttributeConstants.ATTRIBUTE_ARRAYELEMENTTYPE))
                            {
                                IMetaTagAttribute[] attrs = meta.getAllAttributes();
                                for (IMetaTagAttribute attr : attrs)
                                {
                                    String t = attr.getValue();
                                    if (t.equals(definition.getQualifiedName()))
                                    {
                                        needsCoercion = false;
                                    }
                                }
                            }
                        }
                    }
                }
            }
            if (needsCoercion && project.getBuiltinType(BuiltinType.STRING).equals(definition)) {
                //explicit suppression of String coercion
                if (docEmitter instanceof JSRoyaleDocEmitter)
                {
                    JSRoyaleDocEmitter royaleDocEmitter = (JSRoyaleDocEmitter) docEmitter;
                    needsCoercion = royaleDocEmitter.emitStringConversions;
                }
                if (needsCoercion
                        && assignedNode instanceof FunctionCallNode
                        && ((FunctionCallNode) assignedNode).getNameNode() instanceof MemberAccessExpressionNode
                        && ((MemberAccessExpressionNode)((FunctionCallNode) assignedNode).getNameNode()).getRightOperandNode()  instanceof IdentifierNode
                        && ((IdentifierNode)(((MemberAccessExpressionNode)((FunctionCallNode) assignedNode).getNameNode()).getRightOperandNode())).getName().equals("toString")) {
                        //even if toString() is called in an untyped way, assume a call to a method named 'toString' is actually providing a String
                    needsCoercion = false;
                }
            }
            
            if (needsCoercion) {
                //add a comment tag leader, so implicit casts are identifiable in the output
                coercionStart = "/* implicit cast */ "
                        + JSRoyaleEmitterTokens.LANGUAGE_QNAME.getToken()
                        + ASEmitterTokens.MEMBER_ACCESS.getToken()
                        + ASEmitterTokens.AS.getToken()
                        + ASEmitterTokens.PAREN_OPEN.getToken();
                String coercionTypeString = formatQualifiedName(definition.getQualifiedName());
                if (NativeUtils.isSyntheticJSType(coercionTypeString)) {
                    String synthCall;
                    String synthethicType;
                    if (NativeUtils.isVector(coercionTypeString)) {
                        synthCall = JSRoyaleEmitterTokens.SYNTH_VECTOR.getToken();
                        synthethicType = formatQualifiedName(coercionTypeString.substring(8, coercionTypeString.length() -1));
                    } else {
                        synthCall = JSRoyaleEmitterTokens.SYNTH_TYPE.getToken();
                        synthethicType = coercionTypeString;
                    }
                    coercionTypeString = synthCall
                            + ASEmitterTokens.PAREN_OPEN.getToken()
                            + ASEmitterTokens.SINGLE_QUOTE.getToken()
                            + synthethicType
                            + ASEmitterTokens.SINGLE_QUOTE.getToken()
                            + ASEmitterTokens.PAREN_CLOSE.getToken();
                }
                
                coercionEnd = ASEmitterTokens.COMMA.getToken()
                        + ASEmitterTokens.SPACE.getToken()
                        + coercionTypeString
                        + ASEmitterTokens.COMMA.getToken()
                        + ASEmitterTokens.SPACE.getToken()
                        + ASEmitterTokens.TRUE.getToken()
                        + ASEmitterTokens.PAREN_CLOSE.getToken();
                if (project instanceof RoyaleJSProject)
                    ((RoyaleJSProject)project).needLanguage = true;
                getModel().needLanguage = true;
            }
        }

		if (coercionStart != null)
		{
			write(coercionStart);
        }
        emitAssignedValue(assignedNode);
		if (coercionStart != null)
		{
			if (coercionEnd != null)
			{
				write(coercionEnd);
			}
			else
			{
				write(")");
			}
		}
    }

    public BinaryOperatorNodeBase getGeneratedTypeCheck(ExpressionNodeBase leftOperand, ExpressionNodeBase rightOperand) {
        //default to using 'instanceof' for compiler-generated type-checks, because it is native JS
        BinaryOperatorInstanceOfNode check = new BinaryOperatorInstanceOfNode(null,leftOperand, rightOperand);
        return check;
    }

}
