from enum import IntEnum

from .Mesh import BoneWeights4, SubMesh, VertexData
from .NamedObject import NamedObject
from .PPtr import PPtr
from ..export import SpriteHelper
from ..enums import SpriteMeshType, SpritePackingMode, SpritePackingRotation
from ..streams import EndianBinaryWriter, EndianBinaryReader


class Sprite(NamedObject):
    @property
    def image(self):
        return SpriteHelper.get_image_from_sprite(self)

    def __init__(self, reader):
        super().__init__(reader=reader)
        version = self.version

        self.m_Rect = reader.read_rectangle_f()
        self.m_Offset = reader.read_vector2()
        if version >= (4, 5):  # 4.5 and up
            self.m_Border = reader.read_vector4()

        self.m_PixelsToUnits = reader.read_float()
        if version >= (5, 4, 2) or (
            version >= (5, 4, 1, 3) and self.build_type.IsPatch
        ):  # 5.4.1p3 and up
            self.m_Pivot = reader.read_vector2()

        self.m_Extrude = reader.read_u_int()
        if version >= (5, 3):  # 5.3 and up
            self.m_IsPolygon = reader.read_boolean()
            reader.align_stream()

        if version >= (2017,):  # 2017 and up
            first = reader.read_bytes(16)  # GUID
            second = reader.read_long()
            self.m_RenderDataKey = (first, second)
            self.m_AtlasTags = reader.read_string_array()
            self.m_SpriteAtlas = PPtr(reader)  # SpriteAtlas

        self.m_RD = SpriteRenderData(reader)

        if version >= (2017,):  # 2017 and up
            m_PhysicsShapeSize = reader.read_int()
            self.m_PhysicsShape = [
                reader.read_vector2_array() for _ in range(m_PhysicsShapeSize)
            ]

        if version >= (2018,):  # 2018 and up
            m_BonesSize = reader.read_int()
            # TODO: might occur in earlier 2020 versions - 2020.3.13 reported
            if version >= (
                2020,
                3,
            ):
                self.m_Bones = [SpriteBone() for _ in range(m_BonesSize)]
            else:
                self.m_Bones = [reader.read_vector2_array() for _ in range(m_BonesSize)]

    def save(self, writer: EndianBinaryWriter = None):
        if writer is None:
            writer = EndianBinaryWriter(endian=self.reader.endian)
        version = self.version

        super().save(writer)
        writer.write_rectangle_f(self.m_Rect)
        writer.write_vector2(self.m_Offset)
        if version >= (4, 5):  # 4.5 and up
            writer.write_vector4(self.m_Border)

        writer.write_float(self.m_PixelsToUnits)
        if version >= (5, 4, 2) or (
            version >= (5, 4, 1, 3) and self.build_type.IsPatch
        ):  # 5.4.1p3 and up
            writer.write_vector2(self.m_Pivot)

        writer.write_u_int(self.m_Extrude)
        if version >= (5, 3):  # 5.3 and up
            writer.write_boolean(self.m_IsPolygon)
            writer.align_stream()

        if version >= (2017,):  # 2017 and up
            writer.write_bytes(self.m_RenderDataKey[0])  # GUID
            writer.write_long(self.m_RenderDataKey[1])
            writer.write_string_array(self.m_AtlasTags)
            self.m_SpriteAtlas.save(writer)  # SpriteAtlas

        self.m_RD.save(writer, version)

        if version >= (2017,):  # 2017 and up
            writer.write_int(len(self.m_PhysicsShape))
            for phys in self.m_PhysicsShape:
                writer.write_vector2_array(phys)

        if version >= (2018,):  # 2018 and up
            writer.write_int(len(self.m_Bones))
            if version >= (2020, 3):
                for bone in self.m_Bones:
                    bone.save(writer)
            else:
                for bone in self.m_Bones:
                    writer.write_vector2_array(bone)

        self.set_raw_data(writer.bytes)


class SecondarySpriteTexture:
    def __init__(self, reader):
        self.texture = PPtr(reader)  # Texture2D
        self.name = reader.read_string_to_null()

    def save(self, writer):
        self.texture.save(writer)
        writer.write_string_to_null(self.name)


class SpriteSettings:
    def __init__(self, reader):
        self.settingsRaw = reader.read_u_int()
        self.packed = self.settingsRaw & 1  # 1
        self.packingMode = SpritePackingMode((self.settingsRaw >> 1) & 1)  # 1
        self.packingRotation = SpritePackingRotation((self.settingsRaw >> 2) & 0xF)  # 4
        self.meshType = SpriteMeshType((self.settingsRaw >> 6) & 1)  # 1
        # rest of the bits are reserved

    def save(self, writer):
        writer.write_u_int(self.settingsRaw)


