import struct
from abc import ABC, abstractmethod
from io import BytesIO
from pathlib import Path
from typing import Any, BinaryIO, Literal, Optional, Self

from pydantic import BaseModel, NonNegativeInt

from igipy.formats import FileModel

QVMMajorVersion = Literal[8]

QVMMinorVersion = Literal[5, 7]


class QVMInstruction(BaseModel, ABC):
    address: NonNegativeInt
    address_next: NonNegativeInt

    @classmethod
    def model_validate_stream(cls, stream: BinaryIO, address: NonNegativeInt) -> Self:
        return cls(address=address, address_next=stream.tell())

    @property
    def operator(self) -> str:
        message = f"{self.__class__.__name__} does not have an operator"
        raise NotImplementedError(message)

    @property
    def priority(self) -> int:
        message = f"{self.__class__.__name__} does not have a priority"
        raise NotImplementedError(message)


class UnsupportedQVMInstruction(QVMInstruction, ABC):
    @classmethod
    def model_validate_stream(cls, stream: BinaryIO, address: NonNegativeInt) -> Self:
        message = f"{cls.__name__} is not implemented"
        raise NotImplementedError(message)


# Unsupported instructions


# noinspection DuplicatedCode
class InstructionNOP(UnsupportedQVMInstruction):
    pass


class InstructionRET(UnsupportedQVMInstruction):
    pass


class InstructionBT(UnsupportedQVMInstruction):
    pass


class InstructionJSR(UnsupportedQVMInstruction):
    pass


class InstructionPUSHA(UnsupportedQVMInstruction):
    pass


class InstructionPUSHS(UnsupportedQVMInstruction):
    pass


class InstructionPUSHI(UnsupportedQVMInstruction):
    pass


class InstructionBLK(UnsupportedQVMInstruction):
    pass


class InstructionILLEGAL(UnsupportedQVMInstruction):
    pass


# Instructions with arguments


def read_argument(stream: BinaryIO, argument_format: str) -> tuple[Any, ...]:
    argument_bytes = stream.read(struct.calcsize(argument_format))
    return struct.unpack(argument_format, argument_bytes)


# noinspection DuplicatedCode
class InstructionPUSH(QVMInstruction):
    argument: int

    @classmethod
    def model_validate_stream(cls, stream: BinaryIO, address: NonNegativeInt) -> Self:
        argument = read_argument(stream, "<I")[0]
        return cls(address=address, address_next=stream.tell(), argument=argument)


class InstructionPUSHB(QVMInstruction):
    argument: int

    @classmethod
    def model_validate_stream(cls, stream: BinaryIO, address: NonNegativeInt) -> Self:
        argument = read_argument(stream, "<B")[0]
        return cls(address=address, address_next=stream.tell(), argument=argument)


class InstructionPUSHW(QVMInstruction):
    argument: int

    @classmethod
    def model_validate_stream(cls, stream: BinaryIO, address: NonNegativeInt) -> Self:
        argument = read_argument(stream, "<H")[0]
        return cls(address=address, address_next=stream.tell(), argument=argument)


class InstructionPUSHF(QVMInstruction):
    argument: float

    @classmethod
    def model_validate_stream(cls, stream: BinaryIO, address: NonNegativeInt) -> Self:
        argument = read_argument(stream, "<f")[0]
        return cls(address=address, address_next=stream.tell(), argument=argument)


# noinspection DuplicatedCode
class InstructionPUSHSI(QVMInstruction):
    argument: int

    @classmethod
    def model_validate_stream(cls, stream: BinaryIO, address: NonNegativeInt) -> Self:
        argument = read_argument(stream, "<I")[0]
        return cls(address=address, address_next=stream.tell(), argument=argument)


class InstructionPUSHSIB(QVMInstruction):
    argument: int

    @classmethod
    def model_validate_stream(cls, stream: BinaryIO, address: NonNegativeInt) -> Self:
        argument = read_argument(stream, "<B")[0]
        return cls(address=address, address_next=stream.tell(), argument=argument)


class InstructionPUSHSIW(QVMInstruction):
    argument: int

    @classmethod
    def model_validate_stream(cls, stream: BinaryIO, address: NonNegativeInt) -> Self:
        argument = read_argument(stream, "<H")[0]
        return cls(address=address, address_next=stream.tell(), argument=argument)


