
import os
import bpy
from .settings_scene import tryGetMainSceneSettings
from .utils import isWindows
from .panels_common import DrawContext, currentDrawContext
from . import component_registry
from .utils_icons import getPreviewIcon

panelTypes = []

class validationInfo:
    pollFunction: None

panelPropertyInfos = dict()
propDict = dict()

# def tryGetPropInfo(prop):

def componentPrefix():
    return "NEEDLE_component_"

def getComponentKey(typeName):
    return "NEEDLE_component_" + typeName

def getBlenderObjectPropertyKey():
    return "internal___blender_object"

def getIsActive(comp):
    if comp is None: 
        return False
    if not hasattr(comp, "internal___active"):
        return False
    return getattr(comp, "internal___active")

def setIsActive(comp, active):
    if hasattr(comp, "internal___active"):
        setattr(comp, "internal___active", active)

def __needsFilePicker(type:str):
    return type == "videoclip" or type == "audioclip" or type == "imagereference"

# use this to recursively draw the property correctly
def drawProperty(layout, context, compKey : str, comp, propName:str, showLabel:bool = True):
    if comp is None:
        return
    
    prop = getattr(comp, propName, None)

    wasEnabled = layout.enabled
    original_layout = layout

    global currentDrawContext
    if currentDrawContext is not None:
        currentDrawContext.beforeDrawProperty(propName)
    
    try:

        # poll_fn = getattr(prop, "poll", None)
        # if callable(poll_fn): 
        #     # get type name
        #     typeName = type(prop).__name__
        #     global panelPropertyInfos
        #     if currentDrawContext.currentComponentKey not in panelPropertyInfos:
        #         panelPropertyInfos[currentDrawContext.currentComponentKey] = dict()
        #     componentsProps = panelPropertyInfos[currentDrawContext.currentComponentKey]
        #     if typeName not in componentsProps:
        #         print("CAN POLL", typeName)
        #         info = validationInfo()
        #         info.pollFunction = poll_fn
        #         componentsProps[typeName] = info
        #         propDict[prop] = info
        
        # global propDict
        # if not prop in propDict:
        #     info = validationInfo()
        #     propDict[prop] = None
                

        # if its a collection
        if drawList(layout, context, compKey, comp, propName, prop):
            return
        
        draw_fn = getattr(prop, "draw", None)
        # first check if the property is drawable
        if callable(draw_fn): 
            draw_fn(context, layout)
        # otherwise draw the property using the builtin method
        else:
            propScheme = currentDrawContext.currentPropScheme()
            propType = None
            needsFilePicker = False
            # check if the propName exists in the scheme (if we draw a list it's not in the scheme)
            if propScheme is not None:
                propType = propScheme["type"]
                needsFilePicker = __needsFilePicker(propType)

            if isinstance(prop, str):
                # if a component can not be resolved on import (e.g. because it is unknown)
                # we want to keep the guid
                if prop.startswith("{'guid': "):
                    layout = layout.row()
                    layout.enabled = False
            elif needsFilePicker:
                layout = layout.row()

            if showLabel:
                layout.prop(comp, propName)
            else:
                layout.prop(comp, propName, text="")

            if needsFilePicker:
                op = layout.operator("needle.select_file", text="", icon="FILE_NEW")
                # op.target_property = ""
                if propType == "videoclip":
                    op.filter_glob = "*.mp4;*.mov"
                if propType == "audioclip":
                    op.filter_glob = "*.mp3;*.wav"
                if propType == "imagereference":
                    op.filter_glob = "*.png;*.jpg;*.jpeg"
                
                currentFilePath = getattr(prop, "filepath", None)
                if currentFilePath:
                    absPath = bpy.path.abspath(currentFilePath)
                    if not os.path.exists(absPath):
                        original_layout.label(text="MISSING \"" + currentFilePath + "\"", icon='ERROR')
                    elif absPath == currentFilePath:
                        original_layout.label(text="Filepath is absolute on disc (use 'File/External Data/Make Paths Relative')", icon='ERROR')
            pass
    finally:
        original_layout.enabled = wasEnabled
        if currentDrawContext is not None:
            currentDrawContext.afterDrawProperty(propName)


def drawList(layout, context, compKey : str, comp, propName:str, prop) -> bool:
    
    # check if it's a list and NOT an enum_flag type
    if hasattr(prop, "add") and not hasattr(prop, "symmetric_difference"):
        global currentDrawContext
        # the list is nested in an array object
        listName = currentDrawContext.propertyNameStack[-2]
        currentComponent = currentDrawContext.currentComponentKey
        
        listId = currentComponent + "_" + listName
        indexPath = "internal__" + propName + "_index"
        currentDrawContext.startList()
        layout.template_list("NEEDLE_UL_CustomList", listId, comp, propName, comp, indexPath, rows=1)
        currentDrawContext.endList()
        
        if currentDrawContext is not None:
            options = layout.row(align=True)
            options.label(text="")
            addOp = options.operator("component.add_item", icon='ADD', text="Add item")
            addOp.key = currentDrawContext.currentComponentKey
            addOp.path = currentDrawContext.path
            # print("key", compKey, "prop", propName)

            clearOp = options.operator("component.clear_list", icon='X', text="Clear")
            clearOp.key = currentDrawContext.currentComponentKey
            clearOp.path = currentDrawContext.path
        return True
    return False


