// 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.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Numerics;
using System.Text;

using Microsoft.Scripting.Utils;

using IronPython.Runtime;
using IronPython.Runtime.Exceptions;
using IronPython.Runtime.Operations;
using IronPython.Runtime.Types;

[assembly: PythonModule("_opcode", typeof(IronPython.Modules.PythonOpcode))]
namespace IronPython.Modules {
    public static class PythonOpcode {

        /* Instruction opcodes for compiled code */
        private const int POP_TOP = 1;
        private const int ROT_TWO = 2;
        private const int ROT_THREE = 3;
        private const int DUP_TOP = 4;
        private const int DUP_TOP_TWO = 5;
        private const int ROT_FOUR = 6;
        private const int NOP = 9;
        private const int UNARY_POSITIVE = 10;
        private const int UNARY_NEGATIVE = 11;
        private const int UNARY_NOT = 12;
        private const int UNARY_INVERT = 15;
        private const int BINARY_MATRIX_MULTIPLY = 16;
        private const int INPLACE_MATRIX_MULTIPLY = 17;
        private const int BINARY_POWER = 19;
        private const int BINARY_MULTIPLY = 20;
        private const int BINARY_MODULO = 22;
        private const int BINARY_ADD = 23;
        private const int BINARY_SUBTRACT = 24;
        private const int BINARY_SUBSCR = 25;
        private const int BINARY_FLOOR_DIVIDE = 26;
        private const int BINARY_TRUE_DIVIDE = 27;
        private const int INPLACE_FLOOR_DIVIDE = 28;
        private const int INPLACE_TRUE_DIVIDE = 29;
        private const int GET_AITER = 50;
        private const int GET_ANEXT = 51;
        private const int BEFORE_ASYNC_WITH = 52;
        private const int BEGIN_FINALLY = 53;
        private const int END_ASYNC_FOR = 54;
        private const int INPLACE_ADD = 55;
        private const int INPLACE_SUBTRACT = 56;
        private const int INPLACE_MULTIPLY = 57;
        private const int INPLACE_MODULO = 59;
        private const int STORE_SUBSCR = 60;
        private const int DELETE_SUBSCR = 61;
        private const int BINARY_LSHIFT = 62;
        private const int BINARY_RSHIFT = 63;
        private const int BINARY_AND = 64;
        private const int BINARY_XOR = 65;
        private const int BINARY_OR = 66;
        private const int INPLACE_POWER = 67;
        private const int GET_ITER = 68;
        private const int GET_YIELD_FROM_ITER = 69;
        private const int PRINT_EXPR = 70;
        private const int LOAD_BUILD_CLASS = 71;
        private const int YIELD_FROM = 72;
        private const int GET_AWAITABLE = 73;
        private const int INPLACE_LSHIFT = 75;
        private const int INPLACE_RSHIFT = 76;
        private const int INPLACE_AND = 77;
        private const int INPLACE_XOR = 78;
        private const int INPLACE_OR = 79;
        private const int WITH_CLEANUP_START = 81;
        private const int WITH_CLEANUP_FINISH = 82;
        private const int RETURN_VALUE = 83;
        private const int IMPORT_STAR = 84;
        private const int SETUP_ANNOTATIONS = 85;
        private const int YIELD_VALUE = 86;
        private const int POP_BLOCK = 87;
        private const int END_FINALLY = 88;
        private const int POP_EXCEPT = 89;
        private const int HAVE_ARGUMENT = 90;
        private const int STORE_NAME = 90;
        private const int DELETE_NAME = 91;
        private const int UNPACK_SEQUENCE = 92;
        private const int FOR_ITER = 93;
        private const int UNPACK_EX = 94;
        private const int STORE_ATTR = 95;
        private const int DELETE_ATTR = 96;
        private const int STORE_GLOBAL = 97;
        private const int DELETE_GLOBAL = 98;
        private const int LOAD_CONST = 100;
        private const int LOAD_NAME = 101;
        private const int BUILD_TUPLE = 102;
        private const int BUILD_LIST = 103;
        private const int BUILD_SET = 104;
        private const int BUILD_MAP = 105;
        private const int LOAD_ATTR = 106;
        private const int COMPARE_OP = 107;
        private const int IMPORT_NAME = 108;
        private const int IMPORT_FROM = 109;
        private const int JUMP_FORWARD = 110;
        private const int JUMP_IF_FALSE_OR_POP = 111;
        private const int JUMP_IF_TRUE_OR_POP = 112;
        private const int JUMP_ABSOLUTE = 113;
        private const int POP_JUMP_IF_FALSE = 114;
        private const int POP_JUMP_IF_TRUE = 115;
        private const int LOAD_GLOBAL = 116;
        private const int SETUP_FINALLY = 122;
        private const int LOAD_FAST = 124;
        private const int STORE_FAST = 125;
        private const int DELETE_FAST = 126;
        private const int RAISE_VARARGS = 130;
        private const int CALL_FUNCTION = 131;
        private const int MAKE_FUNCTION = 132;
        private const int BUILD_SLICE = 133;
        private const int LOAD_CLOSURE = 135;
        private const int LOAD_DEREF = 136;
        private const int STORE_DEREF = 137;
        private const int DELETE_DEREF = 138;
        private const int CALL_FUNCTION_KW = 141;
        private const int CALL_FUNCTION_EX = 142;
        private const int SETUP_WITH = 143;
        private const int EXTENDED_ARG = 144;
        private const int LIST_APPEND = 145;
        private const int SET_ADD = 146;
        private const int MAP_ADD = 147;
        private const int LOAD_CLASSDEREF = 148;
        private const int BUILD_LIST_UNPACK = 149;
        private const int BUILD_MAP_UNPACK = 150;
        private const int BUILD_MAP_UNPACK_WITH_CALL = 151;
        private const int BUILD_TUPLE_UNPACK = 152;
        private const int BUILD_SET_UNPACK = 153;
        private const int SETUP_ASYNC_WITH = 154;
        private const int FORMAT_VALUE = 155;
        private const int BUILD_CONST_KEY_MAP = 156;
        private const int BUILD_STRING = 157;
        private const int BUILD_TUPLE_UNPACK_WITH_CALL = 158;
        private const int LOAD_METHOD = 160;
        private const int CALL_METHOD = 161;
        private const int CALL_FINALLY = 162;
        private const int POP_FINALLY = 163;