class SpriteVertex:
    def __init__(self, reader):
        version = reader.version

        self.pos = reader.read_vector3()
        if version[:2] <= (4, 3):  # 4.3 and down
            self.uv = reader.read_vector2()

    def save(self, writer, version):
        writer.write_vector3(self.pos)
        if version[:2] <= (4, 3):  # 4.3 and down
            writer.write__vector2(self.uv)


class SpriteRenderData:
    def __init__(self, reader):
        version = reader.version

        self.texture = PPtr(reader)  # Texture2D
        if version >= (5, 2):  # 5.2 and up
            self.alphaTexture = PPtr(reader)  # Texture2D

        if version >= (2019,):  # 2019 and up
            secondaryTexturesSize = reader.read_int()
            self.secondaryTextures = [
                SecondarySpriteTexture(reader) for _ in range(secondaryTexturesSize)
            ]

        if version >= (5, 6):  # 5.6 and up
            SubMeshesSize = reader.read_int()
            self.m_SubMeshes = [SubMesh(reader) for _ in range(SubMeshesSize)]
            IndexBufferSize = reader.read_int()
            self.m_IndexBuffer = reader.read_bytes(IndexBufferSize)
            reader.align_stream()
            self.m_VertexData = VertexData(reader)
        else:
            verticesSize = reader.read_int()
            self.vertices = [SpriteVertex(reader) for _ in range(verticesSize)]
            self.indices = reader.read_u_short_array()
            reader.align_stream()

        if version >= (2018,):  # 2018 and up
            self.m_Bindpose = reader.read_matrix_array()
            if version < (2018, 2):  # 2018.2 down
                self.m_SourceSkinSize = reader.read_int()
                self.m_SourceSkin = [BoneWeights4(reader)]

        self.textureRect = reader.read_rectangle_f()
        self.textureRectOffset = reader.read_vector2()
        if version >= (5, 6):  # 5.6 and up
            self.atlasRectOffset = reader.read_vector2()

        self.settingsRaw = SpriteSettings(reader)
        if version >= (4, 5):  # 4.5 and up
            self.uvTransform = reader.read_vector4()

        if version >= (2017,):  # 2017 and up
            self.downscaleMultiplier = reader.read_float()

    def save(self, writer, version):
        self.texture.save(writer)  # Texture2D
        if version >= (5, 2):  # 5.2 and up
            self.alphaTexture.save(writer)  # Texture2D

        if version >= (2019,):  # 2019 and up
            writer.write_int(len(self.secondaryTextures))
            for tex in self.secondaryTextures:
                tex.save(writer)

        if version >= (5, 6):  # 5.6 and up
            writer.write_int(len(self.m_SubMeshes))
            for mesh in self.m_SubMeshes:
                mesh.save(writer, version)
            writer.write_int(len(self.m_IndexBuffer))
            writer.write_bytes(self.m_IndexBuffer)
            writer.align_stream()
            self.m_VertexData.save(writer, version)
        else:
            writer.write_int(len(self.vertices))
            for vertex in self.vertices:
                vertex.save(writer, version)
            writer.write_u_short_array(self.indices)
            writer.align_stream()

        if version >= (2018,):  # 2018 and up
            writer.write_matrix_array(self.m_Bindpose)
            if version < (2018, 2):  # 2018.2 down
                writer.write_int(self.m_SourceSkinSize)
                self.m_SourceSkin[0].save(writer)

        writer.write_rectangle_f(self.textureRect)
        writer.write_vector2(self.textureRectOffset)
        if version >= (5, 6):  # 5.6 and up
            writer.write_vector2(self.atlasRectOffset)

        self.settingsRaw.save(writer)
        if version >= (4, 5):  # 4.5 and up
            writer.write_vector4(self.uvTransform)

        if version >= (2017,):  # 2017 and up
            writer.write_float(self.downscaleMultiplier)


class SpriteBone:
    name: str
    position: tuple
    rotation: tuple
    length: float
    parentId: int

    def __init__(self, reader: EndianBinaryReader) -> None:
        self.name = reader.read_aligned_string()
        self.position = reader.read_vector3()
        self.rotation = reader.read_vector3()
        self.length = reader.read_float()
        self.parentId = reader.read_int()

    def save(self, writer: EndianBinaryWriter):
        writer.write_aligned_string(self.name)
        writer.write_vector3(self.position)
        writer.write_vector3(self.rotation)
        writer.write_float(self.length)
        writer.write_int(self.parentId)
