from enum import IntEnum

from .NamedObject import NamedObject
from ..export.ShaderConverter import export_shader
from ..enums import ShaderCompilerPlatform, ShaderGpuProgramType, SerializedPropertyType
from ..enums import TextureDimension, PassType


class Shader(NamedObject):
    def export(self):
        return export_shader(self)

    def __init__(self, reader):
        super().__init__(reader=reader)
        version = reader.version
        if version >= (5, 5):  # 5.5 and up
            self.m_ParsedForm = SerializedShader(reader)
            self.platforms = [
                ShaderCompilerPlatform(x) for x in reader.read_u_int_array()
            ]

            if version >= (2019, 3):  # 2019.3 and up
                self.offsets = reader.read_u_int_array_array()[0]
                self.compressedLengths = reader.read_u_int_array_array()[0]
                self.decompressedLengths = reader.read_u_int_array_array()[0]
            else:
                self.offsets = reader.read_u_int_array()
                self.compressedLengths = reader.read_u_int_array()
                self.decompressedLengths = reader.read_u_int_array()
            self.compressedBlob = reader.read_bytes(reader.read_int())
        else:
            self.m_Script = reader.read_bytes(reader.read_int())
            reader.align_stream()
            self.m_PathName = reader.read_aligned_string()
            if version >= (5, 3):  # 5.3 - 5.4
                self.decompressedSize = reader.read_u_int()
                self.m_SubProgramBlob = reader.read_bytes(reader.read_int())


class StructParameter:
    def __init__(self, reader):
        self.m_NameIndex = reader.read_int()
        self.m_Index = reader.read_int()
        self.m_ArraySize = reader.read_int()
        self.m_StructSize = reader.read_int()

        numVectorParams = reader.read_int()
        self.m_VectorParams = [VectorParameter(reader) for _ in range(numVectorParams)]

        numMatrixParams = reader.read_int()
        self.m_MatrixParams = [MatrixParameter(reader) for _ in range(numMatrixParams)]


class SamplerParameter:
    def __init__(self, reader):
        self.sampler = reader.read_u_int()
        self.bindPoint = reader.read_int()


class SerializedTextureProperty:
    def __init__(self, reader):
        self.m_DefaultName = reader.read_aligned_string()
        self.m_TexDim = TextureDimension(reader.read_int())


class SerializedProperty:
    def __init__(self, reader):
        self.m_Name = reader.read_aligned_string()
        self.m_Description = reader.read_aligned_string()
        self.m_Attributes = reader.read_string_array()
        self.m_Type = SerializedPropertyType(reader.read_int())
        self.m_Flags = reader.read_u_int()
        self.m_DefValue = reader.read_float_array(4)
        self.m_DefTexture = SerializedTextureProperty(reader)


class SerializedProperties:
    def __init__(self, reader):
        numProps = reader.read_int()
        self.m_Props = [SerializedProperty(reader) for _ in range(numProps)]


class SerializedShaderFloatValue:
    def __init__(self, reader):
        self.val = reader.read_float()
        self.name = reader.read_aligned_string()


class SerializedShaderRTBlendState:
    def __init__(self, reader):
        self.srcBlend = SerializedShaderFloatValue(reader)
        self.destBlend = SerializedShaderFloatValue(reader)
        self.srcBlendAlpha = SerializedShaderFloatValue(reader)
        self.destBlendAlpha = SerializedShaderFloatValue(reader)
        self.blendOp = SerializedShaderFloatValue(reader)
        self.blendOpAlpha = SerializedShaderFloatValue(reader)
        self.colMask = SerializedShaderFloatValue(reader)


class SerializedStencilOp:
    def __init__(self, reader):
        self.pass_ = SerializedShaderFloatValue(reader)
        self.fail = SerializedShaderFloatValue(reader)
        self.zFail = SerializedShaderFloatValue(reader)
        self.comp = SerializedShaderFloatValue(reader)


class SerializedShaderVectorValue:
    def __init__(self, reader):
        self.x = SerializedShaderFloatValue(reader)
        self.y = SerializedShaderFloatValue(reader)
        self.z = SerializedShaderFloatValue(reader)
        self.w = SerializedShaderFloatValue(reader)
        self.name = reader.read_aligned_string()