# noinspection DuplicatedCode
class InstructionPUSHII(QVMInstruction):
    argument: int

    @classmethod
    def model_validate_stream(cls, stream: BinaryIO, address: NonNegativeInt) -> Self:
        argument = read_argument(stream, "<I")[0]
        return cls(address=address, address_next=stream.tell(), argument=argument)


class InstructionPUSHIIB(QVMInstruction):
    argument: int

    @classmethod
    def model_validate_stream(cls, stream: BinaryIO, address: NonNegativeInt) -> Self:
        argument = read_argument(stream, "<B")[0]
        return cls(address=address, address_next=stream.tell(), argument=argument)


class InstructionPUSHIIW(QVMInstruction):
    argument: int

    @classmethod
    def model_validate_stream(cls, stream: BinaryIO, address: NonNegativeInt) -> Self:
        argument = read_argument(stream, "<H")[0]
        return cls(address=address, address_next=stream.tell(), argument=argument)


class InstructionBRA(QVMInstruction):
    argument: int

    @classmethod
    def model_validate_stream(cls, stream: BinaryIO, address: NonNegativeInt) -> Self:
        argument = read_argument(stream, "<i")[0]
        return cls(address=address, address_next=stream.tell(), argument=argument)


class InstructionBF(QVMInstruction):
    argument: int

    @classmethod
    def model_validate_stream(cls, stream: BinaryIO, address: NonNegativeInt) -> Self:
        argument = read_argument(stream, "<i")[0]
        return cls(address=address, address_next=stream.tell(), argument=argument)


class InstructionCALL(QVMInstruction):
    argument: list[int]

    @classmethod
    def model_validate_stream(cls, stream: BinaryIO, address: NonNegativeInt) -> Self:
        argument_count: int = read_argument(stream, "<I")[0]
        argument_bytes = stream.read(4 * argument_count)
        argument = struct.unpack("<" + "i" * argument_count, argument_bytes)
        return cls(address=address, address_next=stream.tell(), argument=argument)


# Instructions without arguments


# noinspection DuplicatedCode
class InstructionPUSH0(QVMInstruction):
    pass


class InstructionPUSH1(QVMInstruction):
    pass


class InstructionPUSHM(QVMInstruction):
    pass


class InstructionPOP(QVMInstruction):
    pass


class InstructionADD(QVMInstruction):
    @property
    def operator(self) -> str:
        return "+"

    @property
    def priority(self) -> int:
        return 4


class InstructionSUB(QVMInstruction):
    @property
    def operator(self) -> str:
        return "-"

    @property
    def priority(self) -> int:
        return 4


class InstructionMUL(QVMInstruction):
    @property
    def operator(self) -> str:
        return "*"

    @property
    def priority(self) -> int:
        return 3


class InstructionDIV(QVMInstruction):
    @property
    def operator(self) -> str:
        return "/"

    @property
    def priority(self) -> int:
        return 3


class InstructionSHL(QVMInstruction):
    @property
    def operator(self) -> str:
        return "<<"

    @property
    def priority(self) -> int:
        return 5


class InstructionSHR(QVMInstruction):
    @property
    def operator(self) -> str:
        return ">>"

    @property
    def priority(self) -> int:
        return 5


class InstructionAND(QVMInstruction):
    @property
    def operator(self) -> str:
        return "&"

    @property
    def priority(self) -> int:
        return 8


class InstructionOR(QVMInstruction):
    @property
    def operator(self) -> str:
        return "|"

    @property
    def priority(self) -> int:
        return 10


class InstructionXOR(QVMInstruction):
    @property
    def operator(self) -> str:
        return "^"

    @property
    def priority(self) -> int:
        return 9


class InstructionLAND(QVMInstruction):
    @property
    def operator(self) -> str:
        return "&&"

    @property
    def priority(self) -> int:
        return 11


class InstructionLOR(QVMInstruction):
    @property
    def operator(self) -> str:
        return "||"

    @property
    def priority(self) -> int:
        return 12


class InstructionEQ(QVMInstruction):
    @property
    def operator(self) -> str:
        return "=="

    @property
    def priority(self) -> int:
        return 7


