import datetime
import time
import bpy 

modal_dialog_data:dict = {}
"""Contains context/data for the modal dialogue, e.g. a callback function to draw the UI"""

active_dialogues:dict = {}

# original https://blender.stackexchange.com/a/274786
class NEEDLE_OT_modal_dialog(bpy.types.Operator):
    """modal state while the dialog box is active, call invoke default mandatory"""
    bl_idname  = "needle.modal_dialog"
    bl_label   = ""
    bl_options = {'REGISTER', 'INTERNAL'}

    #find if dialog is currently active?
    def get_cls(self):
        id = self.dialogue_id + self.instance_type
        if not id in modal_dialog_data:
            modal_dialog_data[id] = self
        return modal_dialog_data[id]

    dialog_state = False

    def get_dialog_state(self)->bool:
        return self.get_cls().dialog_state
        
    def set_dialog_state(self, value:bool,)->None:
        self.get_cls().dialog_state = value
        return None

    dialogue_id: bpy.props.StringProperty(default="", options={'SKIP_SAVE',},)
    """Contains the key for lookup in the modal_dialog_data dictionary"""#
    
    dialogue_title: bpy.props.StringProperty(default="", options={'SKIP_SAVE',},)

    modal_data = None
    """assigned on modal start using the data key"""
    
    instance_type : bpy.props.StringProperty(default="UNDEFINED", options={'SKIP_SAVE',},)

    def shutdown(self):
        """shutdown the operator"""
        self.__shutdown()
        return None

    def __shutdown(self):
        """shutdown the operator"""
        print("shutdown", self.dialogue_id, self.instance_type)
        self.set_dialog_state(False)
        if self.dialogue_id in active_dialogues:
            del active_dialogues[self.dialogue_id]
        print("Dialogues data:", len(active_dialogues))
        return None

    def invoke(self,context,event,):
        """decide if we'll invoke modal or dialog"""

        self.bl_label = self.dialogue_title

        #launch both modal & dialog instance of this operator simultaneously
        if (self.instance_type=="UNDEFINED"):
            # pass the data key along
            bpy.ops.needle.modal_dialog('INVOKE_DEFAULT',instance_type="DIALOG", dialogue_id=self.dialogue_id)
            bpy.ops.needle.modal_dialog('INVOKE_DEFAULT',instance_type="MODAL", dialogue_id=self.dialogue_id)
            return {'FINISHED'}

        #launch a dialog instance?
        if (self.instance_type=="DIALOG"):
            self.set_dialog_state(True)
            return context.window_manager.invoke_props_dialog(self)
        
        if (self.instance_type=="POPUP"):
            self.set_dialog_state(True)
            return context.window_manager.invoke_popup(self)

        #launch a modal instance?
        if (self.instance_type=="MODAL"):
            self.modal_start(context)
            context.window_manager.modal_handler_add(self)  
            return {'RUNNING_MODAL'}

        return {'FINISHED'}

    def __del__(self):
        """called when the operator has finished"""
        #some of our instances might be gone from memory, 
        #therefore 'instance_type' is not available for some instance at this stage
        #not the dialog box instance tho & we need to update class status
        try:
            print("del", self.dialogue_id, self.instance_type)
            if (self.instance_type=="DIALOG"):
                self.set_dialog_state(False)
        except:
            pass
        return None


    def modal(self,context,event,):
        """for modal instance"""
        #modal state only active while dialog instance is! 
        if (self.get_dialog_state()==False):
            self.modal_quit(context)
            return {'FINISHED'}
        
        if event.type == 'ESC':
            self.modal_quit(context)
            return {'FINISHED'}
    
        elif event.type == 'ENTER':
            return self.execute(context)

        # print("modal")
        return {'PASS_THROUGH'}

    def modal_start(self,context,):
        print("modal_start", self.dialogue_id, self.instance_type)
        return None

    def modal_quit(self,context,):
        print("modal_quit", self.dialogue_id, self.instance_type)
        self.__shutdown()
        return None
    
    def draw_header(self, context,):
        self.layout.label(text="Modal Dialog")

    def draw(self, context,):
        if (self.get_dialog_state()==False):
            return None
        if self.dialogue_id in modal_dialog_data:
            self.modal_data = modal_dialog_data[self.dialogue_id]
            if self.modal_data:
                drawfn = self.modal_data["drawfn"]
                if drawfn:
                    drawfn(self.layout, context)
                    return None
        return None

    def execute(self, context,):
        """mandatory function called when user press on 'ok' """
        try:
            if self.dialogue_id in modal_dialog_data:
                self.modal_data = modal_dialog_data[self.dialogue_id]
                if self.modal_data:
                    acceptFn = self.modal_data["acceptFn"]
                    if acceptFn:
                        acceptFn()
        finally:
            self.__shutdown()
        return {'FINISHED'}
    
    def cancel(self, context,):
        """mandatory function called when user press on 'cancel' """
        try:
            if self.dialogue_id in modal_dialog_data:
                self.modal_data = modal_dialog_data[self.dialogue_id]
                if self.modal_data:
                    cancelFn = self.modal_data["cancelFn"]
                    if cancelFn:
                        cancelFn()
        finally:
            self.__shutdown()
        return None
    


def __generateId():
    return str(time.time())


def openDialogue(opts:dict):
    """open a modal dialogue box.
    opts: dict
        title: str
        drawfn: function
        acceptFn: function
        cancelFn: function
    """
    id = __generateId()
    modal_dialog_data[id] = {
        "drawfn": "draw" in opts and opts["draw"] or None,
        "acceptFn": "accept" in opts and opts["accept"] or None,
        "cancelFn": "cancel" in opts and opts["cancel"] or None,
        }
    title = "title" in opts and opts["title"] or "Modal Dialogue"
    bpy.ops.needle.modal_dialog('INVOKE_DEFAULT',dialogue_id=id, dialogue_title=title)

def register():
    unregister()
    # add modal_data attribute to the operator
    NEEDLE_OT_modal_dialog.modal_data = None

    bpy.utils.register_class(NEEDLE_OT_modal_dialog)


    ## open after 2 sec
    # bpy.app.timers.register(lambda: openModalDialogue(__test_drawDialogue),first_interval=4,)
        

def unregister():
    try:
        bpy.utils.unregister_class(NEEDLE_OT_modal_dialog)
        for active in active_dialogues:
            active.shutdown()
    except:
        pass


def __test_drawDialogue(self, context):
    self.layout.label(text="hello")
    