class FogMode(IntEnum):
    kFogUnknown = (-1,)
    kFogDisabled = (0,)
    kFogLinear = (1,)
    kFogExp = (2,)
    kFogExp2 = (3,)


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

        self.m_Name = reader.read_aligned_string()
        self.rtBlend = [SerializedShaderRTBlendState(reader) for _ in range(8)]
        self.rtSeparateBlend = reader.read_boolean()
        reader.align_stream()
        if version >= (2017, 2):  # 2017.2 and up
            self.zClip = SerializedShaderFloatValue(reader)
        self.zTest = SerializedShaderFloatValue(reader)
        self.zWrite = SerializedShaderFloatValue(reader)
        self.culling = SerializedShaderFloatValue(reader)
        if version >= (2020,):  # 2020.1 and up
            self.conservative = SerializedShaderFloatValue(reader)
        self.offsetFactor = SerializedShaderFloatValue(reader)
        self.offsetUnits = SerializedShaderFloatValue(reader)
        self.alphaToMask = SerializedShaderFloatValue(reader)
        self.stencilOp = SerializedStencilOp(reader)
        self.stencilOpFront = SerializedStencilOp(reader)
        self.stencilOpBack = SerializedStencilOp(reader)
        self.stencilReadMask = SerializedShaderFloatValue(reader)
        self.stencilWriteMask = SerializedShaderFloatValue(reader)
        self.stencilRef = SerializedShaderFloatValue(reader)
        self.fogStart = SerializedShaderFloatValue(reader)
        self.fogEnd = SerializedShaderFloatValue(reader)
        self.fogDensity = SerializedShaderFloatValue(reader)
        self.fogColor = SerializedShaderVectorValue(reader)
        self.fogMode = FogMode(reader.read_int())
        self.gpuProgramID = reader.read_int()
        self.m_Tags = SerializedTagMap(reader)
        self.m_LOD = reader.read_int()
        self.lighting = reader.read_boolean()
        reader.align_stream()


class ShaderBindChannel:
    def __init__(self, reader):
        self.source = reader.read_byte()
        self.target = reader.read_byte()


class ParserBindChannels:
    def __init__(self, reader):
        numChannels = reader.read_int()
        self.m_Channels = [ShaderBindChannel(reader) for _ in range(numChannels)]
        reader.align_stream()
        self.m_SourceMap = reader.read_u_int()


class VectorParameter:
    def __init__(self, reader):
        self.m_NameIndex = reader.read_int()
        self.m_Index = reader.read_int()
        self.m_ArraySize = reader.read_int()
        self.m_Type = reader.read_byte()
        self.m_Dim = reader.read_byte()
        reader.align_stream()


class MatrixParameter:
    def __init__(self, reader):
        self.m_NameIndex = reader.read_int()
        self.m_Index = reader.read_int()
        self.m_ArraySize = reader.read_int()
        self.m_Type = reader.read_byte()
        self.m_RowCount = reader.read_byte()
        reader.align_stream()


class TextureParameter:
    def __init__(self, reader):
        version = reader.version
        self.m_NameIndex = reader.read_int()
        self.m_Index = reader.read_int()
        self.m_SamplerIndex = reader.read_int()
        if version >= (2017, 3):  # 2017.3 and up
            self.m_MultiSampled = reader.read_boolean()
        self.m_Dim = reader.read_byte()
        reader.align_stream()


class BufferBinding:
    def __init__(self, reader):
        self.m_NameIndex = reader.read_int()
        self.m_Index = reader.read_int()
        if reader.version >= (2020,):  # 2020.1 and up
            m_ArraySize = reader.read_int()


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

        self.m_NameIndex = reader.read_int()

        numMatrixParams = reader.read_int()
        self.m_MatrixParams = [MatrixParameter(reader) for _ in range(numMatrixParams)]

        numVectorParams = reader.read_int()
        self.m_VectorParams = [VectorParameter(reader) for _ in range(numVectorParams)]
        if version >= (2017, 3):  # 2017.3 and up
            numStructParams = reader.read_int()
            self.m_StructParams = [
                StructParameter(reader) for _ in range(numStructParams)
            ]
        self.m_Size = reader.read_int()

        if version >= (2021, 1, 4) or (version[0] == 2020 and version >= (2020, 3, 2)):
            self.m_IsPartialCB = reader.read_boolean()
            reader.align_stream()


