import bpy
from .extensionbase import ExtensionBase
from .NEEDLE_components_export import ComponentExportContext
from ..utils_debug import debug_log
from .skybox_utils import copyFileFromTo, tryGetViewportSkyboxSettings
from ..panels_components import getComponentKey
from ..component_utils import getComponent
from ..lightmapping.lightmapping import isLightmapped, modifyLightmapAlpha
from ..blender_materials import assignColorToImage, createMaterialWithTexture, loadImage, flipImage, createImageWithColor, applyFactor
from ..blender_objects import createQuad, deleteObjectsRecursive, markDeletable
from ..utils_gltf import asTexturePointer




def register():
    pass

def unregister():
    pass

def getSkyboxImage(blender_scene, texture_directory:str) -> bpy.types.Image | None:
    skyboxInfo = tryGetViewportSkyboxSettings()
    res = {
        "environmentImagePath": None,
        "backgroundImage": None,
        "backgroundImage_Original": None,
    }
    if skyboxInfo:
        if skyboxInfo.environment_image:
            src_path = skyboxInfo.environment_image.path
            if src_path is not None and len(src_path) > 0:
                debug_log("Using viewport skybox", src_path)
                target_path = texture_directory + "/" + skyboxInfo.environment_image.name
                copyFileFromTo(src_path, target_path)
                res["environmentImagePath"] = skyboxInfo.environment_image.name
            elif skyboxInfo.environment_image.image is not None:
                # we copy it here once because it will later be appended to the created objects and then destroyed
                # if we dont copy it then the actual scene image will be destroyed which isn't what we want
                image = skyboxInfo.environment_image.image
                copy = image.copy()
                res["backgroundImage"] = copy
                # we save the original name here because down below we compare the gltf image by name
                res["backgroundImage_Original"] = image
                return res;

        if skyboxInfo.background_color:
            color = skyboxInfo.background_color
            images = bpy.data.images
            backgroundImage = None
            if "NEEDLE_background_image" in images:
                backgroundImage = images["NEEDLE_background_image"]
            else:
                backgroundImage = images.new(name="NEEDLE_background_image", width=64, height=64, float_buffer=True)
            backgroundImage.colorspace_settings.is_data = True
            # we need to apply the current scene color view transform to the color (otherwise it will look different)
            assignColorToImage(backgroundImage, color, intensity=1.0, apply_view_transform=True)
            res["backgroundImage"] = backgroundImage
            res["backgroundImage_Original"] = backgroundImage
    return res

    # world = blender_scene.world
    # if world is not None:
    #     return (createImageWithColor(world.color, 64, 64, "skybox_color"), False)




def getLightmapImages():
    lightmapImages = []
    # find images
    for image in bpy.data.images:
        if image and image.name.startswith("NEEDLE_lightmap_image"):
            lightmapImages.append(image)
    return lightmapImages


# lastSkyboxImage = None
# skyboxImage : bpy.types.Image = None

# def ensureSkyboxImage():
#     global skyboxImage, lastSkyboxImage
#     image = tryGetSkyboxPath(bpy.context.scene)
#     if image != None and (image != lastSkyboxImage or skyboxImage == None):
#         print("IMAGE CHANGED", image)
#         lastSkyboxImage = image
#         skyboxImage = flipImage(loadImage(image))
#     return 1

# bpy.app.timers.register(ensureSkyboxImage, persistent=False, first_interval=1.0)