class InstructionNE(QVMInstruction):
    @property
    def operator(self) -> str:
        return "!="

    @property
    def priority(self) -> int:
        return 7


class InstructionLT(QVMInstruction):
    @property
    def operator(self) -> str:
        return "<"

    @property
    def priority(self) -> int:
        return 6


class InstructionLE(QVMInstruction):
    @property
    def operator(self) -> str:
        return "<="

    @property
    def priority(self) -> int:
        return 6


class InstructionGT(QVMInstruction):
    @property
    def operator(self) -> str:
        return ">"

    @property
    def priority(self) -> int:
        return 6


class InstructionGE(QVMInstruction):
    @property
    def operator(self) -> str:
        return ">="

    @property
    def priority(self) -> int:
        return 6


class InstructionASSIGN(QVMInstruction):
    @property
    def operator(self) -> str:
        return "="

    @property
    def priority(self) -> int:
        return 14


class InstructionPLUS(QVMInstruction):
    @property
    def operator(self) -> str:
        return "+"

    @property
    def priority(self) -> int:
        return 2


class InstructionMINUS(QVMInstruction):
    @property
    def operator(self) -> str:
        return "-"

    @property
    def priority(self) -> int:
        return 2


class InstructionINV(QVMInstruction):
    @property
    def operator(self) -> str:
        return "~"

    @property
    def priority(self) -> int:
        return 2


class InstructionNOT(QVMInstruction):
    @property
    def operator(self) -> str:
        return "!"

    @property
    def priority(self) -> int:
        return 2


class InstructionBRK(QVMInstruction):
    pass


# AST


class ASTNode(BaseModel, ABC):
    @abstractmethod
    def get_token(self, indent: int = 0) -> str:
        message = f"{self.__class__.__name__} does not have a token"
        raise NotImplementedError(message)


class NumberLiteral(ASTNode):
    value: int | float

    def get_token(self, indent: int = 0) -> str:  # noqa: ARG002
        return f"{self.value}"


class StringLiteral(ASTNode):
    value: str

    def get_token(self, indent: int = 0) -> str:  # noqa: ARG002
        return f'"{self.value}"'


class IdentifierLiteral(ASTNode):
    value: str

    def get_token(self, indent: int = 0) -> str:  # noqa: ARG002
        return f"{self.value}"


class UnaryOperator(ASTNode):
    operator: str
    operand: ASTNode
    priority: int

    def get_token(self, indent: int = 0) -> str:
        operand_token = self.operand.get_token(indent=indent + 1)
        return f"{self.operator}{operand_token}"


class BinaryOperator(ASTNode):
    operator: str
    left_operand: ASTNode
    right_operand: ASTNode
    priority: int

    def get_token(self, indent: int = 0) -> str:
        left_operand_token = self.left_operand.get_token(indent=indent + 1)
        right_operand_token = self.right_operand.get_token(indent=indent + 1)
        return f"{left_operand_token} {self.operator} {right_operand_token}"


class Parentheses(ASTNode):
    expression: ASTNode

    def get_token(self, indent: int = 0) -> str:
        expression_token = self.expression.get_token(indent=indent + 1)
        return f"({expression_token})"


class CallStatement(ASTNode):
    function: ASTNode
    arguments: list[ASTNode]

    def get_token(self, indent: int = 0) -> str:
        function_token = self.function.get_token(indent=indent + 1)
        token_length = len(function_token)
        token_list = []

        for argument in self.arguments:
            argument_token = argument.get_token(indent=indent + 1)

            if isinstance(argument, CallStatement):
                argument_token_indent = "\t" * (indent + 1)
                argument_token = f"\n{argument_token_indent}{argument_token}"
                token_length = len(argument_token) + 2
            elif len(argument_token) + token_length > 300:  # noqa: PLR2004
                argument_token = f"\n{argument_token}"
                token_length = len(argument_token) + 2
            else:
                token_length += len(argument_token) + 2

            token_list.append(argument_token)

        arguments_token = ", ".join(token_list)
        return f"{function_token}({arguments_token})"


