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

import java.util.List;

import org.apache.royale.compiler.codegen.as.IASEmitter;
import org.apache.royale.compiler.definitions.IPackageDefinition;
import org.apache.royale.compiler.internal.semantics.SemanticUtils;
import org.apache.royale.compiler.internal.tree.as.LabeledStatementNode;
import org.apache.royale.compiler.problems.ICompilerProblem;
import org.apache.royale.compiler.problems.InternalCompilerProblem2;
import org.apache.royale.compiler.projects.IASProject;
import org.apache.royale.compiler.tree.ASTNodeID;
import org.apache.royale.compiler.tree.as.IASNode;
import org.apache.royale.compiler.tree.as.IBinaryOperatorNode;
import org.apache.royale.compiler.tree.as.IBlockNode;
import org.apache.royale.compiler.tree.as.ICatchNode;
import org.apache.royale.compiler.tree.as.IClassNode;
import org.apache.royale.compiler.tree.as.IContainerNode;
import org.apache.royale.compiler.tree.as.IDefaultXMLNamespaceNode;
import org.apache.royale.compiler.tree.as.IDynamicAccessNode;
import org.apache.royale.compiler.tree.as.IEmbedNode;
import org.apache.royale.compiler.tree.as.IExpressionNode;
import org.apache.royale.compiler.tree.as.IFileNode;
import org.apache.royale.compiler.tree.as.IForLoopNode;
import org.apache.royale.compiler.tree.as.IForLoopNode.ForLoopKind;
import org.apache.royale.compiler.tree.as.IFunctionCallNode;
import org.apache.royale.compiler.tree.as.IFunctionNode;
import org.apache.royale.compiler.tree.as.IFunctionObjectNode;
import org.apache.royale.compiler.tree.as.IGetterNode;
import org.apache.royale.compiler.tree.as.IIdentifierNode;
import org.apache.royale.compiler.tree.as.IIfNode;
import org.apache.royale.compiler.tree.as.IImportNode;
import org.apache.royale.compiler.tree.as.IInterfaceNode;
import org.apache.royale.compiler.tree.as.IIterationFlowNode;
import org.apache.royale.compiler.tree.as.IKeywordNode;
import org.apache.royale.compiler.tree.as.ILanguageIdentifierNode;
import org.apache.royale.compiler.tree.as.ILiteralContainerNode;
import org.apache.royale.compiler.tree.as.ILiteralNode;
import org.apache.royale.compiler.tree.as.ILiteralNode.LiteralType;
import org.apache.royale.compiler.tree.as.IMemberAccessExpressionNode;
import org.apache.royale.compiler.tree.as.INamespaceAccessExpressionNode;
import org.apache.royale.compiler.tree.as.INamespaceNode;
import org.apache.royale.compiler.tree.as.INumericLiteralNode;
import org.apache.royale.compiler.tree.as.IObjectLiteralValuePairNode;
import org.apache.royale.compiler.tree.as.IPackageNode;
import org.apache.royale.compiler.tree.as.IParameterNode;
import org.apache.royale.compiler.tree.as.IReturnNode;
import org.apache.royale.compiler.tree.as.ISetterNode;
import org.apache.royale.compiler.tree.as.ISwitchNode;
import org.apache.royale.compiler.tree.as.ITerminalNode;
import org.apache.royale.compiler.tree.as.ITernaryOperatorNode;
import org.apache.royale.compiler.tree.as.IThrowNode;
import org.apache.royale.compiler.tree.as.ITryNode;
import org.apache.royale.compiler.tree.as.ITypedExpressionNode;
import org.apache.royale.compiler.tree.as.IUnaryOperatorNode;
import org.apache.royale.compiler.tree.as.IUseNamespaceNode;
import org.apache.royale.compiler.tree.as.IVariableExpressionNode;
import org.apache.royale.compiler.tree.as.IVariableNode;
import org.apache.royale.compiler.tree.as.IWhileLoopNode;
import org.apache.royale.compiler.tree.as.IWhileLoopNode.WhileLoopKind;
import org.apache.royale.compiler.tree.as.IWithNode;
import org.apache.royale.compiler.tree.metadata.IMetaTagNode;
import org.apache.royale.compiler.tree.metadata.IMetaTagsNode;
import org.apache.royale.compiler.units.ICompilationUnit;
import org.apache.royale.compiler.utils.DefinitionUtils;
import org.apache.royale.compiler.visitor.IASNodeStrategy;
import org.apache.royale.compiler.visitor.as.IASBlockVisitor;
import org.apache.royale.compiler.visitor.as.IASBlockWalker;

