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

import static org.apache.royale.compiler.internal.parsing.as.ASTokenTypes.*;

import antlr.Token;

import org.apache.royale.compiler.constants.IASLanguageConstants.BuiltinType;
import org.apache.royale.compiler.definitions.ITypeDefinition;
import org.apache.royale.compiler.parsing.IASToken;
import org.apache.royale.compiler.projects.ICompilerProject;
import org.apache.royale.compiler.tree.as.IASNode;
import org.apache.royale.compiler.tree.as.IDynamicAccessNode;
import org.apache.royale.compiler.tree.as.IBinaryOperatorNode;
import org.apache.royale.compiler.tree.as.IExpressionNode;

/**
 * Abstract base class for all binary operator nodes.
 */
public abstract class BinaryOperatorNodeBase extends OperatorNodeBase implements IBinaryOperatorNode
{
    /**
     * Factory method for various kinds of binary operator nodes.
     */
    public static BinaryOperatorNodeBase create(IASToken operatorToken, ExpressionNodeBase leftOperand, ExpressionNodeBase rightOperand)
    {
        switch (operatorToken.getType())
        {
            // simple assignment
            
            case TOKEN_OPERATOR_ASSIGNMENT:
                return new BinaryOperatorAssignmentNode(operatorToken, leftOperand, rightOperand);
            
            // arithmetic
            
            case TOKEN_OPERATOR_PLUS:
                return new BinaryOperatorPlusNode(operatorToken, leftOperand, rightOperand);
                
            case TOKEN_OPERATOR_PLUS_ASSIGNMENT:
                return new BinaryOperatorPlusAssignmentNode(operatorToken, leftOperand, rightOperand);
                
            case TOKEN_OPERATOR_MINUS:
                return new BinaryOperatorMinusNode(operatorToken, leftOperand, rightOperand);
                
            case TOKEN_OPERATOR_MINUS_ASSIGNMENT:
                return new BinaryOperatorMinusAssignmentNode(operatorToken, leftOperand, rightOperand);
                
            case TOKEN_OPERATOR_STAR:
                return new BinaryOperatorMultiplicationNode(operatorToken, leftOperand, rightOperand);
                
            case TOKEN_OPERATOR_MULTIPLICATION_ASSIGNMENT:
                return new BinaryOperatorMultiplicationAssignmentNode(operatorToken, leftOperand, rightOperand);
                
            case TOKEN_OPERATOR_DIVISION:
                return new BinaryOperatorDivisionNode(operatorToken, leftOperand, rightOperand);
            
            case TOKEN_OPERATOR_DIVISION_ASSIGNMENT:
                return new BinaryOperatorDivisionAssignmentNode(operatorToken, leftOperand, rightOperand);
                
            case TOKEN_OPERATOR_MODULO:
                return new BinaryOperatorModuloNode(operatorToken, leftOperand, rightOperand);
                
            case TOKEN_OPERATOR_MODULO_ASSIGNMENT:
                return new BinaryOperatorModuloAssignmentNode(operatorToken, leftOperand, rightOperand);
                
            // equality
                
            case TOKEN_OPERATOR_EQUAL:
                return new BinaryOperatorEqualNode(operatorToken, leftOperand, rightOperand);
                
            case TOKEN_OPERATOR_NOT_EQUAL:
                return new BinaryOperatorNotEqualNode(operatorToken, leftOperand, rightOperand);
                    
            case TOKEN_OPERATOR_STRICT_EQUAL:
                return new BinaryOperatorStrictEqualNode(operatorToken, leftOperand, rightOperand);
                
            case TOKEN_OPERATOR_STRICT_NOT_EQUAL:
                return new BinaryOperatorStrictNotEqualNode(operatorToken, leftOperand, rightOperand);
                
            // comparison
            
            case TOKEN_OPERATOR_LESS_THAN:
                return new BinaryOperatorLessThanNode(operatorToken, leftOperand, rightOperand);
                
            case TOKEN_OPERATOR_LESS_THAN_EQUALS:
                return new BinaryOperatorLessThanEqualsNode(operatorToken, leftOperand, rightOperand);
                    
            case TOKEN_OPERATOR_GREATER_THAN:
                return new BinaryOperatorGreaterThanNode(operatorToken, leftOperand, rightOperand);
                
            case TOKEN_OPERATOR_GREATER_THAN_EQUALS:
                return new BinaryOperatorGreaterThanEqualsNode(operatorToken, leftOperand, rightOperand);
                
            // logical
                
            case TOKEN_OPERATOR_LOGICAL_AND:
                return new BinaryOperatorLogicalAndNode(operatorToken, leftOperand, rightOperand);
                
            case TOKEN_OPERATOR_LOGICAL_AND_ASSIGNMENT:
                return new BinaryOperatorLogicalAndAssignmentNode(operatorToken, leftOperand, rightOperand);
                
            case TOKEN_OPERATOR_LOGICAL_OR:
                return new BinaryOperatorLogicalOrNode(operatorToken, leftOperand, rightOperand);
                
            case TOKEN_OPERATOR_LOGICAL_OR_ASSIGNMENT:
                return new BinaryOperatorLogicalOrAssignmentNode(operatorToken, leftOperand, rightOperand);
                
            // bitwise logical
                
            case TOKEN_OPERATOR_BITWISE_AND:
                return new BinaryOperatorBitwiseAndNode(operatorToken, leftOperand, rightOperand);
                
            case TOKEN_OPERATOR_BITWISE_AND_ASSIGNMENT:
                return new BinaryOperatorBitwiseAndAssignmentNode(operatorToken, leftOperand, rightOperand);
                
            case TOKEN_OPERATOR_BITWISE_OR:
                return new BinaryOperatorBitwiseOrNode(operatorToken, leftOperand, rightOperand);
                
            case TOKEN_OPERATOR_BITWISE_OR_ASSIGNMENT:
                return new BinaryOperatorBitwiseOrAssignmentNode(operatorToken, leftOperand, rightOperand);
                
            case TOKEN_OPERATOR_BITWISE_XOR:
                return new BinaryOperatorBitwiseXorNode(operatorToken, leftOperand, rightOperand);
                
            case TOKEN_OPERATOR_BITWISE_XOR_ASSIGNMENT:
                return new BinaryOperatorBitwiseXorAssignmentNode(operatorToken, leftOperand, rightOperand);
                
            // shift
                
            case TOKEN_OPERATOR_BITWISE_LEFT_SHIFT:
                return new BinaryOperatorBitwiseLeftShiftNode(operatorToken, leftOperand, rightOperand);
                
            case TOKEN_OPERATOR_BITWISE_LEFT_SHIFT_ASSIGNMENT:
                return new BinaryOperatorBitwiseLeftShiftAssignmentNode(operatorToken, leftOperand, rightOperand);
                
            case TOKEN_OPERATOR_BITWISE_RIGHT_SHIFT:
                return new BinaryOperatorBitwiseRightShiftNode(operatorToken, leftOperand, rightOperand);
                
            case TOKEN_OPERATOR_BITWISE_RIGHT_SHIFT_ASSIGNMENT:
                return new BinaryOperatorBitwiseRightShiftAssignmentNode(operatorToken, leftOperand, rightOperand);
                
            case TOKEN_OPERATOR_BITWISE_UNSIGNED_RIGHT_SHIFT:
                return new BinaryOperatorBitwiseUnsignedRightShiftNode(operatorToken, leftOperand, rightOperand);
                
            case TOKEN_OPERATOR_BITWISE_UNSIGNED_RIGHT_SHIFT_ASSIGNMENT:
                return new BinaryOperatorBitwiseUnsignedRightShiftAssignmentNode(operatorToken, leftOperand, rightOperand);
                
            // other
            
            case TOKEN_KEYWORD_AS:
                return new BinaryOperatorAsNode(operatorToken, leftOperand, rightOperand);
                
            case TOKEN_COMMA:
                return new BinaryOperatorCommaNode(operatorToken, leftOperand, rightOperand);
            
            case TOKEN_KEYWORD_IN:
                return new BinaryOperatorInNode(operatorToken, leftOperand, rightOperand);
            
            case TOKEN_KEYWORD_INSTANCEOF:
                return new BinaryOperatorInstanceOfNode(operatorToken, leftOperand, rightOperand);
            
            case TOKEN_KEYWORD_IS:
                return new BinaryOperatorIsNode(operatorToken, leftOperand, rightOperand);
        }
        
        assert false : "Token '" + operatorToken.getText() + "' unexpected in BinaryOperatorNodeBase.create()";
        return null;
    }
    
