import ast
from typing import Any, Sized

from boa3.internal.model.builtin.method import IBuiltinMethod
from boa3.internal.model.builtin.native.stdlib.stringsplitmethod import \
    StringSplitWithoutRemoveEmptyEntriesMethod
from boa3.internal.model.type.type import Type
from boa3.internal.model.variable import Variable
from boa3.internal.neo.vm.opcode.Opcode import Opcode


class StrSplitMethod(IBuiltinMethod):
    def __init__(self, args: dict[str, Variable] = None, defaults: list[ast.AST] = None):
        identifier = 'split'

        if args is None:
            args: dict[str, Variable] = {
                'self': Variable(Type.str),
                'sep': Variable(Type.str),
                'maxsplit': Variable(Type.int)
            }

        if defaults is None:
            # whitespace is the default separator
            separator_default = ast.parse("' '").body[0].value
            # maxsplit the default value is -1
            maxsplit_default = ast.parse("-1").body[0].value.operand
            maxsplit_default.n = -1
            defaults = [separator_default, maxsplit_default]

        super().__init__(identifier, args, defaults,
                         return_type=Type.list.build_collection(Type.str))

    @property
    def identifier(self) -> str:
        if 1 <= len(self.args) <= 2:
            return '-{0}_2_args'.format(self._identifier)
        return self._identifier

    @property
    def _args_on_stack(self) -> int:
        return len(self.args)

    @property
    def _body(self) -> str | None:
        return None

    @property
    def generation_order(self) -> list[int]:
        # the original string must be the top value in the stack
        indexes = list(range(len(self.args)))
        str_index = list(self.args).index('self')

        if indexes[-1] != str_index:
            # context must be the last generated argument
            indexes.remove(str_index)
            indexes.append(str_index)
        return indexes

    def generate_internal_opcodes(self, code_generator):
        from boa3.internal.model.builtin.builtin import Builtin
        from boa3.internal.model.operation.binaryop import BinaryOp

        code_generator.duplicate_stack_item(3)
        code_generator.swap_reverse_stack_items(2)

        code_generator.convert_builtin_method_call(StringSplitWithoutRemoveEmptyEntriesMethod())

        # if maxsplit > 0
        code_generator.duplicate_stack_item(2)
        code_generator.convert_literal(0)
        is_valid_split_count = code_generator.convert_begin_if()
        code_generator.change_jump(is_valid_split_count, Opcode.JMPLT)

        #   while len(array) < maxsplit + 1:
        while_start = code_generator.convert_begin_while()
        code_generator.duplicate_stack_top_item()

        #       concat values
        concat_operation = BinaryOp.Concat.build(Type.bytearray)  # to return buffer

        code_generator.duplicate_stack_item(4)
        code_generator.duplicate_stack_item(2)
        code_generator.insert_opcode(Opcode.POPITEM, pop_from_stack=True, add_to_stack=[Type.str])
        code_generator.convert_operation(concat_operation, is_internal=True)
        code_generator.duplicate_stack_item(2)
        code_generator.insert_opcode(Opcode.POPITEM, pop_from_stack=True, add_to_stack=[Type.str])
        code_generator.swap_reverse_stack_items(2)
        code_generator.convert_operation(concat_operation, is_internal=True)
        code_generator.convert_cast(Type.str, is_internal=True)
        code_generator.convert_builtin_method_call(Builtin.SequenceAppend, is_internal=True)

        while_condition = code_generator.bytecode_size
        code_generator.duplicate_stack_top_item()
        code_generator.convert_builtin_method_call(Builtin.Len, is_internal=True)
        code_generator.duplicate_stack_item(3)
        code_generator.insert_opcode(Opcode.INC)
        code_generator.convert_operation(BinaryOp.Gt, is_internal=True)

        code_generator.convert_end_while(while_start, while_condition, is_internal=True)

        code_generator.convert_end_if(is_valid_split_count, is_internal=True)
        # clean stack

        code_generator.swap_reverse_stack_items(3)
        code_generator.remove_stack_top_item()
        code_generator.remove_stack_top_item()

    def build(self, value: Any) -> IBuiltinMethod:
        if isinstance(value, Sized) and 1 <= len(value) <= 2:
            return StrSplitWithoutMaxsplitMethod()

        return self


class StrSplitWithoutMaxsplitMethod(StrSplitMethod):
    def __init__(self):
        args: dict[str, Variable] = {
            'self': Variable(Type.str),
            'sep': Variable(Type.str),
        }
        # whitespace is the default separator
        separator_default = ast.parse("' '").body[0].value

        super().__init__(args, defaults=[separator_default])

    def generate_internal_opcodes(self, code_generator):
        code_generator.convert_builtin_method_call(StringSplitWithoutRemoveEmptyEntriesMethod())
