import bpy
import nodeitems_utils
from .animator_utils import string_to_hash, trySetActionActive
from .animator_conditions import Parameter
from .animator_transition import AnimatorTransition
from .animator import NEEDLE_AnimatorController, NEEDLE_AnimatorControllerGroup
from ...component_utils import getGuid
from ...utils import generateRandomStringId


class AnimatorBaseNode(bpy.types.Node):
    bl_label = "AnimatorBaseNode"
    bl_icon = 'NODETREE'

    def serialize(self, serializer):
        pass
    
    @classmethod
    def poll(cls, ntree):
        return True # ntree.bl_idname in {'SimpleNodeTree', 'SimpleNodeTreeGroup'}
        
    def copy(self, node):
        if self.bl_idname == 'SimpleNodeResult': self.label = self.name
        if hasattr(self, 'set_as_output'): self.set_as_output = False
        print(f"Copied {self.name} from {node.name}")

    def free(self):
        """Remove Node"""
        print("Removed node", self.name)

    # This is build-in method
    def update(self):
        pass
    
    def draw_buttons(self, context, layout):
        pass

    def process(self, context, id, path):
        """node method"""
        pass

    def process_group(self, context, id, path):
        """group node method"""
        pass


    ## INITIAL METHOD
    #########################################

    def create_input(self, socket_type, socket_id, socket_label, default_value=None):
        if self.inputs.get(socket_id):
            return None

        input = self.inputs.new(socket_type, socket_id)
        input.text = socket_label

        if default_value: input.default_value = default_value

    def remove_input(self, socket_id):
        input = self.inputs.get(socket_id)
        if input:
            self.inputs.remove(input)

    def create_output(self, socket_type, socket_id, socket_label, default_value=None):
        if self.outputs.get(socket_id):
            return None

        output = self.outputs.new(socket_type, socket_id)
        output.name = socket_label

        if default_value: output.default_value = default_value

    def remove_output(self, socket_id):
        for i in range(len(self.outputs)):
            if self.outputs[i].identifier == socket_id:
                self.outputs.remove(self.outputs[i])
                break

    def get_output(self, socket_id):
        for i in range(len(self.outputs)):
            if self.outputs[i].identifier == socket_id:
                return self.outputs[i]




############################################
# Properties

class AnimatorProperties(AnimatorBaseNode):
    bl_label = "Parameters"
    bl_icon = 'NODETREE'

    parameters: bpy.props.CollectionProperty(type=Parameter)
    parameters_index: bpy.props.IntProperty(default=-1)

    def serialize(self, serializer):
        arr = []
        for p in self.parameters:
            arr.append(p.serialize(serializer))
        return arr

    def draw_buttons(self, context, layout):
        layout.label(text="AnimatorController Parameters")
        for p in self.parameters:
            p.draw(context, layout)
            pass
        layout.operator("needle_animator.add_parameter")
        # add space
        layout.separator()

    def draw_buttons_ext(self, context, layout):
        layout.operator("needle_animator.add_parameter")
        layout.template_list("NEEDLE_UL_AnimatorParametersList", "", self, "parameters", self, "parameters_index")

    def draw_color(self, context, node):
        return (1,0,0,1)



############################################
# Animator State