        private static readonly int PY_INVALID_STACK_EFFECT = int.MaxValue;


        /* Masks and values used by FORMAT_VALUE opcode. */
        private const int FVC_MASK = 0x3;
        private const int FVC_NONE = 0x0;
        private const int FVC_STR = 0x1;
        private const int FVC_REPR = 0x2;
        private const int FVC_ASCII = 0x3;
        private const int FVS_MASK = 0x4;
        private const int FVS_HAVE_SPEC = 0x4;

        public static int stack_effect(CodeContext context, int opcode, object oparg=null) {
            int ioparg = 0;

            if (opcode >= HAVE_ARGUMENT) {
                if (oparg == null) {
                    throw PythonOps.ValueError("stack_effect: opcode requires oparg but oparg was not specified");
                }

                if (!Converter.TryConvertToIndex(oparg, out ioparg)) { // supported since CPython 3.8
                    ioparg = Converter.ImplicitConvertToInt32(oparg) ?? // warning since CPython 3.8, unsupported in 3.10
                        throw PythonOps.TypeError($"an integer is required (got type {PythonOps.GetPythonTypeName(oparg)})");
                }
            } else if (oparg != null) {
                throw PythonOps.ValueError("stack_effect: opcode does not permit oparg but oparg was specified");
            }

            int effect = stack_effect(opcode, ioparg);
            if (effect == PY_INVALID_STACK_EFFECT) {
                throw PythonOps.ValueError("invalid opcode or oparg");
            }
            return effect;
        }