/**
 * A base implementation of the {@link IASBlockVisitor} that will walk the
 * {@link ICompilationUnit}s AST {@link IASNode} hierarchy.
 * 
 * @author Michael Schmalle
 */
public class ASBlockWalker implements IASBlockVisitor, IASBlockWalker
{
    boolean isDebug;

    private IASEmitter emitter;

    @Override
    public IASEmitter getEmitter()
    {
        return emitter;
    }

    private final List<ICompilerProblem> errors;

    public List<ICompilerProblem> getErrors()
    {
        return errors;
    }

    //----------------------------------
    // strategy
    //----------------------------------

    private IASNodeStrategy strategy;

    public IASNodeStrategy getStrategy()
    {
        return strategy;
    }

    public void setStrategy(IASNodeStrategy value)
    {
        strategy = value;
    }

    //----------------------------------
    // project
    //----------------------------------

    private IASProject project;

    @Override
    public IASProject getProject()
    {
        return project;
    }

    public ASBlockWalker(List<ICompilerProblem> errors, IASProject project,
            IASEmitter emitter)
    {
        this.errors = errors;
        this.project = project;
        this.emitter = emitter;
        emitter.setWalker(this);
    }

    //--------------------------------------------------------------------------
    // File level
    //--------------------------------------------------------------------------

    @Override
    public void walk(IASNode node)
    {
    	try {
    		getStrategy().handle(node);
    	}
    	catch (Exception e)
    	{
    		String sp = String.format("%s line %d column %d", node.getSourcePath(), node.getLine() + 1, node.getColumn());
    		if (node.getSourcePath() == null)
    		{
    			IASNode parent = node.getParent();
    			sp = String.format("%s line %d column %d", parent.getSourcePath(), parent.getLine() + 1, parent.getColumn());
    		}
    		InternalCompilerProblem2 problem = new InternalCompilerProblem2(sp, e, "ASBlockWalker");
    		errors.add(problem);
    	}
    }

    @Override
    public void visitCompilationUnit(ICompilationUnit unit)
    {
        debug("visitCompilationUnit()");
        IFileNode node = null;
        try
        {
            node = (IFileNode) unit.getSyntaxTreeRequest().get().getAST();
        }
        catch (InterruptedException e)
        {
            throw new RuntimeException(e);
        }

        walk(node);
    }

    @Override
    public void visitFile(IFileNode node)
    {
        debug("visitFile()");
        
        boolean foundPackage = false;
        int nodeCount = node.getChildCount();
        for (int i = 0; i < nodeCount; i++)
        {
	        IASNode pnode = node.getChild(i);
	        
	        // ToDo (erikdebruin): handle other types of root node, such as when
	        //                     there is no wrapping Package or Class, like
	        //                     in mx.core.Version
	        if (pnode != null)
            { 
                boolean isPackage = pnode instanceof IPackageNode;
                boolean isAllowedAfterPackage = false;
                if(isPackage)
                {
                    foundPackage = true;
                }
                else if(foundPackage)
                {
                    isAllowedAfterPackage = pnode instanceof IInterfaceNode
                        || pnode instanceof IClassNode
                        || pnode instanceof IFunctionNode
                        || pnode instanceof INamespaceNode
                        || pnode instanceof IVariableNode;
                }
                if(isPackage || isAllowedAfterPackage)
                {
                    walk(pnode);
                    
                    if (i < nodeCount - 1)
                    {
                        emitter.writeNewline();
                        emitter.writeNewline();
                        emitter.writeNewline();
                    }
                }
            }
        }
    }

