# ##### BEGIN GPL LICENSE BLOCK #####
#
#  This program is free software; you can redistribute it and/or
#  modify it under the terms of the GNU General Public License
#  as published by the Free Software Foundation; either version 2
#  of the License, or (at your option) any later version.
#
#  This program is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.
#
#  You should have received a copy of the GNU General Public License
#  along with this program; if not, write to the Free Software Foundation,
#  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# ##### END GPL LICENSE BLOCK #####

# Copyright 2011, Ryan Inch

import bpy

from bpy.types import (
    Operator,
)

from bpy.props import (
    BoolProperty,
    StringProperty,
    IntProperty
)

from . import internals

from .internals import (
    layer_collections,
    rto_history,
    qcd_slots,
    update_property_group,
    get_modifiers,
    get_move_selection,
    get_move_active,
    update_qcd_header,
)

from .operator_utils import (
    apply_to_children,
)


class MoveToQCDSlot(Operator):
    '''Move object(s) to QCD slot'''
    bl_label = "Move To QCD Slot"
    bl_idname = "view3d.move_to_qcd_slot"
    bl_options = {'REGISTER', 'UNDO'}

    slot: StringProperty()
    toggle: BoolProperty()

    def execute(self, context):
        global qcd_slots
        global layer_collections

        selected_objects = get_move_selection()
        active_object = get_move_active()
        internals.move_triggered = True

        qcd_laycol = None
        slot_name = qcd_slots.get_name(self.slot)

        if slot_name:
            qcd_laycol = layer_collections[slot_name]["ptr"]

        else:
            return {'CANCELLED'}


        if not selected_objects:
            return {'CANCELLED'}

        # adds object to slot
        if self.toggle:
            if not active_object:
                active_object = tuple(selected_objects)[0]

            if not active_object.name in qcd_laycol.collection.objects:
                for obj in selected_objects:
                    if obj.name not in qcd_laycol.collection.objects:
                        qcd_laycol.collection.objects.link(obj)

            else:
                for obj in selected_objects:
                    if obj.name in qcd_laycol.collection.objects:

                        if len(obj.users_collection) == 1:
                            continue

                        qcd_laycol.collection.objects.unlink(obj)


        # moves object to slot
        else:
            for obj in selected_objects:
                if obj.name not in qcd_laycol.collection.objects:
                    qcd_laycol.collection.objects.link(obj)

                for collection in obj.users_collection:
                    qcd_idx = qcd_slots.get_idx(collection.name)
                    if qcd_idx != self.slot:
                        collection.objects.unlink(obj)


        # update the active object if needed
        if not context.active_object:
            try:
                context.view_layer.objects.active = active_object

            except RuntimeError: # object not in visible slot
                pass

        # update header UI
        update_qcd_header()

        return {'FINISHED'}


class ViewMoveQCDSlot(Operator):
    bl_label = ""
    bl_idname = "view3d.view_move_qcd_slot"
    bl_options = {'REGISTER', 'UNDO'}

    slot: StringProperty()

    @classmethod
    def description(cls, context, properties):
        global qcd_slots

        slot_name = qcd_slots.get_name(properties.slot)

        slot_string = f"QCD Slot {properties.slot}: \"{slot_name}\"\n"

        hotkey_string = (
            "  * LMB - Isolate slot.\n"
            "  * Shift+LMB - Toggle slot.\n"
            "  * Ctrl+LMB - Move objects to slot.\n"
            "  * Ctrl+Shift+LMB - Toggle objects' slot"
            )

        return f"{slot_string}{hotkey_string}"

    def invoke(self, context, event):
        global layer_collections
        global qcd_history

        modifiers = get_modifiers(event)

        if modifiers == {"shift"}:
            bpy.ops.view3d.view_qcd_slot(slot=self.slot, toggle=True)

            return {'FINISHED'}

        elif modifiers == {"ctrl"}:
            bpy.ops.view3d.move_to_qcd_slot(slot=self.slot, toggle=False)
            return {'FINISHED'}

        elif modifiers == {"ctrl", "shift"}:
            bpy.ops.view3d.move_to_qcd_slot(slot=self.slot, toggle=True)
            return {'FINISHED'}

        else:
            bpy.ops.view3d.view_qcd_slot(slot=self.slot, toggle=False)
            return {'FINISHED'}

