/*
 * 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.sejda.sambox.pdmodel.common.function.type4;

import java.util.Stack;

/**
 * Provides the arithmetic operators such as "add" and "sub".
 *
 */
class ArithmeticOperators
{

    private ArithmeticOperators()
    {
        // Private constructor.
    }

    /** Implements the "abs" operator. */
    static class Abs implements Operator
    {

        @Override
        public void execute(ExecutionContext context)
        {
            Number num = context.popNumber();
            if (num instanceof Integer)
            {
                context.getStack().push(Math.abs(num.intValue()));
            }
            else
            {
                context.getStack().push(Math.abs(num.floatValue()));
            }
        }

    }

    /** Implements the "add" operator. */
    static class Add implements Operator
    {

        @Override
        public void execute(ExecutionContext context)
        {
            Number num2 = context.popNumber();
            Number num1 = context.popNumber();
            if (num1 instanceof Integer && num2 instanceof Integer)
            {
                long sum = num1.longValue() + num2.longValue();
                if (sum < Integer.MIN_VALUE || sum > Integer.MAX_VALUE)
                {
                    context.getStack().push((float) sum);
                }
                else
                {
                    context.getStack().push((int) sum);
                }
            }
            else
            {
                float sum = num1.floatValue() + num2.floatValue();
                context.getStack().push(sum);
            }
        }

    }

    /** Implements the "atan" operator. */
    static class Atan implements Operator
    {

        @Override
        public void execute(ExecutionContext context)
        {
            float den = context.popReal();
            float num = context.popReal();
            float atan = (float) Math.atan2(num, den);
            atan = (float) Math.toDegrees(atan) % 360;
            if (atan < 0)
            {
                atan = atan + 360;
            }
            context.getStack().push(atan);
        }

    }

    /** Implements the "ceiling" operator. */
    static class Ceiling implements Operator
    {

        @Override
        public void execute(ExecutionContext context)
        {
            Number num = context.popNumber();
            if (num instanceof Integer)
            {
                context.getStack().push(num);
            }
            else
            {
                context.getStack().push((float) Math.ceil(num.doubleValue()));
            }
        }

    }

    /** Implements the "cos" operator. */
    static class Cos implements Operator
    {

        @Override
        public void execute(ExecutionContext context)
        {
            float angle = context.popReal();
            float cos = (float) Math.cos(Math.toRadians(angle));
            context.getStack().push(cos);
        }

    }

    /** Implements the "cvi" operator. */
    static class Cvi implements Operator
    {

        @Override
        public void execute(ExecutionContext context)
        {
            Number num = context.popNumber();
            context.getStack().push(num.intValue());
        }

    }

    /** Implements the "cvr" operator. */
    static class Cvr implements Operator
    {

        @Override
        public void execute(ExecutionContext context)
        {
            Number num = context.popNumber();
            context.getStack().push(num.floatValue());
        }

    }

    /** Implements the "div" operator. */
    static class Div implements Operator
    {

        @Override
        public void execute(ExecutionContext context)
        {
            Number num2 = context.popNumber();
            Number num1 = context.popNumber();
            context.getStack().push(num1.floatValue() / num2.floatValue());
        }

    }

    /** Implements the "exp" operator. */
    static class Exp implements Operator
    {

        @Override
        public void execute(ExecutionContext context)
        {
            Number exp = context.popNumber();
            Number base = context.popNumber();
            double value = Math.pow(base.doubleValue(), exp.doubleValue());
            context.getStack().push((float) value);
        }

    }

    /** Implements the "floor" operator. */
    static class Floor implements Operator
    {

        @Override
        public void execute(ExecutionContext context)
        {
            Number num = context.popNumber();
            if (num instanceof Integer)
            {
                context.getStack().push(num);
            }
            else
            {
                context.getStack().push((float) Math.floor(num.doubleValue()));
            }
        }

    }

    /** Implements the "idiv" operator. */
    static class IDiv implements Operator
    {

        @Override
        public void execute(ExecutionContext context)
        {
            int num2 = context.popInt();
            int num1 = context.popInt();
            context.getStack().push(num1 / num2);
        }

    }