    @Override
    public void visitPackage(IPackageNode node)
    {
        debug("visitPackage()");
        IPackageDefinition definition = (IPackageDefinition) node
                .getDefinition();
        emitter.emitPackageHeader(definition);
        emitter.emitPackageHeaderContents(definition);
        emitter.emitPackageContents(definition);
        emitter.emitPackageFooter(definition);
    }

    //--------------------------------------------------------------------------
    // Type level
    //--------------------------------------------------------------------------

    @Override
    public void visitClass(IClassNode node)
    {
        debug("visitClass()");
        emitter.emitClass(node);
    }

    @Override
    public void visitInterface(IInterfaceNode node)
    {
        debug("visitInterface()");
        emitter.emitInterface(node);
    }

    @Override
    public void visitVariable(IVariableNode node)
    {
        debug("visitVariable()");
        if (SemanticUtils.isPackageDefinition(node.getDefinition()) ||
            SemanticUtils.isMemberDefinition(node.getDefinition()) ||
            node.getParent() instanceof IFileNode)
        {
            emitter.emitField(node);
        }
        else
        {
            emitter.emitVarDeclaration(node);
        }
    }

    @Override
    public void visitFunction(IFunctionNode node)
    {
        debug("visitFunction()");
        if (SemanticUtils.isPackageDefinition(node.getDefinition()) ||
            DefinitionUtils.isMemberDefinition(node.getDefinition()) ||
            node.getParent() instanceof IFileNode)
        {
            emitter.emitMethod(node);
        }
        else
        {
        	emitter.emitLocalNamedFunction(node);
        }
    }

    @Override
    public void visitParameter(IParameterNode node)
    {
        debug("visitParameter()");
        emitter.emitParameter(node);
    }

    @Override
    public void visitGetter(IGetterNode node)
    {
        debug("visitGetter()");
        emitter.emitGetAccessor(node);
    }

    @Override
    public void visitSetter(ISetterNode node)
    {
        debug("visitSetter()");
        emitter.emitSetAccessor(node);
    }

    @Override
    public void visitNamespace(INamespaceNode node)
    {
        debug("visitNamespace()");
        emitter.emitNamespace(node);
    }

    @Override
    public void visitFunctionCall(IFunctionCallNode node)
    {
        debug("visitFunctionCall()");
        emitter.emitFunctionCall(node);
    }

    @Override
    public void visitBlock(IBlockNode node)
    {
        debug("visitBlock()");
        ASTNodeID pnodeId = node.getParent().getNodeID();
        // (erikdebruin) 'goog' also needs access to the block header for
        //               accessor function blocks
        if (pnodeId == ASTNodeID.FunctionID || pnodeId == ASTNodeID.GetterID
                || pnodeId == ASTNodeID.SetterID)
        {
            emitter.emitFunctionBlockHeader((IFunctionNode) node.getParent());
        }

        final int len = node.getChildCount();
        for (int i = 0; i < len; i++)
        {
            emitter.emitStatement(node.getChild(i));
        }
    }

    @Override
    public void visitIf(IIfNode node)
    {
        debug("visitIf()");
        emitter.emitIf(node);
    }

    @Override
    public void visitForLoop(IForLoopNode node)
    {
        debug("visitForLoop(" + node.getKind() + ")");
        if (node.getKind() == ForLoopKind.FOR)
            visitFor(node);
        else if (node.getKind() == ForLoopKind.FOR_EACH)
            visitForEach(node);
    }

    protected void visitForEach(IForLoopNode node)
    {
        debug("visitForEach()");
        emitter.emitForEachLoop(node);
    }