def isInternalProperty(propName):
    if(propName.startswith("internal__")):
        return True
    if(propName == "blender"):
        return True
    if(propName == "guid"):
        return True
    if propName == "__annotations__":
        return True
    return False




class NEEDLE_UL_CustomList(bpy.types.UIList):
    def draw_item(self, context, layout, data, item, icon, active_data, active_propname, index):
        global currentDrawContext
        if hasattr(item, "value"):
            # value = getattr(item, "value", None)
            drawProperty(layout, context, "", item, "value", False)
        else:
            layoutEntry = layout.box()
            layoutEntry = layoutEntry.column(align=True)
            layoutEntry.label(text="#" + str(index))
            # assuming we don't have lists in lists
            scheme = currentDrawContext.currentComponentScheme
            listName = currentDrawContext.propertyNameStack[-2]
            schemeProp = scheme[listName]
            if "type" in schemeProp:
                # iterate scheme type keys
                schemeType = schemeProp["type"]
                if schemeType:
                    if isinstance(schemeType, str):
                        layoutEntry.label(text=schemeType)
                        pass
                    else:
                        for key in schemeType:
                            row = layoutEntry.split(align=True, factor=.3)
                            row.label(text=key)
                            drawProperty(row, context, "", item, key, False)
            else:
                layoutEntry.label(text="Array scheme needs a \"type\" field")
        
        if currentDrawContext is not None:
            removeOp = layout.operator("component.remove_item", icon='X', text="")
            removeOp.key = currentDrawContext.currentComponentKey
            removeOp.path = currentDrawContext.path
            removeOp.index = index
        pass
# 

customPropertyTypeSchemes = {}

# this is the property drawer for a custom property
class CustomPropertyType(bpy.types.PropertyGroup):
    def draw(self, context, layout):
        # typeName = self.__class__.__name__
        global currentDrawContext
        layout = layout.box()
        layout.label(text=currentDrawContext.currentPropName())

        # iterate properties
        annotations = self.__annotations__ #["internal__propertyNames"]
        for key in annotations:
            if isInternalProperty(key): 
                continue
            if hasattr(self, key):
                drawProperty(layout, context, self.name, self, key)