    /**
     * Constructor.
     * <p>
     * Creates a {@code BinaryOperatorNode} from an operator and both operands.
     * If either operand is {@code null}, we will synthesize
     * empty {@link IdentifierNode} to repair the tree.
     * 
     * @param op operator token
     * @param left first operand
     * @param right second operand
     */
    public BinaryOperatorNodeBase(IASToken op, ExpressionNodeBase left, ExpressionNodeBase right)
    {
        super(op);

        // Synthesize an empty ID node if either operand is null. (Fix for CMP-883)

        if (left == null)
            leftOperandNode = IdentifierNode.createEmptyIdentifierNodeAfterToken((Token)op);
        else
            leftOperandNode = left;

        if (right == null)
            rightOperandNode = IdentifierNode.createEmptyIdentifierNodeAfterToken((Token)op);
        else
            rightOperandNode = right;
        
        span(leftOperandNode, rightOperandNode);
    }
    
    /**
     * Copy constructor.
     * 
     * @param other The node to copy.
     */
    protected BinaryOperatorNodeBase(BinaryOperatorNodeBase other)
    {
        super(other);
        
        this.leftOperandNode = other.leftOperandNode != null ? other.leftOperandNode.copy() : null;
        this.rightOperandNode = other.rightOperandNode != null ? other.rightOperandNode.copy() : null;
    }