    protected void visitFor(IForLoopNode node)
    {
        debug("visitFor()");
        emitter.emitForLoop(node);
    }

    @Override
    public void visitSwitch(ISwitchNode node)
    {
        debug("visitSwitch()");
        emitter.emitSwitch(node);
    }

    @Override
    public void visitWhileLoop(IWhileLoopNode node)
    {
        debug("visitWhileLoopNode()");
        if (node.getKind() == WhileLoopKind.WHILE)
        {
            emitter.emitWhileLoop(node);
        }
        else if (node.getKind() == WhileLoopKind.DO)
        {
            emitter.emitDoLoop(node);
        }
    }

    @Override
    public void visitWith(IWithNode node)
    {
        debug("visitWith()");
        emitter.emitWith(node);
    }

    @Override
    public void visitThrow(IThrowNode node)
    {
        debug("visitThrow()");
        emitter.emitThrow(node);
    }

    @Override
    public void visitTry(ITryNode node)
    {
        debug("visitTry()");
        emitter.emitTry(node);
    }

    @Override
    public void visitCatch(ICatchNode node)
    {
        debug("visitCatch()");
        emitter.emitCatch(node);
    }

    @Override
    public void visitIterationFlow(IIterationFlowNode node)
    {
        debug("visitIterationFlow()");
        emitter.emitIterationFlow(node);
    }

    @Override
    public void visitIdentifier(IIdentifierNode node)
    {
        debug("visitIdentifier(" + node.getName() + ")");
        emitter.emitIdentifier(node);
    }

    @Override
    public void visitNumericLiteral(INumericLiteralNode node)
    {
        debug("visitNumericLiteral(" + node.getNumericValue() + ")");
        emitter.emitNumericLiteral(node);
    }

    @Override
    public void visitDefaultXMLNamespace(IDefaultXMLNamespaceNode node)
    {
        debug("visitDefaultXMLNamespace()");
        walk(node.getKeywordNode()); // default xml namespace
        walk(node.getExpressionNode()); // "http://ns.whatever.com"
    }

    @Override
    public void visitKeyword(IKeywordNode node)
    {
        debug("visitKeyword(" + node.getNodeID().getParaphrase() + ")");
        emitter.emitKeyword(node);
    }

    @Override
    public void visitLiteral(ILiteralNode node)
    {
        debug("visitLiteral(" + node.getValue() + ")");
        if (node.getLiteralType() == LiteralType.NUMBER
                || node.getLiteralType() == LiteralType.BOOLEAN
                || node.getLiteralType() == LiteralType.NULL
                || node.getLiteralType() == LiteralType.NUMBER
                || node.getLiteralType() == LiteralType.REGEXP
                || node.getLiteralType() == LiteralType.STRING
                || node.getLiteralType() == LiteralType.XML
                || node.getLiteralType() == LiteralType.VOID)
        {
            emitter.emitLiteral(node);
        }
        else if (node.getLiteralType() == LiteralType.ARRAY
                || node.getLiteralType() == LiteralType.OBJECT
                || node.getLiteralType() == LiteralType.VECTOR
                || node.getLiteralType() == LiteralType.XMLLIST)
        {
            emitter.emitLiteralContainer((ILiteralContainerNode) node);
        }
    }

    @Override
    public void visitMemberAccessExpression(IMemberAccessExpressionNode node)
    {
        debug("visitMemberAccessExpression()");
        emitter.emitMemberAccessExpression(node);
    }

    @Override
    public void visitNamespaceAccessExpression(
            INamespaceAccessExpressionNode node)
    {
        debug("visitNamespaceAccessExpression()");
        emitter.emitNamespaceAccessExpression(node);
    }

    @Override
    public void visitDynamicAccess(IDynamicAccessNode node)
    {
        debug("visitDynamicAccess()");
        emitter.emitDynamicAccess(node);
    }