class WhileStatement(ASTNode):
    condition: ASTNode
    body: "StatementList"

    def get_token(self, indent: int = 0) -> str:
        token = ""
        token += f"{indent * '\t'}while({self.condition.get_token(indent + 1)})\n"
        token += f"{indent * '\t'}{{\n"

        for statement in self.body.statements:
            if isinstance(statement, (IfStatement, WhileStatement)):
                token += f"{statement.get_token(indent + 1)}\n"
            else:
                token += f"{(indent + 1) * '\t'}{statement.get_token(indent + 1)};\n"

        token += f"{indent * '\t'}}}\n"

        return token


class IfStatement(ASTNode):
    condition: ASTNode
    body: "StatementList"
    else_body: Optional["StatementList"] = None

    def get_token(self, indent: int = 0) -> str:
        condition_token = self.condition.get_token(indent=indent + 1)
        condition_token_indent = "\t" * indent

        token_indent_string = "\t" * indent

        token = f"{condition_token_indent}if({condition_token})\n"
        token += self.get_statements_token(self.body.statements, indent=indent)

        if self.else_body:
            token += f"{token_indent_string}else\n"
            token += self.get_statements_token(self.else_body.statements, indent=indent)

        return token

    @classmethod
    def get_statements_token(cls, statements: list[ASTNode], indent: int = 0) -> str:
        statements_token_indent = "\t" * indent
        statements_token = ""
        statements_token += f"{statements_token_indent}{{\n"

        for statement in statements:
            statement_token_indent = "\t" * (indent + 1)
            statement_token = statement.get_token(indent=indent + 1)

            if not isinstance(statement, (IfStatement, WhileStatement)):
                statement_token = f"{statement_token_indent}{statement_token};\n"

            statements_token += statement_token

        statements_token += f"{statements_token_indent}}}\n"

        return statements_token