class AnimatorState(AnimatorBaseNode):
    bl_label = "Animator State"
    bl_icon = 'ACTION'
    
    state_id: bpy.props.StringProperty(default="")
    action: bpy.props.PointerProperty(type=bpy.types.Action)
    looping: bpy.props.BoolProperty(name="Looping", default=True)
    transitions: bpy.props.CollectionProperty(type=AnimatorTransition)
    transitions_index: bpy.props.IntProperty(default=-1)



    ############################################
    # serialization

    def serialize(self, serializer):
        model = {
            "name" : self.name,
            "hash" : string_to_hash(self.name),
            "motion" :  self.serializeMotion(serializer),
            "transitions" : self.serializeTransitions(serializer)
        }
        return model

    def serializeMotion(self, serializer):
        assignedAction = self.action
        if assignedAction is None:
            return None
        obj = {
            "name" : assignedAction.name,
            "isLooping" : self.looping,
            "guid" : getGuid(assignedAction),
            "clip" : "" # < will be filled in later when serializing the animations
            # "clips" : [{
            #     "node" : 0, # if multiple animators use the same animator controller we dont want to copy the whole controller but instead store the clip pointer per node
            #     "clip" : "animations/0"
            # }] 
        }
        serializer.registerAnimation(obj, "clip", assignedAction)
        return obj

    def serializeTransitions(self, serializer):
        arr = []
        for transition in self.transitions:
            res = transition.serialize(serializer, self)
            if res is not None:
                arr.append(res)
        return arr


    ############################################

    def init(self, context):
        self.create_input('MotionSocket', 'input', "")
        self.state_id = generateRandomStringId(10)

    def copy(self, node):
        self.state_id = generateRandomStringId(10)
        self.use_custom_color = False
        self.location = (node.location[0] + 30, node.location[1] + 30)
        super().copy(node)
    
    def update(self):
        self.ensureInputAvailable()
        # we need to defer this after update is done to mark all links as valid
        bpy.app.timers.register(self.ensureOutputHasOnlyOneTarget)
        pass

    def insert_link(self, link):
        pass

    def draw_buttons(self, context, layout):
        region = context.region
        # UI is the region in the foldout to the right
        # we dont want to draw the full list in the node by default
        # this is the UI in the right panel
        if region and region.type == 'UI':
            setStateLayout = layout.column(align=True)
            setDefaultStateLabel = "Set as default state"
            if self.getIsDefaultState():
                setDefaultStateLabel = "This is the default state"
                setStateLayout.enabled = False
            setStateLayout.operator("needle_animator.set_default_state", icon='ANIM', text=setDefaultStateLabel)
            layout.separator()
            layout.label(text="Make sure used actions are added as NLA tracks")
            layout.prop(self, 'action', text='Motion')
            layout.prop(self, 'looping', text='Looping')
            layout.label(text="Create transitions to get connect animator states", icon="INFO")
            layout.operator("needle_animator.add_transition", text="Add Transition", icon='ADD')
            layout.template_list("NEEDLE_UL_AnimatorTransitionList", "", self, "transitions", self, "transitions_index", rows=1)
        # this is the node UI:
        else:
            # if self.getIsDefaultState():
            #     layout.label(text="Default State", icon='ANIM')
            col = layout.column(align=True)
            col.prop(self, 'action', text='')

            if self.select:
                trySetActionActive(self, self.action)


    # def draw_buttons_ext(self, context, layout):
    #     layout.template_list("NEEDLE_UL_AnimatorTransitionList", "", self, "transitions", self, "transitions_index")
    #     layout.operator("needle_animator.add_transition", text="Add Transition", icon='ADD')

    def draw_label(self):
        # display the state name
        return self.name

    def getIsDefaultState(self):
        tree = self.id_data
        return self.state_id == tree.default_state

    def onTransitionAdded(self, transition):
        output = self.outputs.get(transition.transitionId)
        if not output:
            self.create_output('MotionSocket', transition.transitionId, transition.name)

    def onTransitionRemoved(self, transition):
        self.remove_output(transition)

    def onTransitionNameChanged(self, id, name):
        output = self.get_output(id)
        if output:
            output.name = name

    def getTransition(self, id):
        for transition in self.transitions:
            if transition.transitionId == id:
                return transition

    # which state does this transition point to
    def getDestinationStateIndex(self, id):
        output = self.get_output(id)
        if output and len(output.links) > 0:
            targetNode = output.links[0].to_node
            return self.__tryFindDestinationStateIndex(targetNode)
        return None
    
    def __tryFindDestinationStateIndex(self, targetNode):
        tree = self.id_data
        statesFound = 0
        # we need to iterate all nodes since we need to count the AnimatorState nodes only
        for i in range(len(tree.nodes)):
            node = tree.nodes[i]
            # check if its connected and if this the graph node is the target
            if node == targetNode:
                if node.type == "REROUTE":
                    if len(node.outputs) > 0 and len(node.outputs[0].links) > 0:
                        return self.__tryFindDestinationStateIndex(node.outputs[0].links[0].to_node)
                    return None
                return statesFound
            elif node.bl_idname == 'AnimatorState':
                statesFound += 1    
        return None

    def ensureInputAvailable(self):
        # hasEmptyInput = False
        for input in self.inputs:
            input.link_limit = 9999
        #     if not input.is_linked:
        #         if hasEmptyInput:
        #             self.inputs.remove(input)
        #         hasEmptyInput = True
        
        # if not hasEmptyInput:
        #     self.create_input('MotionSocket', "input_" + generateRandomStringId(5), "")

    def ensureOutputHasOnlyOneTarget(self):
        for input in self.inputs:
            input.link_limit = 99

        for output in self.outputs:
            # if a output has more then one target we want to remove previously made links
            # since one transition can always just have one target
            # we can not just set the link limit because that UX is not so nice in blender then
            previouslyConnectedLink = None
            for i in range(len(output.links)):
                link = output.links[i]
                if link.is_valid is False:
                    link.is_valid = True
                if link.to_node != self and link.to_node:
                    if previouslyConnectedLink:
                        self.id_data.links.remove(previouslyConnectedLink)
                    previouslyConnectedLink = link
                

            
class AnimatorNodeCategory(nodeitems_utils.NodeCategory):
    @classmethod
    def poll(cls, context):
        return context.space_data.tree_type in {NEEDLE_AnimatorController.bl_idname, NEEDLE_AnimatorControllerGroup.bl_idname}


node_categories = [
    AnimatorNodeCategory("STATE", "State", items=[
        nodeitems_utils.NodeItem('AnimatorState'),
    ]),
    AnimatorNodeCategory("PROPERTIES", "Props", items=[
        nodeitems_utils.NodeItem('AnimatorProperties'),
    ]),
]


def register():
    bpy.utils.register_class(AnimatorState)
    bpy.utils.register_class(AnimatorProperties)
    try:
        nodeitems_utils.unregister_node_categories("AnimatorNodeCategory")
    except Exception:
        pass
    nodeitems_utils.register_node_categories("AnimatorNodeCategory", node_categories)

def unregister():
    bpy.utils.unregister_class(AnimatorState)
    bpy.utils.unregister_class(AnimatorProperties)
    nodeitems_utils.unregister_node_categories("AnimatorNodeCategory")