        private static int stack_effect(int opcode, int oparg, int jump = -1) {
            switch (opcode) {
                case NOP:
                case EXTENDED_ARG:
                    return 0;

                /* Stack manipulation */
                case POP_TOP:
                    return -1;
                case ROT_TWO:
                case ROT_THREE:
                case ROT_FOUR:
                    return 0;
                case DUP_TOP:
                    return 1;
                case DUP_TOP_TWO:
                    return 2;

                /* Unary operators */
                case UNARY_POSITIVE:
                case UNARY_NEGATIVE:
                case UNARY_NOT:
                case UNARY_INVERT:
                    return 0;

                case SET_ADD:
                case LIST_APPEND:
                    return -1;
                case MAP_ADD:
                    return -2;

                /* Binary operators */
                case BINARY_POWER:
                case BINARY_MULTIPLY:
                case BINARY_MATRIX_MULTIPLY:
                case BINARY_MODULO:
                case BINARY_ADD:
                case BINARY_SUBTRACT:
                case BINARY_SUBSCR:
                case BINARY_FLOOR_DIVIDE:
                case BINARY_TRUE_DIVIDE:
                    return -1;
                case INPLACE_FLOOR_DIVIDE:
                case INPLACE_TRUE_DIVIDE:
                    return -1;

                case INPLACE_ADD:
                case INPLACE_SUBTRACT:
                case INPLACE_MULTIPLY:
                case INPLACE_MATRIX_MULTIPLY:
                case INPLACE_MODULO:
                    return -1;
                case STORE_SUBSCR:
                    return -3;
                case DELETE_SUBSCR:
                    return -2;

                case BINARY_LSHIFT:
                case BINARY_RSHIFT:
                case BINARY_AND:
                case BINARY_XOR:
                case BINARY_OR:
                    return -1;
                case INPLACE_POWER:
                    return -1;
                case GET_ITER:
                    return 0;

                case PRINT_EXPR:
                    return -1;
                case LOAD_BUILD_CLASS:
                    return 1;
                case INPLACE_LSHIFT:
                case INPLACE_RSHIFT:
                case INPLACE_AND:
                case INPLACE_XOR:
                case INPLACE_OR:
                    return -1;

                case SETUP_WITH:
                    /* 1 in the normal flow.
                     * Restore the stack position and push 6 values before jumping to
                     * the handler if an exception be raised. */
                    return jump != 0 ? 6 : 1;
                case WITH_CLEANUP_START:
                    return 2; /* or 1, depending on TOS */
                case WITH_CLEANUP_FINISH:
                    /* Pop a variable number of values pushed by WITH_CLEANUP_START
                     * + __exit__ or __aexit__. */
                    return -3;
                case RETURN_VALUE:
                    return -1;
                case IMPORT_STAR:
                    return -1;
                case SETUP_ANNOTATIONS:
                    return 0;
                case YIELD_VALUE:
                    return 0;
                case YIELD_FROM:
                    return -1;
                case POP_BLOCK:
                    return 0;
                case POP_EXCEPT:
                    return -3;
                case END_FINALLY:
                case POP_FINALLY:
                    /* Pop 6 values when an exception was raised. */
                    return -6;

                case STORE_NAME:
                    return -1;
                case DELETE_NAME:
                    return 0;
                case UNPACK_SEQUENCE:
                    return oparg - 1;
                case UNPACK_EX:
                    return (oparg & 0xFF) + (oparg >> 8);
                case FOR_ITER:
                    /* -1 at end of iterator, 1 if continue iterating. */
                    return jump > 0 ? -1 : 1;

                case STORE_ATTR:
                    return -2;
                case DELETE_ATTR:
                    return -1;
                case STORE_GLOBAL:
                    return -1;
                case DELETE_GLOBAL:
                    return 0;
                case LOAD_CONST:
                    return 1;
                case LOAD_NAME:
                    return 1;
                case BUILD_TUPLE:
                case BUILD_LIST:
                case BUILD_SET:
                case BUILD_STRING:
                    return 1 - oparg;
                case BUILD_LIST_UNPACK:
                case BUILD_TUPLE_UNPACK:
                case BUILD_TUPLE_UNPACK_WITH_CALL:
                case BUILD_SET_UNPACK:
                case BUILD_MAP_UNPACK:
                case BUILD_MAP_UNPACK_WITH_CALL:
                    return 1 - oparg;
                case BUILD_MAP:
                    return 1 - 2 * oparg;
                case BUILD_CONST_KEY_MAP:
                    return -oparg;
                case LOAD_ATTR:
                    return 0;
                case COMPARE_OP:
                    return -1;
                case IMPORT_NAME:
                    return -1;
                case IMPORT_FROM:
                    return 1;

                /* Jumps */
                case JUMP_FORWARD:
                case JUMP_ABSOLUTE:
                    return 0;

                case JUMP_IF_TRUE_OR_POP:
                case JUMP_IF_FALSE_OR_POP:
                    return jump != 0 ? 0 : -1;

                case POP_JUMP_IF_FALSE:
                case POP_JUMP_IF_TRUE:
                    return -1;

                case LOAD_GLOBAL:
                    return 1;

                /* Exception handling */
                case SETUP_FINALLY:
                    /* 0 in the normal flow.
                     * Restore the stack position and push 6 values before jumping to
                     * the handler if an exception be raised. */
                    return jump != 0 ? 6 : 0;
                case BEGIN_FINALLY:
                    /* Actually pushes 1 value, but count 6 for balancing with
                     * END_FINALLY and POP_FINALLY.
                     * This is the main reason of using this opcode instead of
                     * "LOAD_CONST None". */
                    return 6;
                case CALL_FINALLY:
                    return jump != 0 ? 1 : 0;

                case LOAD_FAST:
                    return 1;
                case STORE_FAST:
                    return -1;
                case DELETE_FAST:
                    return 0;

                case RAISE_VARARGS:
                    return -oparg;

                /* Functions and calls */
                case CALL_FUNCTION:
                    return -oparg;
                case CALL_METHOD:
                    return -oparg - 1;
                case CALL_FUNCTION_KW:
                    return -oparg - 1;
                case CALL_FUNCTION_EX:
                    return -1 - (((oparg & 0x01) != 0) ? 1 : 0);
                case MAKE_FUNCTION:
                    return -1 - (((oparg & 0x01) != 0) ? 1 : 0) - (((oparg & 0x02) != 0) ? 1 : 0) -
                        (((oparg & 0x04) != 0) ? 1 : 0) - (((oparg & 0x08) != 0) ? 1 : 0);
                case BUILD_SLICE:
                    if (oparg == 3)
                        return -2;
                    else
                        return -1;

                /* Closures */
                case LOAD_CLOSURE:
                    return 1;
                case LOAD_DEREF:
                case LOAD_CLASSDEREF:
                    return 1;
                case STORE_DEREF:
                    return -1;
                case DELETE_DEREF:
                    return 0;

                /* Iterators and generators */
                case GET_AWAITABLE:
                    return 0;
                case SETUP_ASYNC_WITH:
                    /* 0 in the normal flow.
                     * Restore the stack position to the position before the result
                     * of __aenter__ and push 6 values before jumping to the handler
                     * if an exception be raised. */
                    return jump != 0 ? -1 + 6 : 0;
                case BEFORE_ASYNC_WITH:
                    return 1;
                case GET_AITER:
                    return 0;
                case GET_ANEXT:
                    return 1;
                case GET_YIELD_FROM_ITER:
                    return 0;
                case END_ASYNC_FOR:
                    return -7;
                case FORMAT_VALUE:
                    /* If there's a fmt_spec on the stack, we go from 2->1,
                       else 1->1. */
                    return (oparg & FVS_MASK) == FVS_HAVE_SPEC ? -1 : 0;
                case LOAD_METHOD:
                    return 1;
                default:
                    return PY_INVALID_STACK_EFFECT;
            }
        }
    }
}
