
from ..panels_components import getBlenderObjectPropertyKey, getComponentKey
from ..component_utils import findByGuid
from .reference_resolver import ReferenceResolver, FailedToResolve, RegisteredObject
from ..component_registry import getScheme
import bpy


class ComponentImportContext:
    def __init__(self, importer):
        self.importer = importer
        self.gltf = None
        self.resolver : ReferenceResolver = None
        self.blender_object = None
        self.component_name = None
        self.component = None
        self.component_data = None
        self.component_schema = None

        self.nodes = None
        self.materials = None

        self.current_field_name = None
        self.current_field_value = None
        self.current_index = None
        pass


globally_registered_deserializers = []
# currently active deserializer stack
currently_deserializing = []

class BaseComponentDeserializer:

    def tryDeserialize(context : ComponentImportContext) -> any:
        pass

def registerDeserializer(deserializer : BaseComponentDeserializer):
    globally_registered_deserializers.append(deserializer)

def deserializeComponent(context : ComponentImportContext, deserializers:list = None) -> any:
    if deserializers is None:
        deserializers = globally_registered_deserializers
    for deserializer in deserializers:
        # ensure that we dont get into an endless loop
        if deserializer in currently_deserializing:
            continue
        try:
            currently_deserializing.append(deserializer)
            res = deserializer.tryDeserialize(context)
            if res != None:
                return res
        finally:
            currently_deserializing.remove(deserializer)
    return None


component_property_deserializers = []

class CoreComponentDeserializer(BaseComponentDeserializer):

    def tryDeserialize(self, context : ComponentImportContext) -> bool:
        if context.component_data is None: return

        componentData = context.component_data
        component = context.component
        schema = context.component_schema
        for key in componentData.keys():
            value = componentData[key]
            if hasattr(component, key) == False:
                continue
            try:
                context.current_field_name = key
                context.current_field_value = value
                context.current_index = None

                isArray = False

                # if value is dict
                if isinstance(value, dict):
                    # invoke with component property deserializers
                    res = deserializeComponent(context, component_property_deserializers)
                    value = res
                    
                elif isinstance(value, list):
                    isArray = True
                    context.current_index = 0
                    for i in range(len(value)):
                        item = value[i]
                        context.current_field_value = item
                        # invoke with component property deserializers
                        res = deserializeComponent(context, component_property_deserializers)
                        if res != None:
                            value[i] = res
                        context.current_index += 1
                elif isinstance(value, str):
                    res = deserializeComponent(context, component_property_deserializers)
                    if res != None:
                        value = res
                
                context.current_field_value = value
                context.current_index = None
                
                if key in schema:
                    schemaProperty = schema[key]
                    if schemaProperty and "type" in schemaProperty:
                        type = schemaProperty["type"]
                        if type == "enum":
                            value = str(value)

                
                if value == FailedToResolve:
                    value = None

                if value is None:
                    # if the value is not resolved but the serialized data is a component reference:
                    dataValue = componentData[key]
                    pass

                if isArray:
                    # array entries are wrapped in a custom property which contains a value property
                    objectList = getattr(component, key)
                    array = objectList.array
                    array.clear()
                    for i in range(len(value)):
                        newEntry = array.add()
                        val = value[i]
                        if val == FailedToResolve:
                            val = None
                        newEntry.value = val
                else:
                    # if the target is a component reference:
                    _compValue = getattr(component, key)
                    if _compValue is not None and hasattr(_compValue, "obj") and hasattr(_compValue, "typeName") and getattr(_compValue, "typeName"):
                        _compValue.obj = value[getBlenderObjectPropertyKey()]
                        fullName = getComponentKey(value.name)
                        _compValue.component = fullName
                    else:
                        # if its a primitive value just write it         
                        setattr(component, key, value)

            except Exception as e:
                print("ERR: Failed to deserialize component", context.component_name, key, value, e)
                import traceback
                print(traceback.format_exc())
            finally:
                context.current_field_name = None
                context.current_field_value = None
                context.current_index = None
registerDeserializer(CoreComponentDeserializer())


class ComponentPropertyDeserializer(BaseComponentDeserializer):

    priority : int = 0

    def __init__(self) -> None:
        super().__init__()
        component_property_deserializers.append(self)
        component_property_deserializers.sort(key=lambda x: x.priority, reverse=True)

class ColorDeserializer(ComponentPropertyDeserializer):
    def tryDeserialize(self, context : ComponentImportContext) -> bool:
        value = context.current_field_value
        if value is None: return
        if not isinstance(value, dict): return None
        # fieldName = context.current_field_name
        if "r" in value and "g" in value and "b" in value:
            # TODO: figure out how to build alpha property fields
            # if "a" in value:
            #     res = (value["r"], value["g"], value["b"], value["a"])
            # else:
            res = (value["r"], value["g"], value["b"])
            return res
ColorDeserializer()

class ObjectReferenceDeserializer(ComponentPropertyDeserializer):
    def tryDeserialize(self, context: ComponentImportContext):
        value = context.current_field_value
        if value is None: return
        if isinstance(value, dict):
            if "node" in value:
                index = value["node"]
                if isinstance(index, int):
                    obj = context.nodes[index]
                    if obj is not None:
                        return obj
                    else:
                        print("ERR: Failed to resolve object reference", index, "in", context.component_name)
                        return FailedToResolve
        elif isinstance(value, str) and value.startswith("/nodes/"):
            index = int(value[len("/nodes/"):])
            obj = context.nodes[index]
            if obj is not None:
                return obj
            else:
                print("ERR: Failed to resolve object reference", index, "in", context.component_name)
                return FailedToResolve

        
ObjectReferenceDeserializer()

class ComponentReferenceDeserializer(ComponentPropertyDeserializer):
    def tryDeserialize(self, context: ComponentImportContext):
        value = context.current_field_value
        if value is None: return
        if not isinstance(value, dict): return None
        keysInDict = value.keys()
        if len(keysInDict) != 1: return None
        if "guid" in value and isinstance(value["guid"], str):
            # resolve reference to either object or component
            res = findByGuid(context.nodes, value["guid"])
            if res is not None:
                return res
ComponentReferenceDeserializer()

class JsonPointerDeserializer(ComponentPropertyDeserializer):
    def tryDeserialize(self, context: ComponentImportContext):
        value = context.current_field_value
        if value is None: return
        if not isinstance(value, str): return None
        res = context.resolver.tryResolve(value)
        if res == FailedToResolve:
            return res
        elif isinstance(res, RegisteredObject):
            return res.blender_object
JsonPointerDeserializer()



class UnresolvedComponentReference(ComponentPropertyDeserializer):
    """If a component could not be resolved we want to serialize the reference as a string to be able to export it again"""

    def __init__(self) -> None:
        # make sure this runs at the very end
        self.priority = -100_000
        super().__init__()
    
    def tryDeserialize(self, context: ComponentImportContext):
        value = context.current_field_value
        if value is None: return
        if not isinstance(value, dict): return None
        if "guid" in value and isinstance(value["guid"], str):
            # the value is a component reference but it could not be resolved by any of the previous serializers
            return str(value)
        return
UnresolvedComponentReference()