class NEEDLE_lightmaps(ExtensionBase):
    __createdObjects = []

    # __skyboxMaterial: bpy.types.Material or None
    __skyboxPath: str | None
    __skybox_GltfTexture: object | None

    __lightmapMaterials = []
    __lightmapTextureExtensions = []
    
    def __init__(self, ext) -> None:
        self.ext = ext
        self.__createdObjects.clear()
        self.__skybox_GltfTexture = None
        # self.__skyboxMaterial = None
        self.__skyboxPath = None 
        self.__skyboxImage = None

        self.__lightmapMaterials.clear()
        self.__lightmapTextureExtensions.clear()

    def gather_asset_hook(self, gltf2_asset, export_settings):
        if self.exportContext.exportOnlyComponentsInScene:
            return
        # we export the skybox image in the same directory as the gltf
        # that way we don't have to worry about relative paths
        # it's the lazy way for now
        blender_scene = bpy.context.scene
        res = getSkyboxImage(blender_scene, export_settings["gltf_filedirectory"])
        if res is not None:
            if "environmentImagePath" in res:
                self.__skyboxPath = res["environmentImagePath"]
            if "backgroundImage" in res:
                self.componentExportContext = ComponentExportContext(self.getExportSettings())
                self.__skyboxImage = res["backgroundImage_Original"]
                self.__createSkybox(res["backgroundImage"], True)
                # this didnt work - the image wasnt included in the gltf:
                # res = self.componentExportContext.exportAsset(res["backgroundColor"], "images", allowDeferExport=False)
                # if res:
                #     self.__skyboxGltfImage = res
                #     # image = res["backgroundColor"]
                #     # gltf_image = self.componentExportContext.exportAsset(image, "textures")
                #     debug_log("Exporting color as texture is not yet supported")
        # check if any object in the scene is lightmapped:
        hasAnyLightmappedObject = False
        for obj in bpy.data.objects:
            hasAnyLightmappedObject = isLightmapped(obj)
            if hasAnyLightmappedObject:
                print("Found lightmapped object", obj.name)
                break
            pass
        if hasAnyLightmappedObject:
            self.__createLightmap()
        pass

    def __createSkybox(self, skyboxImage, needs_flip):
        # if the skyboxImage is a string we assume its a path
        if isinstance(skyboxImage, str):
            self.__skyboxPath = skyboxImage
            pass
        # this is the fallback path that we used earlier where we exported the skybox as part of the glTF
        # this shouldnt be used anymore but we keep it for now
        elif isinstance(skyboxImage, bpy.types.Image):
            debug_log("Legacy skybox export: Creating skybox from image", skyboxImage.name)
            if needs_flip:
                flipImage(skyboxImage)
            self.__skyboxMaterial = createMaterialWithTexture("skybox", skyboxImage)
            quad = createQuad(self.__skyboxMaterial)
            quad.name = "NEEDLE__skybox"
            quad.scale = (0, 0, 0)
            quad.hide_render = True
            self.__createdObjects.append(quad)
    
    def __createLightmap(self):
        lightmaps = getLightmapImages()
        for lightmap in lightmaps:
            # TODO: this could be saved in the blend file or on disc to save processing because it's quite slow
            debug_log("Export lightmap image:", lightmap.name)
            markDeletable(lightmap, False)
            # create a new image with the lightmap pixels
            lightmapImage = bpy.data.images.new(lightmap.name + "-clone", lightmap.size[0], lightmap.size[1], alpha=True, float_buffer=True)
            lightmapImage.generated_type = 'BLANK'
            # https://docs.blender.org/api/current/bpy.types.Image.html#bpy.types.Image.alpha_mode
            lightmapImage.alpha_mode = 'CHANNEL_PACKED'
            lightmapImage.colorspace_settings.name = lightmap.colorspace_settings.name
            # lightmapImage.pixels = lightmap.pixels
            modifyLightmapAlpha(lightmapImage, lightmap.pixels)
            lightmapImage.name = "Lightmap-" + str(len(self.__lightmapMaterials))
            lightmapMaterial = createMaterialWithTexture("lightmap", lightmapImage)
            self.__lightmapMaterials.append(lightmapMaterial)
            # create quad
            quad = createQuad(lightmapMaterial)
            quad.name = "NEEDLE__lightmap"
            quad.scale = (0, 0, 0)
            quad.location = (0, .001, .01)
            quad.hide_render = True
            self.__createdObjects.append(quad)
        pass


    def gather_texture_hook(self, gltf2_texture, blender_shader_sockets, export_settings):
        # if self.__skyboxMaterial:
        #     for socket in blender_shader_sockets:
        #         node = socket.node
        #         # check if socket is skybox material
        #         for skyboxNode in self.__skyboxMaterial.node_tree.nodes:
        #             if skyboxNode == node:
        #                 self.__skyboxTextureExt = gltf2_texture
        #                 return

        if len(self.__lightmapMaterials) > 0:
            for socket in blender_shader_sockets:
                # in Blender 4.1 this seems to have changed and the socket is now nested inside another object
                if hasattr(socket, "socket") != False:
                    socket = socket.socket
                node = socket.node
                for lightmapMaterial in self.__lightmapMaterials:
                    for lightmapNode in lightmapMaterial.node_tree.nodes:
                        if lightmapNode == node:
                            self.__lightmapTextureExtensions.append(gltf2_texture)
                            return
        
        if self.__skyboxImage and not self.__skybox_GltfTexture:
            if gltf2_texture.source and gltf2_texture.source.name == self.__skyboxImage.name: 
                self.__skybox_GltfTexture = gltf2_texture
            pass
        pass

    def gather_gltf_extensions_hook(self, gltf2_object, export_settings):

        textures = []

        # if self.__skyboxGltfImage:
        #     if not gltf2_object.images:
        #         gltf2_object.images = []
        #     gltf2_object.images.append(self.__skyboxGltfImage)
        
        # collect SKYBOX
        if self.__skyboxPath:
            relative_path = self.__skyboxPath
            # file_dir = export_settings["gltf_filedirectory"]
            # if file_dir is not None:
            #     relative_path = bpy.path.relpath(relative_path, file_di6r)
            texture = {
                "pointer": relative_path,
                "type": 1,
                "index": 0,
            }
            textures.append(texture)
            pass
        elif self.__skybox_GltfTexture:
            index = -1
            for i in range(len(gltf2_object.textures)):
                if gltf2_object.textures[i] == self.__skybox_GltfTexture:
                    index = i
                    break
            if index >= 0:
                texture = {
                    "pointer": asTexturePointer(index),
                    "type": 1,
                    "index": 0,
                }
                textures.append(texture)

        # collect LIGHTMAPS
        lightmapIndex = 0
        for ext in self.__lightmapTextureExtensions:
            # find index of texture
            index = -1
            for i in range(len(gltf2_object.textures)):
                if gltf2_object.textures[i] == ext:
                    index = i
                    break
            if index < 0:
                continue
            texture = {
                "pointer": asTexturePointer(index),
                "type": 0,
                "index": lightmapIndex,
            }
            lightmapIndex = lightmapIndex + 1
            textures.append(texture)

        if len(textures) == 0:
            return

        gltf2_object.extensions['NEEDLE_lightmaps'] = {"textures": textures}
        gltf2_object.extensions_used.append("NEEDLE_lightmaps")

        gltf2_object.extensions["NEEDLE_lighting_settings"] = {
            "ambientIntensity": 1,
            "ambientLight": [1, 1, 1, 1],
            "ambientMode": 0,
            "environmentReflectionSource": 0,
        }
        gltf2_object.extensions_used.append("NEEDLE_lighting_settings")
        # gltf2_object.extensions_used.append("KHR_texture_transform")
        pass

    def onAfterExport(self, exportContext):
        deleteObjectsRecursive(self.__createdObjects)
        pass
