import io
import ntpath

from . import bin_ops
from . import read_ascii_xps
from . import xps_const
from . import xps_types


def flagName(flag):
    flagList = {
        0: xps_const.BACK_FACE_CULLING,
        1: xps_const.ALWAYS_FORCE_CULLING,
        2: xps_const.MODEL_CAST_SHADOWS,
        3: xps_const.TANGENT_SPACE_RED,
        4: xps_const.TANGENT_SPACE_GREEN,
        5: xps_const.TANGENT_SPACE_BLUE,
        6: xps_const.GLOSS,
        7: xps_const.HAS_BONE_DIRECTIONS,
    }
    return flagList.get(flag, flag)


def flagsDefault():
    flags = {
        xps_const.BACK_FACE_CULLING: False,
        xps_const.ALWAYS_FORCE_CULLING: False,
        xps_const.MODEL_CAST_SHADOWS: True,
        xps_const.TANGENT_SPACE_RED: 0,  # Straight X channel
        xps_const.TANGENT_SPACE_GREEN: 1,  # Invert Y channel
        xps_const.TANGENT_SPACE_BLUE: 0,  # Straight Z channel
        xps_const.GLOSS: 10,
        xps_const.HAS_BONE_DIRECTIONS: False,
    }
    return flags


def flagValue(flag, value):
    # Flags
    # 00: Backface culling
    # 01: Always force culling
    # 02: Model cast shadows
    # 06: Save current bump specular gloss

    if flag in (0, 1, 2, 6, 7):
        return bool(value)
    # Flags
    # 03: X space
    # 04: Y space
    # 05: Z space
    elif flag in (3, 4, 5):
        return (value % 2)
    else:
        return value


def intToCoords(flag):
    flagValue = {
        0: '+',
        1: '-',
    }
    return flagValue.get(flag, 'Uk')


def printNormalMapSwizzel(tangentSpaceRed, tangentSpaceGreen, tangentSpaceBlue):
    # Default XPS NormalMapTangentSpace == 0 1 0 == X+ Y- Z+
    print('Tangent Space Normal Map Swizzel Coordinates:')
    print('X{} Y{} Z{}'.format(intToCoords(tangentSpaceRed), intToCoords(tangentSpaceGreen), intToCoords(tangentSpaceBlue)))
    print('')


def readFilesString(file):
    lengthByte2 = 0

    lengthByte1 = bin_ops.readByte(file)

    if (lengthByte1 >= xps_const.LIMIT):
        lengthByte2 = bin_ops.readByte(file)
    length = (lengthByte1 % xps_const.LIMIT) + (lengthByte2 * xps_const.LIMIT)

    string = bin_ops.readString(file, length)
    return string


def readVertexColor(file):
    r = bin_ops.readByte(file)
    g = bin_ops.readByte(file)
    b = bin_ops.readByte(file)
    a = bin_ops.readByte(file)
    vertexColor = [r, g, b, a]
    return vertexColor


def readUvVert(file):
    x = bin_ops.readSingle(file)  # X pos
    y = bin_ops.readSingle(file)  # Y pos
    coords = [x, y]
    return coords


def readXYZ(file):
    x = bin_ops.readSingle(file)  # X pos
    y = bin_ops.readSingle(file)  # Y pos
    z = bin_ops.readSingle(file)  # Z pos
    coords = [x, y, z]
    return coords


def read4Float(file):
    x = bin_ops.readSingle(file)
    y = bin_ops.readSingle(file)
    z = bin_ops.readSingle(file)
    w = bin_ops.readSingle(file)
    coords = [x, y, z, w]
    return coords


def read4Int16(file):
    r = bin_ops.readInt16(file)
    g = bin_ops.readInt16(file)
    b = bin_ops.readInt16(file)
    a = bin_ops.readInt16(file)
    vertexColor = [r, g, b, a]
    return vertexColor


def readTriIdxs(file):
    face1 = bin_ops.readUInt32(file)
    face2 = bin_ops.readUInt32(file)
    face3 = bin_ops.readUInt32(file)
    faceLoop = [face1, face2, face3]
    return faceLoop