class StatementList(ASTNode):
    statements: list[ASTNode]

    @classmethod
    def from_instructions(  # noqa: C901, PLR0912, PLR0915
        cls,
        identifiers: list[str],
        strings: list[str],
        instructions: dict[NonNegativeInt, QVMInstruction],
        start_address: NonNegativeInt = 0,
        stop_address: NonNegativeInt | None = None,
    ) -> Self:
        statements: list[ASTNode] = []

        current_address = start_address

        while current_address != stop_address:
            current_instruction = instructions[current_address]

            match current_instruction.__class__.__qualname__:
                case "InstructionBRK" | "InstructionBRA":
                    break

                case "InstructionPOP":
                    current_address = current_instruction.address_next

                case "InstructionPUSH" | "InstructionPUSHB" | "InstructionPUSHW" | "InstructionPUSHF":
                    statement = NumberLiteral(value=current_instruction.argument)
                    statements.append(statement)
                    current_address = current_instruction.address_next

                case "InstructionPUSH0":
                    statement = NumberLiteral(value=0)
                    statements.append(statement)
                    current_address = current_instruction.address_next

                case "InstructionPUSH1":
                    statement = NumberLiteral(value=1)
                    statements.append(statement)
                    current_address = current_instruction.address_next

                case "InstructionPUSHM":
                    statement = NumberLiteral(value=0xFFFFFFFF)
                    statements.append(statement)
                    current_address = current_instruction.address_next

                case "InstructionPUSHSI" | "InstructionPUSHSIB" | "InstructionPUSHSIW":
                    statement = StringLiteral(value=strings[current_instruction.argument])
                    statements.append(statement)
                    current_address = current_instruction.address_next

                case "InstructionPUSHII" | "InstructionPUSHIIB" | "InstructionPUSHIIW":
                    statement = IdentifierLiteral(value=identifiers[current_instruction.argument])
                    statements.append(statement)
                    current_address = current_instruction.address_next

                case "InstructionPLUS" | "InstructionMINUS" | "InstructionINV" | "InstructionNOT":
                    operand = statements.pop()

                    if isinstance(operand, (UnaryOperator, BinaryOperator)):
                        operand = Parentheses(expression=operand)

                    statement = UnaryOperator(
                        operator=current_instruction.operator,
                        operand=operand,
                        priority=current_instruction.priority,
                    )
                    statements.append(statement)
                    current_address = current_instruction.address_next

                case (
                    "InstructionADD"
                    | "InstructionSUB"
                    | "InstructionMUL"
                    | "InstructionDIV"
                    | "InstructionSHL"
                    | "InstructionSHR"
                    | "InstructionAND"
                    | "InstructionOR"
                    | "InstructionXOR"
                    | "InstructionLAND"
                    | "InstructionLOR"
                    | "InstructionEQ"
                    | "InstructionNE"
                    | "InstructionLT"
                    | "InstructionLE"
                    | "InstructionGT"
                    | "InstructionGE"
                    | "InstructionASSIGN"
                ):
                    right_operand = statements.pop()
                    left_operand = statements.pop()

                    if (
                        isinstance(right_operand, (UnaryOperator, BinaryOperator))
                        and current_instruction.priority < right_operand.priority
                    ):
                        right_operand = Parentheses(expression=right_operand)

                    if (
                        isinstance(left_operand, (UnaryOperator, BinaryOperator))
                        and current_instruction.priority < left_operand.priority
                    ):
                        left_operand = Parentheses(expression=left_operand)

                    statement = BinaryOperator(
                        operator=current_instruction.operator,
                        left_operand=left_operand,
                        right_operand=right_operand,
                        priority=current_instruction.priority,
                    )
                    statements.append(statement)
                    current_address = current_instruction.address_next

                case "InstructionCALL":
                    function = statements.pop()
                    arguments = []

                    for address in current_instruction.argument:
                        argument = cls.from_instructions(
                            identifiers=identifiers,
                            strings=strings,
                            instructions=instructions,
                            start_address=address,
                            stop_address=None,
                        )

                        if len(argument.statements) > 1:
                            message = f"Unexpected statement count: {len(argument.statements)}"
                            raise ValueError(message)

                        argument = argument.statements[0]
                        arguments.append(argument)

                    statement = CallStatement(function=function, arguments=arguments)
                    statements.append(statement)

                    next_address = current_instruction.address_next
                    next_instruction = instructions[next_address]

                    current_address = next_instruction.address_next + next_instruction.argument

                case "InstructionBF":
                    condition = statements.pop()
                    body = cls.from_instructions(
                        identifiers=identifiers,
                        strings=strings,
                        instructions=instructions,
                        start_address=current_instruction.address_next,
                        stop_address=None,
                    )

                    next_address = current_instruction.address_next + current_instruction.argument - 5
                    next_instruction = instructions[next_address]

                    match next_instruction.argument:
                        case _ if next_instruction.argument < 0:
                            statement = WhileStatement(condition=condition, body=body)
                            current_address = current_instruction.address_next + current_instruction.argument

                        case 0:
                            statement = IfStatement(condition=condition, body=body)
                            current_address = current_instruction.address_next + current_instruction.argument

                        case _ if next_instruction.argument > 0:
                            else_body = cls.from_instructions(
                                identifiers=identifiers,
                                strings=strings,
                                instructions=instructions,
                                start_address=current_instruction.address_next + current_instruction.argument,
                                stop_address=next_instruction.address_next + next_instruction.argument,
                            )
                            statement = IfStatement(condition=condition, body=body, else_body=else_body)
                            current_address = next_instruction.address_next + next_instruction.argument
                        case _:
                            message = f"Unsupported argument: {next_instruction.argument}"
                            raise ValueError(message)

                    statements.append(statement)
                case _:
                    message = f"Unsupported instruction: {current_instruction.__class__.__qualname__}"
                    raise ValueError(message)

        return cls(statements=statements)

    def get_token(self, indent: int = 0) -> str:
        token = ""

        for statement in self.statements:
            statement_token = statement.get_token(indent=indent)

            if not isinstance(statement, (IfStatement, WhileStatement)):
                statement_token = f"{statement_token};\n"

            token += statement_token

        return token


# QVM


