import os
import brotli

'''感谢由 Charlie_Ping “查理平” 带来的bdx转换代码'''


class BdxConverter:
    __header = "BD@"
    __bin_header = b"BDX"
    __generator_author = b"&Musicreater"

    keys = {
        # x--, x++, addSmallX(-128~127), addX(-32768~32767), addBigX(-2147483648~2147483647)
        "x": [b"\x0f", b"\x0e", b"\x1c", b"\x14", b"\x15"],
        "y": [b"\x11", b"\x10", b"\x1d", b"\x16", b"\x17"],
        "z": [b"\x13", b"\x12", b"\x1e", b"\x18", b"\x19"],
        "end": b"\x58",
        "isSigned": b"\x5a",
        "placeCommandBlockWithData": b"\x1b",
        "placeBlock": b"\x07"
    }

    def __init__(self, file_path: str, author: str, blocks):
        self.author = author
        self.blocks = blocks
        self.file_path = file_path
        self.direction = [0, 0, 0]
        self.block_type = self.get_block_type
        self.__file = self.create_and_upload_file

    @property
    def get_block_type(self):
        """
        blocks
        [
            {
                "direction": [x: int, y: int, z: int],
                block_name: str,
                particular_value: int,
            }
        ]
        :return: list 给出的所有方块种类名称
        """
        block_type = set()
        for block in self.blocks:
            block_type.add(block["block_name"])
        block_type = list(block_type)
        return block_type

    @property
    def create_and_upload_file(self):
        """
        创建一个bdx文件
        :return: 一个文件对象
        """
        _dir = os.path.dirname(self.file_path)
        if not os.path.isdir(_dir):
            os.makedirs(_dir)
        _bytes = self.__bin_header
        _bytes += b"\x00"
        _bytes += self.author.encode("utf-8") + self.__generator_author
        for i in self.block_type:
            _bytes += b"\x00\x01"
            _bytes += bytes(i, encoding="utf-8")
        _bytes += b"\x00"
        _bytes += self.upload_blocks()
        _bytes += b"X"
        with open(self.file_path, "w+") as f:
            f.write("BD@")
            f.close()
        with open(self.file_path, "ab+") as f:
            f.write(brotli.compress(_bytes))
            f.close()
        return open(self.file_path,'a+')
    
    def upload_blocks(self):
        """
        计算差值
        写入移动过程
        写入方块
        更新差值
        :return:
        """
        _types = b""


        for block in self.blocks:
            # print(f"当前方块：{block['block_name']}, 位置： {block['direction']}]")
            diff = self.move_pointer(self.direction, block["direction"])
            _types += diff
            if block["block_name"] in ["command_block",
                                       "chain_command_block",
                                       "repeating_command_block"]:
                _types += self.obtain_command_block(block)
            else:
                _types += self.obtain_universal_block(block)
            self.direction = block["direction"]

        
        return _types

    def move_pointer(self, direction: list, new_direction):
        """
        给出 两个[x, y, z]坐标，返回pointer的移动过程
        :param direction: 坐标 1
        :param new_direction: 坐标 2
        :return: bytes
        """
        _bytes = b""
        for i, sign in enumerate(["x", "y", "z"]):
            # print(f"<{sign}> 新-旧={new_direction[i]-direction[i]}")
            distance = new_direction[i] - direction[i]
            if distance == 0:
                # print("距离是0？跳过了")
                continue
            _bytes += self.obtain_pointer_type(distance, sign)
            # print(f"向 {sign} 运动了 {distance} 格子")
        return _bytes

    @classmethod
    def obtain_pointer_type(cls, num: int, coordinate: str):
        """

        用于确定辅助玩家以某一数据类型走指定长度

        -1 -> 0
        1 -> 1
        [128, 127] -> 2
        [-32768, 32767] -> 3
        [-2147483648, 2147483647] -> 4
        :param num:
        :param coordinate: 坐标轴种类，x y 或 z
        :return:
        """
        if num == 0:
            return
        pointer = 0
        condition = (num != -1,  # byte=0, pointer=1
                     num < -1 or num > 1,  # byte=1, pointer=2
                     num < -128 or num > 127,  # byte=2, pointer=3
                     num < -32768 or num > 32767,  # byte=4, pointer=4
                     )
        for i in condition:
            if i:
                pointer += 1
        pointer_type = cls.keys[coordinate][pointer]

        byte_len = 2 ** (pointer - 2)
        if byte_len >= 1:
            num_byte = num.to_bytes(byte_len, byteorder="big", signed=True)
            return pointer_type + num_byte
        return pointer_type

    def obtain_universal_block(self, block):
        """
        给定一个方块， 返回此方块在这个bdx中的id和方块data
        :param block: {block_name: str,particular_value: int}
        :return: bytes
        """
        block_id = b"\x07" + self.block_type.index(block["block_name"]).to_bytes(2, byteorder="big", signed=False)
        particular_value = block["particular_value"].to_bytes(2, byteorder="big", signed=False)
        block_header = block_id + particular_value
        return block_header

    def obtain_command_block(self, block):
        """
        给定一个命令方块，返回命令方块各种数据
        :param block: {
            "direction": [x: int, y: int, z: int]
            "block_name": str,
            "particular_value": int,
            "impluse": int,  # unsigned_int32
            "command": str,
            "customName": str,
            "lastOutput": str,  # 没特殊要求写个\x00就得了
            "tickdelay": int,  # int32
            "executeOnFirstTick": int,  # 1 bytes
            "trackOutput": int,  # 1 bytes
            "conditional": int,  # 1 bytes
            "needRedstone": int  # 1 bytes
        }
        :return: bytes of command_block
        """

        block_id = b"\x1b" + self.block_type.index(block["block_name"]).to_bytes(2, byteorder="big", signed=False)
        particular_value = block["particular_value"].to_bytes(2, byteorder="big", signed=False)
        block_header = block_id + particular_value
        for i in [
            block["impluse"].to_bytes(4, byteorder="big", signed=False),
            bytes(block["command"], encoding="utf-8") + b"\x00",
            bytes(block["customName"], encoding="utf-8") + b"\x00",
            bytes(block["lastOutput"], encoding="utf-8") + b"\x00",
            block["tickdelay"].to_bytes(4, byteorder="big", signed=True),
            block["executeOnFirstTick"].to_bytes(1, byteorder="big"),
            block["trackOutput"].to_bytes(1, byteorder="big"),
            block["conditional"].to_bytes(1, byteorder="big"),
            block["needRedstone"].to_bytes(1, byteorder="big")
        ]:
            block_header += i
        return block_header

if __name__ == '__main__':
    block = [{"direction": [-1, -1, -1], "block_name": "concrete", "particular_value": 5},
             {"direction": [1, 5, 1], "block_name": "stained_glass", "particular_value": 7},
             {"direction": [2, 4, 1], "block_name": "command_block", "particular_value": 3,
              "impluse": 0,
              "command": "say A generator test",
              "customName": "test",
              "lastOutput": "",
              "tickdelay": 24,
              "executeOnFirstTick": 0,
              "trackOutput": 0,
              "conditional": 0,
              "needRedstone": 1
              },
             {"direction": [3, 4, 1], "block_name": "concrete", "particular_value": 6},
             {"direction": [-123412133, 4, 1], "block_name": "concrete", "particular_value": 7}]
    bdx = BdxConverter("./test02.bdx", "Charlie_Ping",block)