# this is the base panel class that draw a component
# in unity terms that's the Editor class 
# we create sub editors for it when parsing the scheme files
class NEEDLE_PT_ComponentPanelBase(bpy.types.Panel):
    bl_label = "Component Panel"
    bl_space_type = 'PROPERTIES'
    bl_region_type = 'WINDOW'
    bl_context = "object"
    bl_description = "Component Panel"

    key = bpy.props.StringProperty(name="key", default="")
    scheme = None
    isMissing = False
    
    @classmethod
    def poll(self, context):
        key = self.key
        obj = context.object

        # global panelPropertyInfos
        # if key in panelPropertyInfos:
        #     componentsProps = panelPropertyInfos[key]
        #     for typeName in componentsProps:
        #         info = componentsProps[typeName]
        #         if info.pollFunction is not None:
        #             info.pollFunction(info.prop, context)

        # check if key is string
        if not isinstance(key, str):
            return True
        comp = getattr(obj, key)
        return getIsActive(comp)

    def draw_header(self, context):
        width = context.region.width
        layout = self.layout
        # TODO: how to right align this thing on OSX
        # if isWindows():
            # layout.ui_units_x = width * .0435

        icon = getPreviewIcon("logo_small_bw")
        row = layout.row()
        layout.use_property_split = True
        row.alignment = "EXPAND"
        row.scale_x = 1
        row.label(text="", icon_value=icon)
        if not self.isMissing:
            key = self.key
            obj = context.object
            if not isinstance(key, str):
                return
            comp = getattr(obj, key)
            row.prop(comp, "enabled", text="")

            label = self.scheme["bl_label"]
            self.bl_label = ""
            row.label(text=label)

            # draw remove component button
            # self.draw_removeComponent(layout, key)

            # operatorLayout = layout.row()
            # layout.label(text="")
            # removeLayout = row.row()
            # removeLayout.scale_x = 1
            # removeLayout.alignment = "LEFT"
            # # removeLayout.ui_units_x = 2
            # op = removeLayout.operator("needle.removecomponent", text="", icon="X")
            # if op is not None:
            #     op.key = key
            #     op.displayName = self.bl_label 

    def draw(self, context):
        layout = self.layout

        # print("KEY: \"" + self.key + "\"")
        key = self.key
        obj = context.object
        
        # check if key is string
        if not isinstance(key, str) or self.isMissing:
            row = layout.row()
            # make label colorful
            row.alert = True
            row.label(text="Component is missing!", icon='ERROR')
            op = row.operator("needle.removecomponent", text="", icon="X")
            if isinstance(key, str):
                op.key = key
            return

        comp = getattr(obj, key)
        componentProps = self.scheme["__annotations__"]
        componentName = self.scheme["bl_label"]

        if component_registry.isKnown(componentName) == False:
            return

        scheme = component_registry.schemes[componentName]
        # print(component_registry.schemes)

        if hasattr(comp, "description") and comp.description is not None and comp.description != "":
            # multiline label
            layout.label(text=comp.description, icon='INFO')

        if "Collider" in componentName or componentName == "Rigidbody":
            settings = tryGetMainSceneSettings()
            if settings and not settings.useRapier:
                box = layout.box()
                box.label(text="Physics are currently disabled in your project:", icon='ERROR')
                box.label(text="Enable \"UsePhysics\" in the Needle Engine panel")
                layout = layout.column()
                layout.enabled = False


        global currentDrawContext
        if currentDrawContext is None:
            currentDrawContext = DrawContext()
        currentDrawContext.reset()
        currentDrawContext.currentComponentKey = key
        currentDrawContext.currentComponentScheme = scheme

        did_drawItems = False
        for kvp in componentProps.items():
            propName = kvp[0]
            if isInternalProperty(propName):
                continue
            if propName == "enabled":
                continue
            # get property info
            if propName in scheme:
                schemeInfo = scheme[propName]
                if "type" in schemeInfo:
                    type = schemeInfo["type"]
                    # dont draw a property for a function
                    if type == "fn":
                        continue
                    if type == "label":
                        layout.separator()
                        labelLayout = layout.row()
                        labelLayout.scale_y = 1.3
                        labelLayout.label(text=propName)
                        continue
            did_drawItems = True
            currentDrawContext.resetPath()
            drawProperty(layout, context, key, comp, propName)
        
        if not did_drawItems:
            infoLayout = layout.column()
            infoLayout.enabled = False
            infoLayout.label(text="This component doesn't have any properties")
        
        currentDrawContext.reset()
        self.draw_removeComponent(self.layout, key, obj)
    
    def draw_removeComponent(self, layout, key, obj):
        row = layout.row()
        row.label(text="")
        # has any object actively selected?
        hasObjectSelected = len(bpy.context.selected_objects) > 0
        row.enabled = hasObjectSelected
        op = row.operator("needle.removecomponent", text="", icon="X")
        if op is not None:
            op.key = key
            op.displayName = self.bl_label 



def getPanelTypeName(componentName):
    return "NEEDLE_PT_ComponentPanel_" + componentName

def createPanelType(key:str, schema : dict or None, fullName : str or None, typeInfo : dict or None):

    if schema is None:
        schema = {
            "internal__displayName": key,
            "name": key,
        }

    ui_info =  { 
        "bl_label": schema["internal__displayName"],
        "bl_description": "test",
        "bl_context" : "object",
    }
    
    # dont put the camera panel in the camera tab
    # if key == "Camera":
    #     ui_info["bl_context"] = "data"

    panelType = type(getPanelTypeName(schema["name"]), (NEEDLE_PT_ComponentPanelBase,), ui_info)
    panelTypes.append(panelType)

    if fullName is not None:
        panelType.key = fullName
    if typeInfo is not None:
        panelType.scheme = typeInfo["schema"]
    
    bpy.utils.register_class(panelType)
    return panelType


def register():
    panelPropertyInfos = {}
    # bpy.utils.unregister_class(CustomList)
    # bpy.utils.register_class(CustomList)
    pass

def unregister():
    # bpy.utils.unregister_class(CustomList)
    unregisterPanelTypes()

def unregisterPanelTypes():
    for panelType in panelTypes:
        try:
            bpy.utils.unregister_class(panelType)
        except RuntimeError:
            pass
    panelTypes.clear()


def detectMissingComponents():
    knownComponents = bpy.types.Object.__dict__.keys()
    for fullName in knownComponents:
        if not fullName.startswith("NEEDLE_component_"):
            continue
        name = fullName.replace("NEEDLE_component_", "")
        # print("TEST: " + name)
        if component_registry.isMissing(name):
            print(">>> Component " + name + " is not registered in the component registry")
            panel = createPanelType(name, None, fullName, None)
            panel.isMissing = True