// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the Apache 2.0 License.
// See the LICENSE file in the project root for more information.

using System;
using System.Numerics;

using Microsoft.Scripting.Runtime;

using IronPython.Runtime.Operations;

namespace IronPython.Compiler.Ast {
    public partial class BinaryExpression {
        const int MaximumInlineStringLength = 1024 * 1024;

        internal override ConstantExpression ConstantFold() {
            Expression left = Left.ConstantFold();
            Expression right = Right.ConstantFold();

            var constLeft = left as ConstantExpression;
            var constRight = right as ConstantExpression;
            try {
                if (constLeft != null && constRight != null &&
                    constLeft.Value != null && constRight.Value != null &&
                    constLeft.Value.GetType() == constRight.Value.GetType()) {
                    #region Generated Python Constant Folding

                    // *** BEGIN GENERATED CODE ***
                    // generated by function: gen_constant_folding from: generate_ops.py

                    if (constLeft.Value.GetType() == typeof(Int32)) {
                        switch (Operator) {
                            case PythonOperator.Add: return new ConstantExpression(Int32Ops.Add((Int32)constLeft.Value, (Int32)constRight.Value));
                            case PythonOperator.Subtract: return new ConstantExpression(Int32Ops.Subtract((Int32)constLeft.Value, (Int32)constRight.Value));
                            case PythonOperator.Power: return new ConstantExpression(Int32Ops.Power((Int32)constLeft.Value, (Int32)constRight.Value));
                            case PythonOperator.Multiply: return new ConstantExpression(Int32Ops.Multiply((Int32)constLeft.Value, (Int32)constRight.Value));
                            case PythonOperator.FloorDivide: return new ConstantExpression(Int32Ops.FloorDivide((Int32)constLeft.Value, (Int32)constRight.Value));
                            case PythonOperator.TrueDivide: return new ConstantExpression(Int32Ops.TrueDivide((Int32)constLeft.Value, (Int32)constRight.Value));
                            case PythonOperator.Mod: return new ConstantExpression(Int32Ops.Mod((Int32)constLeft.Value, (Int32)constRight.Value));
                            case PythonOperator.LeftShift: return new ConstantExpression(Int32Ops.LeftShift((Int32)constLeft.Value, (Int32)constRight.Value));
                            case PythonOperator.RightShift: return new ConstantExpression(Int32Ops.RightShift((Int32)constLeft.Value, (Int32)constRight.Value));
                            case PythonOperator.BitwiseAnd: return new ConstantExpression(Int32Ops.BitwiseAnd((Int32)constLeft.Value, (Int32)constRight.Value));
                            case PythonOperator.BitwiseOr: return new ConstantExpression(Int32Ops.BitwiseOr((Int32)constLeft.Value, (Int32)constRight.Value));
                            case PythonOperator.ExclusiveOr: return new ConstantExpression(Int32Ops.ExclusiveOr((Int32)constLeft.Value, (Int32)constRight.Value));
                            case PythonOperator.LessThan: return new ConstantExpression(ScriptingRuntimeHelpers.BooleanToObject((Int32)constLeft.Value < (Int32)constRight.Value));
                            case PythonOperator.GreaterThan: return new ConstantExpression(ScriptingRuntimeHelpers.BooleanToObject((Int32)constLeft.Value > (Int32)constRight.Value));
                            case PythonOperator.LessThanOrEqual: return new ConstantExpression(ScriptingRuntimeHelpers.BooleanToObject((Int32)constLeft.Value <= (Int32)constRight.Value));
                            case PythonOperator.GreaterThanOrEqual: return new ConstantExpression(ScriptingRuntimeHelpers.BooleanToObject((Int32)constLeft.Value >= (Int32)constRight.Value));
                            case PythonOperator.Equals: return new ConstantExpression(ScriptingRuntimeHelpers.BooleanToObject((Int32)constLeft.Value == (Int32)constRight.Value));
                            case PythonOperator.NotEquals: return new ConstantExpression(ScriptingRuntimeHelpers.BooleanToObject((Int32)constLeft.Value != (Int32)constRight.Value));
                        }
                    }
                    if (constLeft.Value.GetType() == typeof(Double)) {
                        switch (Operator) {
                            case PythonOperator.Add: return new ConstantExpression(DoubleOps.Add((Double)constLeft.Value, (Double)constRight.Value));
                            case PythonOperator.Subtract: return new ConstantExpression(DoubleOps.Subtract((Double)constLeft.Value, (Double)constRight.Value));
                            case PythonOperator.Power: return new ConstantExpression(DoubleOps.Power((Double)constLeft.Value, (Double)constRight.Value));
                            case PythonOperator.Multiply: return new ConstantExpression(DoubleOps.Multiply((Double)constLeft.Value, (Double)constRight.Value));
                            case PythonOperator.FloorDivide: return new ConstantExpression(DoubleOps.FloorDivide((Double)constLeft.Value, (Double)constRight.Value));
                            case PythonOperator.TrueDivide: return new ConstantExpression(DoubleOps.TrueDivide((Double)constLeft.Value, (Double)constRight.Value));
                            case PythonOperator.Mod: return new ConstantExpression(DoubleOps.Mod((Double)constLeft.Value, (Double)constRight.Value));
                            case PythonOperator.LessThan: return new ConstantExpression(ScriptingRuntimeHelpers.BooleanToObject((Double)constLeft.Value < (Double)constRight.Value));
                            case PythonOperator.GreaterThan: return new ConstantExpression(ScriptingRuntimeHelpers.BooleanToObject((Double)constLeft.Value > (Double)constRight.Value));
                            case PythonOperator.LessThanOrEqual: return new ConstantExpression(ScriptingRuntimeHelpers.BooleanToObject((Double)constLeft.Value <= (Double)constRight.Value));
                            case PythonOperator.GreaterThanOrEqual: return new ConstantExpression(ScriptingRuntimeHelpers.BooleanToObject((Double)constLeft.Value >= (Double)constRight.Value));
                            case PythonOperator.Equals: return new ConstantExpression(ScriptingRuntimeHelpers.BooleanToObject((Double)constLeft.Value == (Double)constRight.Value));
                            case PythonOperator.NotEquals: return new ConstantExpression(ScriptingRuntimeHelpers.BooleanToObject((Double)constLeft.Value != (Double)constRight.Value));
                        }
                    }
                    if (constLeft.Value.GetType() == typeof(BigInteger)) {
                        switch (Operator) {
                            case PythonOperator.Add: return new ConstantExpression(BigIntegerOps.Add((BigInteger)constLeft.Value, (BigInteger)constRight.Value));
                            case PythonOperator.Subtract: return new ConstantExpression(BigIntegerOps.Subtract((BigInteger)constLeft.Value, (BigInteger)constRight.Value));
                            case PythonOperator.Power: return new ConstantExpression(BigIntegerOps.Power((BigInteger)constLeft.Value, (BigInteger)constRight.Value));
                            case PythonOperator.Multiply: return new ConstantExpression(BigIntegerOps.Multiply((BigInteger)constLeft.Value, (BigInteger)constRight.Value));
                            case PythonOperator.FloorDivide: return new ConstantExpression(BigIntegerOps.FloorDivide((BigInteger)constLeft.Value, (BigInteger)constRight.Value));
                            case PythonOperator.TrueDivide: return new ConstantExpression(BigIntegerOps.TrueDivide((BigInteger)constLeft.Value, (BigInteger)constRight.Value));
                            case PythonOperator.Mod: return new ConstantExpression(BigIntegerOps.Mod((BigInteger)constLeft.Value, (BigInteger)constRight.Value));
                            case PythonOperator.LeftShift: return new ConstantExpression(BigIntegerOps.LeftShift((BigInteger)constLeft.Value, (BigInteger)constRight.Value));
                            case PythonOperator.RightShift: return new ConstantExpression(BigIntegerOps.RightShift((BigInteger)constLeft.Value, (BigInteger)constRight.Value));
                            case PythonOperator.BitwiseAnd: return new ConstantExpression(BigIntegerOps.BitwiseAnd((BigInteger)constLeft.Value, (BigInteger)constRight.Value));
                            case PythonOperator.BitwiseOr: return new ConstantExpression(BigIntegerOps.BitwiseOr((BigInteger)constLeft.Value, (BigInteger)constRight.Value));
                            case PythonOperator.ExclusiveOr: return new ConstantExpression(BigIntegerOps.ExclusiveOr((BigInteger)constLeft.Value, (BigInteger)constRight.Value));
                            case PythonOperator.LessThan: return new ConstantExpression(ScriptingRuntimeHelpers.BooleanToObject((BigInteger)constLeft.Value < (BigInteger)constRight.Value));
                            case PythonOperator.GreaterThan: return new ConstantExpression(ScriptingRuntimeHelpers.BooleanToObject((BigInteger)constLeft.Value > (BigInteger)constRight.Value));
                            case PythonOperator.LessThanOrEqual: return new ConstantExpression(ScriptingRuntimeHelpers.BooleanToObject((BigInteger)constLeft.Value <= (BigInteger)constRight.Value));
                            case PythonOperator.GreaterThanOrEqual: return new ConstantExpression(ScriptingRuntimeHelpers.BooleanToObject((BigInteger)constLeft.Value >= (BigInteger)constRight.Value));
                            case PythonOperator.Equals: return new ConstantExpression(ScriptingRuntimeHelpers.BooleanToObject((BigInteger)constLeft.Value == (BigInteger)constRight.Value));
                            case PythonOperator.NotEquals: return new ConstantExpression(ScriptingRuntimeHelpers.BooleanToObject((BigInteger)constLeft.Value != (BigInteger)constRight.Value));
                        }
                    }
                    if (constLeft.Value.GetType() == typeof(Complex)) {
                        switch (Operator) {
                            case PythonOperator.Add: return new ConstantExpression(ComplexOps.Add((Complex)constLeft.Value, (Complex)constRight.Value));
                            case PythonOperator.Subtract: return new ConstantExpression(ComplexOps.Subtract((Complex)constLeft.Value, (Complex)constRight.Value));
                            case PythonOperator.Power: return new ConstantExpression(ComplexOps.Power((Complex)constLeft.Value, (Complex)constRight.Value));
                            case PythonOperator.Multiply: return new ConstantExpression(ComplexOps.Multiply((Complex)constLeft.Value, (Complex)constRight.Value));
                            case PythonOperator.TrueDivide: return new ConstantExpression(ComplexOps.TrueDivide((Complex)constLeft.Value, (Complex)constRight.Value));
                        }
                    }

                    // *** END GENERATED CODE ***

                    #endregion

                    if (constLeft.Value.GetType() == typeof(string) && Operator == PythonOperator.Add) {
                        return new ConstantExpression((string)constLeft.Value + (string)constRight.Value);
                    }
                } else if (Operator == PythonOperator.Multiply && constLeft != null && constRight != null) {
                    // 10000 check is to avoid creating massive strings in IL - there's a limit to the number of
                    // bytes of strings we get to have so we don't want to use them all up.
                    if (constLeft.Value.GetType() == typeof(string) && constRight.Value.GetType() == typeof(int)) {
                        var res = StringOps.Multiply((string)constLeft.Value, (int)constRight.Value);
                        if (res.Length < MaximumInlineStringLength) {
                            return new ConstantExpression(res);
                        }
                    } else if (constLeft.Value.GetType() == typeof(int) && constRight.Value.GetType() == typeof(string)) {
                        var res = StringOps.Multiply((string)constRight.Value, (int)constLeft.Value);
                        if (res.Length < MaximumInlineStringLength) {
                            return new ConstantExpression(res);
                        }
                    }
                }   
            } catch (ArithmeticException) {
            }

            return null;
        }

    }
}