def readHeader(file):
    xpsHeader = xps_types.XpsHeader()
    flags = flagsDefault()

    # MagicNumber
    magic_number = bin_ops.readUInt32(file)
    # XPS Version
    version_mayor = bin_ops.readUInt16(file)
    version_minor = bin_ops.readUInt16(file)
    # XNAaral Name
    xna_aral = readFilesString(file)
    # Settings Length
    settingsLen = bin_ops.readUInt32(file)
    # MachineName
    machineName = readFilesString(file)
    # UserName
    userName = readFilesString(file)
    # File-->File
    filesString = readFilesString(file)
    xpsPoseData = None

    # print('*'*80)
    hasTangent = bin_ops.hasTangentVersion(version_mayor, version_minor)
    if (hasTangent):
        # print('OLD Format')
        settingsStream = io.BytesIO(file.read(settingsLen * 4))
    else:
        # print('NEW Format')
        valuesRead = 0
        hash = bin_ops.readUInt32(file)
        valuesRead += 1 * 4
        items = bin_ops.readUInt32(file)
        valuesRead += 1 * 4
        # print('hash', hash)
        # print('items', items)
        for i in range(items):
            # print('valuesRead', valuesRead)
            optType = bin_ops.readUInt32(file)
            valuesRead += 1 * 4
            optcount = bin_ops.readUInt32(file)
            valuesRead += 1 * 4
            optInfo = bin_ops.readUInt32(file)
            valuesRead += 1 * 4

            # print('------')
            # print('count',i)
            # print('optType',optType)
            # print('optcount',optcount)
            # print('optInfo',optInfo)

            if (optType == 0):
                # print('Read None')
                readNone(file, optcount)
                valuesRead += optcount * 2
            elif (optType == 1):
                # print('Read Pose')
                xpsPoseData = readDefaultPose(file, optcount, optInfo)
                readCount = bin_ops.roundToMultiple(optcount, xps_const.ROUND_MULTIPLE)
                valuesRead += readCount
            elif (optType == 2):
                # print('Read Flags')
                flags = readFlags(file, optcount)
                valuesRead += optcount * 2 * 4
            else:
                # print('Read Waste')
                loopStart = valuesRead // 4
                loopFinish = settingsLen
                # print (loopStart, loopFinish)
                for j in range(loopStart, loopFinish):
                    # print('waste',j - loopStart)
                    waste = bin_ops.readUInt32(file)

    xpsHeader.magic_number = magic_number
    xpsHeader.version_mayor = version_mayor
    xpsHeader.version_minor = version_minor
    xpsHeader.xna_aral = xna_aral
    xpsHeader.settingsLen = settingsLen
    xpsHeader.machine = machineName
    xpsHeader.user = userName
    xpsHeader.files = filesString
    xpsHeader.pose = xpsPoseData
    xpsHeader.flags = flags
    return xpsHeader


def findHeader(file):
    header = None

    # Check for MAGIC_NUMBER
    number = bin_ops.readUInt32(file)
    file.seek(0)

    if (number == xps_const.MAGIC_NUMBER):
        print('Header Found')
        header = readHeader(file)

    # logHeader(header)
    return header


def readNone(file, optcount):
    for i in range(optcount):
        waste = bin_ops.readUInt32(file)


def readFlags(file, optcount):
    flags = {}
    for i in range(optcount):
        flag = bin_ops.readUInt32(file)
        value = bin_ops.readUInt32(file)
        flags[flagName(flag)] = flagValue(flag, value)
    printNormalMapSwizzel(flags[flagName(3)], flags[flagName(4)], flags[flagName(5)])
    return flags


def logHeader(xpsHeader):
    print("MAGIX:", xpsHeader.magic_number)
    print('VER MAYOR:', xpsHeader.version_mayor)
    print('VER MINOR:', xpsHeader.version_minor)
    print('NAME:', xpsHeader.xna_aral)
    print('SETTINGS LEN:', xpsHeader.settingsLen)
    print('MACHINE:', xpsHeader.machine)
    print('USR:', xpsHeader.user)
    print('FILES:', xpsHeader.files)
    print('SETTING:', xpsHeader.settings)
    print('DEFAULT POSE:', xpsHeader.pose)


def readBones(file, header):
    bones = []
    # Bone Count
    boneCount = bin_ops.readUInt32(file)

    for boneId in range(boneCount):
        boneName = readFilesString(file)
        parentId = bin_ops.readInt16(file)
        coords = readXYZ(file)

        xpsBone = xps_types.XpsBone(boneId, boneName, coords, parentId)
        bones.append(xpsBone)
    return bones