class ViewQCDSlot(Operator):
    '''View objects in QCD slot'''
    bl_label = "View QCD Slot"
    bl_idname = "view3d.view_qcd_slot"
    bl_options = {'UNDO'}

    slot: StringProperty()
    toggle: BoolProperty()

    def execute(self, context):
        global qcd_slots
        global layer_collections
        global rto_history

        qcd_laycol = None
        slot_name = qcd_slots.get_name(self.slot)

        if slot_name:
            qcd_laycol = layer_collections[slot_name]["ptr"]

        else:
            return {'CANCELLED'}


        # get objects not in object mode
        locked_active_obj = context.view_layer.objects.active
        locked_objs = []
        locked_objs_mode = ""
        for obj in context.view_layer.objects:
            if obj.mode != 'OBJECT':
                if obj.mode not in ['POSE', 'WEIGHT_PAINT'] or obj == locked_active_obj:
                    locked_objs.append(obj)
                    locked_objs_mode = obj.mode


        if self.toggle:
            # check if slot can be toggled off.
            for obj in qcd_laycol.collection.objects:
                if obj.mode != 'OBJECT':
                    if obj.mode not in ['POSE', 'WEIGHT_PAINT'] or obj == locked_active_obj:
                        return {'CANCELLED'}

            # get current child exclusion state
            child_exclusion = []

            def get_child_exclusion(layer_collection):
                child_exclusion.append([layer_collection, layer_collection.exclude])

            apply_to_children(qcd_laycol, get_child_exclusion)

            # toggle exclusion of qcd_laycol
            qcd_laycol.exclude = not qcd_laycol.exclude

            # set correct state for all children
            for laycol in child_exclusion:
                laycol[0].exclude = laycol[1]

            # restore locked objects back to their original mode
            # needed because of exclude child updates
            if locked_objs:
                context.view_layer.objects.active = locked_active_obj
                bpy.ops.object.mode_set(mode=locked_objs_mode)

            # set layer as active layer collection
            context.view_layer.active_layer_collection = qcd_laycol

        else:
            # exclude all collections
            for laycol in layer_collections.values():
                if laycol["name"] != qcd_laycol.name:
                    # prevent exclusion if locked objects in this collection
                    if set(locked_objs).isdisjoint(laycol["ptr"].collection.objects):
                        laycol["ptr"].exclude = True
                    else:
                        laycol["ptr"].exclude = False

            # un-exclude target collection
            qcd_laycol.exclude = False

            # exclude all children
            def exclude_all_children(layer_collection):
                # prevent exclusion if locked objects in this collection
                if set(locked_objs).isdisjoint(layer_collection.collection.objects):
                    layer_collection.exclude = True
                else:
                    layer_collection.exclude = False

            apply_to_children(qcd_laycol, exclude_all_children)

            # restore locked objects back to their original mode
            # needed because of exclude child updates
            if locked_objs:
                context.view_layer.objects.active = locked_active_obj
                bpy.ops.object.mode_set(mode=locked_objs_mode)

            # set layer as active layer collection
            context.view_layer.active_layer_collection = qcd_laycol


        # update header UI
        update_qcd_header()

        view_layer = context.view_layer.name
        if view_layer in rto_history["exclude"]:
            del rto_history["exclude"][view_layer]
        if view_layer in rto_history["exclude_all"]:
            del rto_history["exclude_all"][view_layer]


        return {'FINISHED'}


class RenumerateQCDSlots(Operator):
    bl_label = "Renumber QCD Slots"
    bl_description = (
        "Renumber QCD slots.\n"
        "  * LMB - Renumber (breadth first) from slot 1.\n"
        "  * +Ctrl - Linear.\n"
        "  * +Alt - Reset.\n"
        "  * +Shift - Constrain to branch"
        )
    bl_idname = "view3d.renumerate_qcd_slots"
    bl_options = {'REGISTER', 'UNDO'}

    def invoke(self, context, event):
        global qcd_slots

        modifiers = get_modifiers(event)

        beginning = False
        depth_first = False
        constrain = False

        if 'alt' in modifiers:
            beginning=True

        if 'ctrl' in modifiers:
            depth_first=True

        if 'shift' in modifiers:
            constrain=True

        qcd_slots.renumerate(beginning=beginning,
                             depth_first=depth_first,
                             constrain=constrain)

        update_property_group(context)

        return {'FINISHED'}