    /** Implements the "ln" operator. */
    static class Ln implements Operator
    {

        @Override
        public void execute(ExecutionContext context)
        {
            Number num = context.popNumber();
            context.getStack().push((float) Math.log(num.doubleValue()));
        }

    }

    /** Implements the "log" operator. */
    static class Log implements Operator
    {

        @Override
        public void execute(ExecutionContext context)
        {
            Number num = context.popNumber();
            context.getStack().push((float) Math.log10(num.doubleValue()));
        }

    }

    /** Implements the "mod" operator. */
    static class Mod implements Operator
    {

        @Override
        public void execute(ExecutionContext context)
        {
            int int2 = context.popInt();
            int int1 = context.popInt();
            context.getStack().push(int1 % int2);
        }

    }

    /** Implements the "mul" operator. */
    static class Mul implements Operator
    {

        @Override
        public void execute(ExecutionContext context)
        {
            Number num2 = context.popNumber();
            Number num1 = context.popNumber();
            if (num1 instanceof Integer && num2 instanceof Integer)
            {
                long result = num1.longValue() * num2.longValue();
                if (result >= Integer.MIN_VALUE && result <= Integer.MAX_VALUE)
                {
                    context.getStack().push((int) result);
                }
                else
                {
                    context.getStack().push((float) result);
                }
            }
            else
            {
                double result = num1.doubleValue() * num2.doubleValue();
                context.getStack().push((float) result);
            }
        }

    }

    /** Implements the "neg" operator. */
    static class Neg implements Operator
    {

        @Override
        public void execute(ExecutionContext context)
        {
            Number num = context.popNumber();
            if (num instanceof Integer)
            {
                int v = num.intValue();
                if (v == Integer.MIN_VALUE)
                {
                    context.getStack().push(-num.floatValue());
                }
                else
                {
                    context.getStack().push(-num.intValue());
                }
            }
            else
            {
                context.getStack().push(-num.floatValue());
            }
        }

    }

    /** Implements the "round" operator. */
    static class Round implements Operator
    {

        @Override
        public void execute(ExecutionContext context)
        {
            Number num = context.popNumber();
            if (num instanceof Integer)
            {
                context.getStack().push(num.intValue());
            }
            else
            {
                context.getStack().push((float) Math.round(num.doubleValue()));
            }
        }

    }

    /** Implements the "sin" operator. */
    static class Sin implements Operator
    {

        @Override
        public void execute(ExecutionContext context)
        {
            float angle = context.popReal();
            float sin = (float) Math.sin(Math.toRadians(angle));
            context.getStack().push(sin);
        }

    }

    /** Implements the "sqrt" operator. */
    static class Sqrt implements Operator
    {

        @Override
        public void execute(ExecutionContext context)
        {
            float num = context.popReal();
            if (num < 0)
            {
                throw new IllegalArgumentException("argument must be nonnegative");
            }
            context.getStack().push((float) Math.sqrt(num));
        }

    }

    /** Implements the "sub" operator. */
    static class Sub implements Operator
    {

        @Override
        public void execute(ExecutionContext context)
        {
            Stack<Object> stack = context.getStack();
            Number num2 = context.popNumber();
            Number num1 = context.popNumber();
            if (num1 instanceof Integer && num2 instanceof Integer)
            {
                long result = num1.longValue() - num2.longValue();
                if (result < Integer.MIN_VALUE || result > Integer.MAX_VALUE)
                {
                    stack.push((float) result);
                }
                else
                {
                    stack.push((int) result);
                }
            }
            else
            {
                float result = num1.floatValue() - num2.floatValue();
                stack.push(result);
            }
        }

    }

    /** Implements the "truncate" operator. */
    static class Truncate implements Operator
    {

        @Override
        public void execute(ExecutionContext context)
        {
            Number num = context.popNumber();
            if (num instanceof Integer)
            {
                context.getStack().push(num.intValue());
            }
            else
            {
                context.getStack().push((float) (int) (num.floatValue()));
            }
        }

    }

}