    /**
     * The expression to the left of the operator
     */
    protected ExpressionNodeBase leftOperandNode;

    /**
     * The expression to the right of the operator
     */
    protected ExpressionNodeBase rightOperandNode;
    
    //
    // NodeBase overrides
    //
    
    @Override
    public int getChildCount()
    {
        int count = 0;
        
        if (leftOperandNode != null)
            count++;
        
        if (rightOperandNode != null)
            count++;
        
        return count;
    }

    @Override
    public IASNode getChild(int i)
    {
        if (i == 0)
            return leftOperandNode != null ? leftOperandNode : rightOperandNode;
        
        else if (i == 1)
            return rightOperandNode;
        
        return null;
    }
    
    @Override
    protected void setChildren(boolean fillInOffsets)
    {
        if (leftOperandNode != null)
            leftOperandNode.setParent(this);

        if (rightOperandNode != null)
            rightOperandNode.setParent(this);
    }

    @Override
    protected void fillInOffsets()
    {
        if (rightOperandNode == null && leftOperandNode == null && operatorStart != -1)
            span(operatorStart, operatorStart + 1, -1, -1);
        else
            super.fillInOffsets();
    }
    
    //
    // ExpressionNodeBase overrides
    //
    
    @Override
    public ITypeDefinition resolveType(ICompilerProject project)
    {
        // Various subclasses override this default behavior.
        return project.getBuiltinType(BuiltinType.ANY_TYPE);
    }
    
    //
    // OperatorNodeBase overrides
    //
    
    @Override
    public ExpressionType getExpressionType()
    {
        return ExpressionType.BINARY;
    }
    
    //
    // IBinaryOperatorNode implementations
    //
    
    @Override
    public IExpressionNode getLeftOperandNode()
    {
        return leftOperandNode;
    }

    @Override
    public IExpressionNode getRightOperandNode()
    {
        return rightOperandNode;
    }

    //
    // Other methods
    //

    /**
     * Sets the left-hand side of the expression.
     * 
     * @param leftOperandNode Left-hand side expression.
     */
    public void setLeftOperandNode(ExpressionNodeBase leftOperandNode)
    {
        this.leftOperandNode = leftOperandNode;
    }

    /**
     * Sets the right side of the expression
     * 
     * @param rightOperandNode ExpressionNodeBase to the right of the operator
     */
    public void setRightOperandNode(ExpressionNodeBase rightOperandNode)
    {
        this.rightOperandNode = rightOperandNode;
    }
    
    public boolean isOperatingOnArray()
    {
        if (leftOperandNode instanceof IDynamicAccessNode || rightOperandNode instanceof IDynamicAccessNode)
            return true;
        
        if (leftOperandNode instanceof IBinaryOperatorNode)
        {
            boolean onArray = ((BinaryOperatorNodeBase)leftOperandNode).isOperatingOnArray();
            if (onArray)
                return true;
        }
        
        if (rightOperandNode instanceof IBinaryOperatorNode)
            return ((BinaryOperatorNodeBase)rightOperandNode).isOperatingOnArray();

        return false;
    }

    /**
     * Utility method called by {@link #resolveType(ICompilerProject)}
     * in subclasses for assignment operators.
     */
    protected ITypeDefinition resolveAssignmentType(ICompilerProject project)
    {
        // The type of any assignment expression is the type
        // of what we are assigning to.
        return getLeftOperandNode().resolveType(project);
    }

    /**
     * Utility method called by {@link #resolveType(ICompilerProject)}
     * in subclasses for the <code>&&</code> and <code>||</code> operators.
     */
    protected ITypeDefinition resolveLogicalType(ICompilerProject project)
    {
        // The old compiler says the type of && or || is one of the operand types,
        // but only checks if the types are equivalent.
        // TODO: Could probably be smarter - calculate common base class and use that as the type?
        ITypeDefinition leftType = getLeftOperandNode().resolveType(project);
        ITypeDefinition rightType = getRightOperandNode().resolveType(project);
        if (leftType != null && leftType.equals(rightType))
            return leftType;
        
        return project.getBuiltinType(BuiltinType.ANY_TYPE);
    }
}
