from typing import Tuple
import bpy
from ..utils_debug import debug_warn

from .NEEDLE_components_export import ComponentExportContext, DeferredResolveInfo

class CurrentObject():
    def __init__(self, obj:object, key:str or int):
        self.obj = obj
        self.key = key
        pass
    
    def write(self, val):
        self.obj[self.key] = val

class RegisteredObject():
    def __init__(self, blender_object : object, key : str):
        self.blender_object = blender_object
        self.key = key

class FailedToResolve: 
    pass

# similar to how we resolve pointers at runtime
# key: json pointer base
# value: array name that contains the objects
pointerArrayDict = {
    "/materials/" : "materials",
    "/nodes/" : "nodes",
    "/meshes/" : "meshes",
    "/textures/" : "textures",
    "/images/" : "images",
}

class ReferenceResolver():
    def __init__(self, dict : ComponentExportContext):
        # the list of resources that have been exported so far
        self.__context = dict

    def tryGetJsonPointerBase(self, obj: object, type:str = None) -> None or str:
        # override json pointer type because textures are currently in blender "images" (because we create an image picker for a texture field)
        if type is not None:
            if type == "texture" or type == "tex" and isinstance(obj, bpy.types.Image):
                return "/textures/"
        
        if isinstance(obj, bpy.types.Material):
            return "/materials/"
        elif isinstance(obj, bpy.types.Object):
            return "/nodes/"
        elif isinstance(obj, bpy.types.Mesh):
            return "/meshes/"
        elif isinstance(obj, bpy.types.Texture):
            return "/textures/"
        elif isinstance(obj, bpy.types.Image):
            return "/images/"
        return None
    
    def tryResolve(self, pathOrObject: str or object, key:str = None, currentObject:CurrentObject = None) -> None or FailedToResolve or RegisteredObject or DeferredResolveInfo:
        """you can give this method a json pointer and it will try to resolve it to a previously exported object\n
        or you can give it an object and it will try to add it to be resolved later (returning a DeferredResolveInfo which should be registered to the export context)
        """

            
        if pathOrObject is None: return None

        # try resolve a path to a object
        if isinstance(pathOrObject, str) and pathOrObject.startswith("/"):

            # here we handle references to extensions
            # to import extensions we first need to have it registered in our blender importer
            # then we can call resolveJsonPointerImport on the extension
            if pathOrObject.startswith("/extensions/"):
                parts = pathOrObject.split("/")
                extensionName = parts[2]
                # check if the extension is known / exists
                ext = self.__context.importer.getExtension(extensionName)
                if ext:
                    # we have an extension - now we call resolveJsonPointerImport on it
                    # this method SHOULD exist if it derives from extensionbase
                    if hasattr(ext, "resolveJsonPointerImport"):
                        gltfOrDeferredObjectExport = ext.resolveJsonPointerImport(pathOrObject)
                        if gltfOrDeferredObjectExport is not None:
                            return RegisteredObject(gltfOrDeferredObjectExport, pathOrObject)
                        else:
                            debug_warn("ExtensionHandler exists \"" + extensionName + "\" but resolveJsonPointerImport is not a method(?)")
                            return FailedToResolve
                    return FailedToResolve
                else:
                    debug_warn("Failed to resolve reference " + pathOrObject)
                    return FailedToResolve
                
            else:
                for key in pointerArrayDict:
                    if pathOrObject.startswith(key):
                        index = int(pathOrObject[len(key):])
                        fieldName = pointerArrayDict[key]
                        # e.g. data.materials / data.nodes / data.textures
                        dataArray = getattr(self.__context, fieldName, None)
                        if dataArray is not None and index >= 0 and index < len(dataArray):
                            return RegisteredObject(dataArray[index], key)# (dataArray[index], key)
                        else:
                            return FailedToResolve

            # check if its a component reference { guid: <string> } or a node reference { node: <int> }
        # elif isinstance(pathOrObject, dict):

            # we can ignore guids because when importing components we have two options
            # 1) the component is known in which case the serialized guid is set on the blender component (and subsequently re-exported again since it must not change)
            # 2) the component is unknown in which case the guid will just be frozen as a string and re-exported as is
            # IF we would have to handle component references somehow this is how you would detect them:
            # if "guid" in pathOrObject:
            #     print("guid", pathOrObject["guid"])

            # the reference to some blender object
            # if "node" in pathOrObject:
            #     print("node", pathOrObject["node"])

        # try resolve a object to a path
        if key is not None and isinstance(pathOrObject, object):

            if key.startswith("/nodes/") and currentObject is not None:
                print("> Defer pointer for " + pathOrObject.name)
                self.__context.registerNodeReference(pathOrObject, currentObject.obj, currentObject.key)
                return

            if key in pointerArrayDict and getattr(self.__context, "exportAsset", None) is not None:
                # we need to always defer pointer resolving
                # since otherwise we can not be sure of the order of elements in the exported json:
                fieldName = pointerArrayDict[key]
                gltf_key = fieldName
                gltfOrDeferredObjectExport = self.__context.exportAsset(pathOrObject, gltf_key)
                if gltfOrDeferredObjectExport:
                    deferredResolve = DeferredResolveInfo(gltf_key, key, gltfOrDeferredObjectExport)
                    print("> Defer pointer for " + pathOrObject.name)
                    if currentObject:
                        self.__context.register(deferredResolve, currentObject.obj, currentObject.key)
                    return deferredResolve
                # we can not just resolve the pointer the asset was already exported
                # because if we add new assets via extension the order might change
                # so we need to defer the resolving until the export is done
                    # arr = getattr(self.data, fieldName, None)
                    # if arr is not None:
                    #     for i in range(len(arr)):
                    #         obj = arr[i]
                    #         if pathOrObject == obj:
                                # return key + str(i)

            return FailedToResolve

        return None
            