class UAVParameter:
    def __init__(self, reader):
        self.m_NameIndex = reader.read_int()
        self.m_Index = reader.read_int()
        self.m_OriginalIndex = reader.read_int()


class SerializedProgramParameters:
    def __init__(self, reader):
        numVectorParams = reader.read_int()
        self.m_VectorParams = [VectorParameter(reader) for _ in range(numVectorParams)]

        numMatrixParams = reader.read_int()
        self.m_MatrixParams = [MatrixParameter(reader) for _ in range(numMatrixParams)]

        numTextureParams = reader.read_int()
        self.m_TextureParams = [
            TextureParameter(reader) for _ in range(numTextureParams)
        ]

        numBufferParams = reader.read_int()
        self.m_BufferParams = [BufferBinding(reader) for _ in range(numBufferParams)]

        numConstantBuffers = reader.read_int()
        self.m_ConstantBuffers = [
            ConstantBuffer(reader) for _ in range(numConstantBuffers)
        ]

        numConstantBufferBindings = reader.read_int()
        self.m_ConstantBufferBindings = [
            BufferBinding(reader) for _ in range(numConstantBufferBindings)
        ]

        numUAVParams = reader.read_int()
        self.m_UAVParams = [UAVParameter(reader) for _ in range(numUAVParams)]

        numSamplers = reader.read_int()
        self.m_Samplers = [SamplerParameter(reader) for _ in range(numSamplers)]


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

        self.m_BlobIndex = reader.read_u_int()
        self.m_Channels = ParserBindChannels(reader)

        if (2019,) <= version < (2021, 2):  # 2019 ~2021.1
            self.m_GlobalKeywordIndices = reader.read_u_short_array()
            reader.align_stream()
            self.m_LocalKeywordIndices = reader.read_u_short_array()
            reader.align_stream()
        else:
            self.m_KeywordIndices = reader.read_u_short_array()
            if version >= (2017,):  # 2017 and up
                reader.align_stream()

        self.m_ShaderHardwareTier = reader.read_byte()
        self.m_GpuProgramType = ShaderGpuProgramType(reader.read_byte())
        reader.align_stream()

        if version >= (2021, 1, 4) or (version[0] == 2020 and version >= (2020, 3, 2)):
            self.m_Parameters = SerializedProgramParameters(reader)
        else:
            numVectorParams = reader.read_int()
            self.m_VectorParams = [
                VectorParameter(reader) for _ in range(numVectorParams)
            ]

            numMatrixParams = reader.read_int()
            self.m_MatrixParams = [
                MatrixParameter(reader) for _ in range(numMatrixParams)
            ]

            numTextureParams = reader.read_int()
            self.m_TextureParams = [
                TextureParameter(reader) for _ in range(numTextureParams)
            ]

            numBufferParams = reader.read_int()
            self.m_BufferParams = [
                BufferBinding(reader) for _ in range(numBufferParams)
            ]

            numConstantBuffers = reader.read_int()
            self.m_ConstantBuffers = [
                ConstantBuffer(reader) for _ in range(numConstantBuffers)
            ]

            numConstantBufferBindings = reader.read_int()
            self.m_ConstantBufferBindings = [
                BufferBinding(reader) for _ in range(numConstantBufferBindings)
            ]

            numUAVParams = reader.read_int()
            self.m_UAVParams = [UAVParameter(reader) for _ in range(numUAVParams)]

            if version >= (2017,):  # 2017 and up
                numSamplers = reader.read_int()
                self.m_Samples = [SamplerParameter(reader) for _ in range(numSamplers)]

        if version >= (2017, 2):  # 2017.2 and up
            if version >= (2021,):
                self.m_ShaderRequirements = reader.read_long()
            else:
                self.m_ShaderRequirements = reader.read_int()


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

        numSubPrograms = reader.read_int()
        self.m_SubPrograms = [
            SerializedSubProgram(reader) for _ in range(numSubPrograms)
        ]

        if version >= (2021, 1, 4) or (version[0] == 2020 and version >= (2020, 3, 2)):
            self.m_CommonParameters = SerializedProgramParameters(reader)


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

        if version >= (2020, 2):  # 2020.2 and up
            numEditorDataHash = reader.read_int()
            m_EditorDataHash = [
                reader.read_bytes(16)  # Hash128(reader)
                for _ in range(numEditorDataHash)
            ]
            reader.align_stream()
            m_Platforms = reader.read_byte_array()
            reader.align_stream()
            if version < (2021, 2):
                m_LocalKeywordMask = reader.read_u_short_array()
                reader.align_stream()
                m_GlobalKeywordMask = reader.read_u_short_array()
                reader.align_stream()

        numIndices = reader.read_int()
        self.m_NameIndices = {}
        for _ in range(numIndices):
            key = reader.read_aligned_string()
            self.m_NameIndices[key] = reader.read_int()
        self.m_Type = PassType(reader.read_int())
        self.m_State = SerializedShaderState(reader)
        self.m_ProgramMask = reader.read_u_int()
        self.progVertex = SerializedProgram(reader)
        self.progFragment = SerializedProgram(reader)
        self.progGeometry = SerializedProgram(reader)
        self.progHull = SerializedProgram(reader)
        self.progDomain = SerializedProgram(reader)
        if version >= (2019, 3):  # 2019.3 and up
            self.progRayTracing = SerializedProgram(reader)
        self.m_HasInstancingVariant = reader.read_boolean()
        if version >= (2018,):  # 2018 and up
            self.m_HasProceduralInstancingVariant = reader.read_boolean()
        reader.align_stream()
        self.m_UseName = reader.read_aligned_string()
        self.m_Name = reader.read_aligned_string()
        self.m_TextureName = reader.read_aligned_string()
        self.m_Tags = SerializedTagMap(reader)
        if version >= (2021, 2):
            m_SerializedKeywordStateMask = reader.read_u_short_array()
            reader.align_stream()