class QVMHeader(BaseModel):
    signature: Literal[b"LOOP"]
    major_version: QVMMajorVersion
    minor_version: QVMMinorVersion
    identifiers_points_offset: NonNegativeInt
    identifiers_data_offset: NonNegativeInt
    identifiers_points_size: NonNegativeInt
    identifiers_data_size: NonNegativeInt
    strings_points_offset: NonNegativeInt
    strings_data_offset: NonNegativeInt
    strings_points_size: NonNegativeInt
    strings_data_size: NonNegativeInt
    instructions_data_offset: NonNegativeInt
    instructions_data_size: NonNegativeInt
    unknown_1: Literal[0]
    unknown_2: Literal[0]
    footer_data_offset: NonNegativeInt | None = None

    @classmethod
    def model_validate_bytes(cls, data: bytes) -> "QVMHeader":
        obj_values = struct.unpack("4s14I", data[:60])
        obj_mapping = dict(zip(cls.__pydantic_fields__.keys(), obj_values, strict=False))
        obj = cls(**obj_mapping)

        if obj.minor_version == 5 and len(data[60:]) > 4:  # noqa: PLR2004
            footer_offset = struct.unpack("I", data[60:64])[0]
            obj.footer_data_offset = footer_offset

        return obj

    @property
    def identifiers_data_slice(self) -> slice:
        return slice(self.identifiers_data_offset, self.identifiers_data_offset + self.identifiers_data_size)

    @property
    def strings_data_slice(self) -> slice:
        return slice(self.strings_data_offset, self.strings_data_offset + self.strings_data_size)

    @property
    def instructions_data_slice(self) -> slice:
        return slice(self.instructions_data_offset, self.instructions_data_offset + self.instructions_data_size)


class QVMStringList(BaseModel):
    value: list[str]

    @classmethod
    def model_validate_bytes(cls, data: bytes) -> Self:
        value_bytes = data.split(b"\x00")[:-1]
        value_strings = [value.decode("utf-8") for value in value_bytes]
        value = [value.replace("\n", "\\n").replace('"', '\\"') for value in value_strings]
        return cls(value=value)


class QVMInstructionDict(BaseModel):
    value: dict[NonNegativeInt, QVMInstruction]

    @classmethod
    def model_validate_bytes(cls, data: bytes, version: QVMMinorVersion) -> Self:
        stream = BytesIO(data)
        value = {}
        codes = cls.get_code_instruction_dict(version)

        while stream.tell() < len(data):
            address = stream.tell()
            instruction_class = codes.get(stream.read(1), UnsupportedQVMInstruction)
            instruction = instruction_class.model_validate_stream(stream, address)
            value[instruction.address] = instruction

        return cls(value=value)

    @classmethod
    def get_code_instruction_dict(cls, version: QVMMinorVersion) -> dict[bytes, type[QVMInstruction]]:
        match version:
            case 5:
                return cls.get_code_instruction_dict_v5()
            case 7:
                return cls.get_code_instruction_dict_v7()
            case _:
                message = f"Unsupported QVM version: {version}"
                raise ValueError(message)

    # noinspection DuplicatedCode
    @classmethod
    def get_code_instruction_dict_v5(cls) -> dict[bytes, type[QVMInstruction]]:
        return {
            b"\x00": InstructionBRK,
            b"\x01": InstructionNOP,
            b"\x02": InstructionPUSH,
            b"\x03": InstructionPUSHB,
            b"\x04": InstructionPUSHW,
            b"\x05": InstructionPUSHF,
            b"\x06": InstructionPUSHA,
            b"\x07": InstructionPUSHS,
            b"\x08": InstructionPUSHSI,
            b"\x09": InstructionPUSHSIB,
            b"\x0a": InstructionPUSHSIW,
            b"\x0b": InstructionPUSHI,
            b"\x0c": InstructionPUSHII,
            b"\x0d": InstructionPUSHIIB,
            b"\x0e": InstructionPUSHIIW,
            b"\x0f": InstructionPUSH0,
            b"\x10": InstructionPUSH1,
            b"\x11": InstructionPUSHM,
            b"\x12": InstructionPOP,
            b"\x13": InstructionRET,
            b"\x14": InstructionBRA,
            b"\x15": InstructionBF,
            b"\x16": InstructionBT,
            b"\x17": InstructionJSR,
            b"\x18": InstructionCALL,
            b"\x19": InstructionADD,
            b"\x1a": InstructionSUB,
            b"\x1b": InstructionMUL,
            b"\x1c": InstructionDIV,
            b"\x1d": InstructionSHL,
            b"\x1e": InstructionSHR,
            b"\x1f": InstructionAND,
            b"\x20": InstructionOR,
            b"\x21": InstructionXOR,
            b"\x22": InstructionLAND,
            b"\x23": InstructionLOR,
            b"\x24": InstructionEQ,
            b"\x25": InstructionNE,
            b"\x26": InstructionLT,
            b"\x27": InstructionLE,
            b"\x28": InstructionGT,
            b"\x29": InstructionGE,
            b"\x2a": InstructionASSIGN,
            b"\x2b": InstructionPLUS,
            b"\x2c": InstructionMINUS,
            b"\x2d": InstructionINV,
            b"\x2e": InstructionNOT,
            b"\x2f": InstructionBLK,
            b"\x30": InstructionILLEGAL,
        }

    # noinspection DuplicatedCode
    @classmethod
    def get_code_instruction_dict_v7(cls) -> dict[bytes, type[QVMInstruction]]:
        return {
            b"\x00": InstructionBRK,
            b"\x01": InstructionNOP,
            b"\x02": InstructionRET,
            b"\x03": InstructionBRA,
            b"\x04": InstructionBF,
            b"\x05": InstructionBT,
            b"\x06": InstructionJSR,
            b"\x07": InstructionCALL,
            b"\x08": InstructionPUSH,
            b"\x09": InstructionPUSHB,
            b"\x0a": InstructionPUSHW,
            b"\x0b": InstructionPUSHF,
            b"\x0c": InstructionPUSHA,
            b"\x0d": InstructionPUSHS,
            b"\x0e": InstructionPUSHSI,
            b"\x0f": InstructionPUSHSIB,
            b"\x10": InstructionPUSHSIW,
            b"\x11": InstructionPUSHI,
            b"\x12": InstructionPUSHII,
            b"\x13": InstructionPUSHIIB,
            b"\x14": InstructionPUSHIIW,
            b"\x15": InstructionPUSH0,
            b"\x16": InstructionPUSH1,
            b"\x17": InstructionPUSHM,
            b"\x18": InstructionPOP,
            b"\x19": InstructionADD,
            b"\x1a": InstructionSUB,
            b"\x1b": InstructionMUL,
            b"\x1c": InstructionDIV,
            b"\x1d": InstructionSHL,
            b"\x1e": InstructionSHR,
            b"\x1f": InstructionAND,
            b"\x20": InstructionOR,
            b"\x21": InstructionXOR,
            b"\x22": InstructionLAND,
            b"\x23": InstructionLOR,
            b"\x24": InstructionEQ,
            b"\x25": InstructionNE,
            b"\x26": InstructionLT,
            b"\x27": InstructionLE,
            b"\x28": InstructionGT,
            b"\x29": InstructionGE,
            b"\x2a": InstructionASSIGN,
            b"\x2b": InstructionPLUS,
            b"\x2c": InstructionMINUS,
            b"\x2d": InstructionINV,
            b"\x2e": InstructionNOT,
            b"\x2f": InstructionBLK,
            b"\x30": InstructionILLEGAL,
        }