    @Override
    public void visitTypedExpression(ITypedExpressionNode node)
    {
        debug("visitITypedExpression()");
        emitter.emitTypedExpression(node);
    }

    @Override
    public void visitAsOperator(IBinaryOperatorNode node)
    {
        debug("visitAsOperator()");
        emitter.emitAsOperator(node);
    }

    @Override
    public void visitIsOperator(IBinaryOperatorNode node)
    {
        debug("visitIsOperator()");
        emitter.emitIsOperator(node);
    }

    @Override
    public void visitBinaryOperator(IBinaryOperatorNode node)
    {
        debug("visitBinaryOperator(" + node.getOperator().getOperatorText()
                + ")");
        emitter.emitBinaryOperator(node);
    }

    @Override
    public void visitUnaryOperator(IUnaryOperatorNode node)
    {
        debug("visitUnaryOperator()");
        emitter.emitUnaryOperator(node);
    }

    @Override
    public void visitTerminal(ITerminalNode node)
    {
        debug("visitTerminal(" + node.getKind() + ")");
        walk(node.getStatementContentsNode());
    }

    @Override
    public void visitFunctionObject(IFunctionObjectNode node)
    {
        emitter.emitFunctionObject((IFunctionObjectNode) node);
    }

    @Override
    public void visitVariableExpression(IVariableExpressionNode node)
    {
        debug("visitVariableExpression()");
        emitter.emitVariableExpression(node);
    }

    @Override
    public void visitExpression(IExpressionNode node)
    {
        debug("visitExpression()");
        // XXX (mschmalle) anything getting past here?
    }

    @Override
    public void visitImport(IImportNode node)
    {
        debug("visitImport()");
        emitter.emitImport(node);
    }

    @Override
    public void visitMetaTags(IMetaTagsNode node)
    {
        debug("visitMetaTags()");
        IMetaTagNode[] tags = node.getAllTags();
        for (IMetaTagNode tag : tags)
        {
            walk(tag);
        }
    }

    @Override
    public void visitMetaTag(IMetaTagNode node)
    {
        debug("visitMetaTag(" + node.getTagName() + ")");
        emitter.emitMetaTag(node);
    }

    @Override
    public void visitUseNamespace(IUseNamespaceNode node)
    {
        debug("visitUseNamespace(" + node.getTargetNamespace() + ")");
        emitter.emitUseNamespace(node);
    }

    @Override
    public void visitEmbed(IEmbedNode node)
    {
        debug("visitEmbed(" + node.getAttributes()[0].getValue() + ")");
        emitter.emitEmbed(node); 
    }

    @Override
    public void visitContainer(IContainerNode node)
    {
        debug("visitContainer()");
        emitter.emitContainer(node);
    }

    @Override
    public void visitE4XFilter(IMemberAccessExpressionNode node)
    {
        debug("visitE4XFilter()");
        emitter.emitE4XFilter(node);
    }
    
    @Override
    public void visitReturn(IReturnNode node)
    {
        debug("visitReturn()");
        emitter.emitReturn(node);
    }

    @Override
    public void visitTernaryOperator(ITernaryOperatorNode node)
    {
        debug("visitTernaryOperator()");
        emitter.emitTernaryOperator(node);
    }

    @Override
    public void visitLabeledStatement(LabeledStatementNode node)
    {
        debug("visitLabeledStatement()");
        emitter.emitLabelStatement(node);
    }

    @Override
    public void visitObjectLiteralValuePair(IObjectLiteralValuePairNode node)
    {
        debug("visitIObjectLiteralValuePair()");
        emitter.emitObjectLiteralValuePair(node);
    }

    @Override
    public void visitLanguageIdentifierNode(ILanguageIdentifierNode node)
    {
        emitter.emitLanguageIdentifier(node);
    }

    //--------------------------------------------------------------------------
    // 
    //--------------------------------------------------------------------------

    protected void debug(String message)
    {
        if (isDebug)
        {
            System.out.println(message);
        }
    }
}