class SerializedTagMap:
    def __init__(self, reader):
        numTags = reader.read_int()
        self.tags = {}
        for _ in range(numTags):
            key = reader.read_aligned_string()
            self.tags[key] = reader.read_aligned_string()


class SerializedSubShader:
    def __init__(self, reader):
        numPasses = reader.read_int()
        self.m_Passes = [SerializedPass(reader) for _ in range(numPasses)]
        self.m_Tags = SerializedTagMap(reader)
        self.m_LOD = reader.read_int()


class SerializedShaderDependency:
    def __init__(self, reader):
        self.from_ = reader.read_aligned_string()
        self.to = reader.read_aligned_string()


class SerializedCustomEditorForRenderPipeline:
    def __init__(self, reader):
        self.customEditorName = reader.read_aligned_string()
        self.renderPipelineType = reader.read_aligned_string()


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

        self.m_PropInfo = SerializedProperties(reader)
        numSubShaders = reader.read_int()
        self.m_SubShaders = [SerializedSubShader(reader) for _ in range(numSubShaders)]

        if version >= (2021, 2):
            self.m_KeywordNames = reader.read_string_array()
            self.m_KeywordFlags = reader.read_bytes(reader.read_int())
            reader.align_stream()

        self.m_Name = reader.read_aligned_string()
        self.m_CustomEditorName = reader.read_aligned_string()
        self.m_FallbackName = reader.read_aligned_string()
        numDependencies = reader.read_int()
        self.m_Dependencies = [
            SerializedShaderDependency(reader) for _ in range(numDependencies)
        ]

        if version >= (2021,):
            m_CustomEditorForRenderPipelinesSize = reader.read_int()
            self.m_CustomEditorForRenderPipelines = [
                SerializedCustomEditorForRenderPipeline(reader)
                for _ in range(m_CustomEditorForRenderPipelinesSize)
            ]

        self.m_DisableNoSubshadersMessage = reader.read_boolean()
        reader.align_stream()