class QVM(FileModel):
    header: QVMHeader
    identifiers: QVMStringList
    strings: QVMStringList
    instructions: QVMInstructionDict

    @classmethod
    def model_validate_stream(cls, stream: BytesIO, path: str | None = None, size: int | None = None) -> Self:
        instance = cls.model_validate_bytes(data=stream.read())
        instance.meta_path = path
        instance.meta_size = size
        return instance

    @classmethod
    def model_validate_bytes(cls, data: bytes) -> Self:
        header = QVMHeader.model_validate_bytes(data=data)

        identifiers_data = data[header.identifiers_data_slice]
        identifiers = QVMStringList.model_validate_bytes(data=identifiers_data)

        strings_data = data[header.strings_data_slice]
        strings = QVMStringList.model_validate_bytes(data=strings_data)

        instructions_data = data[header.instructions_data_slice]
        instructions = QVMInstructionDict.model_validate_bytes(data=instructions_data, version=header.minor_version)

        return cls(header=header, identifiers=identifiers, strings=strings, instructions=instructions)

    def model_dump_stream(self, path: Path, stream: BytesIO) -> tuple[Path, BytesIO]:
        path = path.with_suffix(".qsc")
        stream.write(self.get_statement_list().get_token().encode())
        return path, stream

    def get_statement_list(self) -> "StatementList":
        return StatementList.from_instructions(
            identifiers=self.identifiers.value,
            strings=self.strings.value,
            instructions=self.instructions.value,
        )