def readMeshes(file, xpsHeader, hasBones):
    meshes = []
    meshCount = bin_ops.readUInt32(file)

    hasHeader = bool(xpsHeader)

    verMayor = xpsHeader.version_mayor if hasHeader else 0
    verMinor = xpsHeader.version_minor if hasHeader else 0

    hasTangent = bin_ops.hasTangentVersion(verMayor, verMinor, hasHeader)
    hasVariableWeights = bin_ops.hasVariableWeights(verMayor, verMinor, hasHeader)

    for meshId in range(meshCount):
        # Name
        meshName = readFilesString(file)
        if not meshName:
            meshName = 'unnamed'
        # print('Mesh Name:', meshName)
        # uv Count
        uvLayerCount = bin_ops.readUInt32(file)
        # Textures
        textures = []
        textureCount = bin_ops.readUInt32(file)
        for texId in range(textureCount):
            textureFile = ntpath.basename(readFilesString(file))
            # print('Texture file', textureFile)
            uvLayerId = bin_ops.readUInt32(file)

            xpsTexture = xps_types.XpsTexture(texId, textureFile, uvLayerId)
            textures.append(xpsTexture)

        # Vertices
        vertex = []
        vertexCount = bin_ops.readUInt32(file)

        for vertexId in range(vertexCount):
            coord = readXYZ(file)
            normal = readXYZ(file)
            vertexColor = readVertexColor(file)

            uvs = []
            for uvLayerId in range(uvLayerCount):
                uvVert = readUvVert(file)
                uvs.append(uvVert)
                if hasTangent:
                    tangent = read4Float(file)

            boneWeights = []
            if hasBones:
                # if cero bones dont have weights to read

                boneIdx = []
                boneWeight = []
                if hasVariableWeights:
                    weightsCount = bin_ops.readInt16(file)
                else:
                    weightsCount = 4

                for x in range(weightsCount):
                    boneIdx.append(bin_ops.readInt16(file))
                for x in range(weightsCount):
                    boneWeight.append(bin_ops.readSingle(file))

                for idx in range(len(boneIdx)):
                    boneWeights.append(
                        xps_types.BoneWeight(boneIdx[idx], boneWeight[idx]))
            xpsVertex = xps_types.XpsVertex(
                vertexId, coord, normal, vertexColor, uvs, boneWeights)
            vertex.append(xpsVertex)

        # Faces
        faces = []
        triCount = bin_ops.readUInt32(file)
        for i in range(triCount):
            triIdxs = readTriIdxs(file)
            faces.append(triIdxs)
        xpsMesh = xps_types.XpsMesh(
            meshName, textures, vertex, faces, uvLayerCount)
        meshes.append(xpsMesh)
    return meshes


def readIoStream(filename):
    with open(filename, "rb") as a_file:
        ioStream = io.BytesIO(a_file.read())
    return ioStream


def readXpsModel(filename):
    print('File:', filename)

    ioStream = readIoStream(filename)
    print('Reading Header')
    xpsHeader = findHeader(ioStream)
    print('Reading Bones')
    bones = readBones(ioStream, xpsHeader)
    hasBones = bool(bones)
    print('Read', len(bones), 'Bones')
    print('Reading Meshes')
    meshes = readMeshes(ioStream, xpsHeader, hasBones)
    print('Read', len(meshes), 'Meshes')

    xpsData = xps_types.XpsData(xpsHeader, bones, meshes)
    return xpsData


def readDefaultPose(file, poseLenghtUnround, poseBones):
    # print('Import Pose')
    poseBytes = b''
    if poseLenghtUnround:
        for i in range(0, poseBones):
            poseBytes += file.readline()

    poseLenght = bin_ops.roundToMultiple(
        poseLenghtUnround, xps_const.ROUND_MULTIPLE)
    emptyBytes = poseLenght - poseLenghtUnround
    file.read(emptyBytes)
    poseString = bin_ops.decodeBytes(poseBytes)
    bonesPose = read_ascii_xps.poseData(poseString)
    return bonesPose


if __name__ == "__main__":
    readfilename = r'G:\3DModeling\XNALara\XNALara_XPS\Young Samus\Generic_Item.mesh'

    print('----READ START----')
    xpsData = readXpsModel(readfilename)
    print('----READ END----')
