bl_info = {
    "name": "Objects Selection Sets",
    "author": "Gregory Smirnoff",
    "version": (0, 0, 7),
    "blender": (2, 80, 0),
    "location": "View3D > Add > Toolshelf",
    "description": "",
    "warning": "",
    "wiki_url": "",
    "category": "Object",
}


from traceback import print_stack
import bpy
from bpy.types import Operator, AddonPreferences, PropertyGroup, UIList
from bpy.props import StringProperty, IntProperty, BoolProperty, CollectionProperty
import subprocess
import os

### Preferences
      
### Functions

def get_objects(self): 


    try:

        set_objects = ''
        ob = bpy.context.scene.oss_list[bpy.context.scene.oss_list_index].set_objects
        obj_length = len(ob) 
            
                    
        for i, item in enumerate(bpy.context.scene.oss_list[bpy.context.scene.oss_list_index].set_objects):

            if item.object != None:
                if (i < obj_length-1):
                        set_objects += item.object.name+','
                if (i == obj_length-1):
                        set_objects += item.object.name
                    
        return set_objects
    
    except:
        return ''  
        
### UI


class OSS_PT_MAIN(bpy.types.Panel):
    bl_label = "Object Selection Sets"
    bl_idname = "OSS_PT_MAINPANEL"
    bl_space_type = 'VIEW_3D'
    bl_region_type = 'UI'
    bl_category = 'Object Sets'

    def draw(self, context):

        scene = context.scene        

        layout = self.layout
        main = layout.row()        
        ul_list = main.column()
        ul_list_opers = main.column(align=True)        
        

        list_obs_ops = layout.row(align=True)
        list_obs = layout.column()
        sel = layout.row()


        ul_list.template_list("SETS_UL_LIST", "", scene, "oss_list", scene, "oss_list_index", rows=6)
        ul_list_opers.operator('oss.add', text='', icon = 'ADD')    

        if len(bpy.context.scene.oss_list):

            ul_list_opers.operator('oss.delete',text='', icon = 'REMOVE') 

            if len(bpy.context.scene.oss_list) > 1:    
                ul_list_opers.operator("oss.move", text='', icon="SORT_DESC").direction = 'UP'
                ul_list_opers.operator("oss.move",text='', icon="SORT_ASC").direction = 'DOWN' 
                ul_list_opers.operator("oss.union_down",text='', icon="DOWNARROW_HLT") 

            ul_list_opers.separator()    
            ul_list_opers.operator("oss.set_to_col",text='', icon="COLLECTION_NEW")
            ul_list_opers.separator() 
            ul_list_opers.prop(scene, "show_list_objects", text='', icon="MATCUBE")                   


            if not bpy.context.scene.show_list_objects:
                ul_list.prop(context.scene, 'object_names')        

            if bpy.context.scene.show_list_objects:
                    obs = bpy.context.scene.oss_list[bpy.context.scene.oss_list_index]
                    list_obs_ops.operator("oss.add_obj",text="Add", icon="OBJECT_DATAMODE")
                    list_obs_ops.operator("oss.remove_obj",text="Remove", icon="OBJECT_HIDDEN")            
                    #list_obs.label(text = "Set Name: "+bpy.context.scene.oss_list[bpy.context.scene.oss_list_index].set_name )
                    list_obs.template_list("OBJECTS_UL_LIST", "", obs, "set_objects", obs, "objects_list_index", rows=6)



            sel.operator('oss.select', icon = 'MOUSE_LMB')
            sel.prop(context.window_manager, 'autoselect', text='', icon='PINNED') 
            sel.prop(context.window_manager, 'select_local', text='', icon='OBJECT_DATA')                 

    
        


class OSS_PT_ADD(bpy.types.Operator):
    bl_label = "Add Set"
    bl_idname = 'oss.add'
    
    def execute(self, context):
        
        ob = bpy.context.selected_objects
        
        if len(ob) > 0:    
        
            length = len(bpy.context.scene.oss_list)
            
            set_name = 'Objects_'+str(length)

            s = context.scene
            item = s.oss_list.add()
            item.set_name = set_name

            item.set_objects.clear()

            for obj in ob:
                 set_obj = item.set_objects.add()
                 set_obj.object = obj   
     
                    
            s.oss_list_index = (len(s.oss_list)-1)      
                    
        else:
            self.report({'INFO'}, "Selected objects!")            
                    
                    

            #item.set_objects
        

        return {"FINISHED"}


class OSS_PT_DEL(bpy.types.Operator):
    bl_label = "Remove Set"
    bl_idname = 'oss.delete'
    
    def execute(self, context):
        
        s = context.scene        
        
        s.oss_list.remove(s.oss_list_index)
                    
        if s.oss_list_index == 0:
            s.oss_list_index -= 1 
            
        if s.oss_list_index > (len(s.oss_list)-1):
            s.oss_list_index = (len(s.oss_list)-1)           
                   


        return {"FINISHED"}


class OSS_PT_MOVE(bpy.types.Operator):
    
    bl_idname = "oss.move"
    bl_label = ""
    bl_description = "Move selected set up or down"

    direction:bpy.props.EnumProperty(items=( \
        ('UP', "Up", "Move up"), \
        ('DOWN', "Down", "Move down"))
    )
    

    def execute(self, context):
        length = len(context.scene.oss_list)
        if length > 1:
            s = context.scene
            d = -1 if self.direction == 'UP' else 1
            new_index = (s.oss_list_index + d) % len(s.oss_list)
            s.oss_list.move(s.oss_list_index, new_index)
            s.oss_list_index = new_index
            
        return {'FINISHED'}  


class OSS_PT_UNION_DOWN(bpy.types.Operator):
    
    bl_idname = "oss.union_down"
    bl_label = ""
    bl_description = "Union with down set"


    def execute(self, context):
       
        
        oslist = context.scene.oss_list
        length = len(oslist)
        index = context.scene.oss_list_index

        obs = context.scene.oss_list[bpy.context.scene.oss_list_index].set_objects
        index_obs = context.scene.oss_list[bpy.context.scene.oss_list_index].objects_list_index   

        
        if length > 1 and (index != (length-1) ):

            context.scene.oss_list[bpy.context.scene.oss_list_index].set_name =  context.scene.oss_list[bpy.context.scene.oss_list_index].set_name+"+"+context.scene.oss_list[bpy.context.scene.oss_list_index+1].set_name  
            
            for item in context.scene.oss_list[bpy.context.scene.oss_list_index+1].set_objects:
                 set_obj = obs.add()
                 set_obj.object = item.object 
                         

            s = context.scene
            s.oss_list.remove(s.oss_list_index+1)
            if s.oss_list_index == 0:
                s.oss_list_index -= 1
                         
 
        return {'FINISHED'}  
    
class OSS_OP_COL_FROM_SET(bpy.types.Operator):

    bl_label = "Set To New Collection"
    bl_description = "Set To New Collection"
    bl_idname = 'oss.set_to_col'
    
    def execute(self, context):

        oslist = context.scene.oss_list
        length = len(oslist)
        index = context.scene.oss_list_index

        if length:
            obs = context.scene.oss_list[bpy.context.scene.oss_list_index].set_objects
            index_obs = context.scene.oss_list[bpy.context.scene.oss_list_index].objects_list_index

            name =  context.scene.oss_list[bpy.context.scene.oss_list_index].set_name


            if len(obs):
                new_coll = bpy.data.collections.new(name)

                # Add collection to scene collection
                bpy.context.scene.collection.children.link(new_coll)

                for item in obs:         
                    new_coll.objects.link(item.object)        
         
                       
        
        return {"FINISHED"}         


class OSS_PT_ADD_OBJ(bpy.types.Operator):
    
    bl_idname = "oss.add_obj"
    bl_label = ""
    bl_description = "Add objects to set"


    def execute(self, context):


        ob = bpy.context.selected_objects

        obs = context.scene.oss_list[bpy.context.scene.oss_list_index].set_objects
        index = context.scene.oss_list[bpy.context.scene.oss_list_index].objects_list_index        
        
        if len(ob) > 0:    
        
            length = len(obs)

            find_count = 0
            names = ""
                    
            for obj in ob: 

                find = False

                for val in obs:
                    if(val.object==obj):
                        find=True
                        find_count+=1
                        names+=obj.name+" "
                        break

                if not find:    
                    set_obj = obs.add()
                    set_obj.object = obj 

            if  find_count > 0: self.report({'INFO'}, "Some objects are already in the set: "+names)
                       
                 
            if  find_count == 0:        
                context.scene.oss_list[bpy.context.scene.oss_list_index].objects_list_index = (len(obs)-1)  


                         
        return {'FINISHED'}

class OSS_PT_REMOVE_OBJ(bpy.types.Operator):
    
    bl_idname = "oss.remove_obj"
    bl_label = ""
    bl_description = "Remove objects from set"


    def execute(self, context):

        obs = context.scene.oss_list[bpy.context.scene.oss_list_index].set_objects
        index = context.scene.oss_list[bpy.context.scene.oss_list_index].objects_list_index

        
        obs.remove(index)
                    
        if index == 0:
            context.scene.oss_list[bpy.context.scene.oss_list_index].objects_list_index -= 1 
            
        if index > (len(obs)-1):
            context.scene.oss_list[bpy.context.scene.oss_list_index].objects_list_index = (len(obs)-1)          
                    
                         
        return {'FINISHED'}                      
                                  
    
    
    
class OSS_PT_SEL(bpy.types.Operator):

    bl_label = "Select Set"
    bl_idname = 'oss.select'
    
    def execute(self, context):
        
        length = len(bpy.context.scene.oss_list)
        
        if not length: self.report({'INFO'}, "Sets is empty")
        
        if length > 0:
        
            bpy.ops.object.select_all(action='DESELECT')

            for item in bpy.context.scene.oss_list[bpy.context.scene.oss_list_index].set_objects:
                 
                 try:

                    item.object.select_set(True)

                    if context.window_manager.select_local:
                    
                        if context.space_data.local_view==None:
                            bpy.ops.view3d.localview()
                            #bpy.ops.object.bounding_box_source()
                            bpy.ops.view3d.view_selected(use_all_regions=False)
                        else:
                            bpy.ops.view3d.localview()
                            bpy.ops.view3d.localview()
                            #bpy.ops.object.bounding_box_source()    
                            bpy.ops.view3d.view_selected(use_all_regions=False) 
                       
                 except:
                     pass   
                       
        
        return {"FINISHED"}   



 




####_UI_List________________________###############################




class SelectionSetsObjects(bpy.types.PropertyGroup):
    
    '''Objects list'''

    def empty_poll(self, object):
       
       obs_names = bpy.context.scene.object_names

       return object.name not in obs_names  
                                
    object: bpy.props.PointerProperty(type=bpy.types.Object, poll=empty_poll)


class SelectionSetsItem(bpy.types.PropertyGroup):
    
    '''Set Items'''

    def set_index(self, value):
         bpy.context.scene.oss_list[bpy.context.scene.oss_list_index].objects_list_index = value

    set_name : bpy.props.StringProperty(get=None, description = "")
    set_objects : bpy.props.CollectionProperty(type=SelectionSetsObjects)
    objects_list_index : bpy.props.IntProperty(min=0, set=None) 
    
    
    
#______________________________________________________________________________________________________________________________

class OBJECTS_UL_LIST(UIList):
    
    def draw_item(self, context, layout, data, item, icon, active_data, active_propname, index):
        
        if self.layout_type in {'DEFAULT', 'COMPACT'}:

                num = layout.row(align = True) 
                num.scale_x = 0.4
                main = layout.row(align = True) 
                num.label(text=str(index))
                main.prop(item, "object", text='')
                


class SETS_UL_LIST(UIList):
    
    def draw_item(self, context, layout, data, item, icon, active_data, active_propname, index):
        
        if self.layout_type in {'DEFAULT', 'COMPACT'}:
                
                main = layout.row(align = True) 

                text = str(context.scene.oss_list_index)
                
                main.prop(item, "set_name", text='', emboss=False, icon="MATCUBE") 
        

#______________________________________________________________________________________________________________________________    

 
def oss_update_func(self, context):
    if context.window_manager.autoselect:
        try:
            bpy.ops.oss.select()
        except: None    
        
        
def register():

    bpy.utils.register_class(OSS_PT_MAIN)
    bpy.utils.register_class(OSS_PT_ADD)
    bpy.utils.register_class(OSS_PT_DEL)
    bpy.utils.register_class(OSS_PT_MOVE)
    bpy.utils.register_class(OSS_PT_UNION_DOWN)
    bpy.utils.register_class(OSS_OP_COL_FROM_SET)
    bpy.utils.register_class(OSS_PT_ADD_OBJ)
    bpy.utils.register_class(OSS_PT_REMOVE_OBJ)
    bpy.utils.register_class(OSS_PT_SEL)
    bpy.utils.register_class(SelectionSetsObjects)
    bpy.utils.register_class(SelectionSetsItem)
    bpy.utils.register_class(OBJECTS_UL_LIST)
    bpy.utils.register_class(SETS_UL_LIST)
 
    bpy.types.Scene.oss_list = CollectionProperty(type=SelectionSetsItem)
    bpy.types.Scene.oss_list_index = IntProperty(min=0, update=oss_update_func) 
    bpy.types.Scene.show_list_objects = BoolProperty(description = "Edit objects in set")   

      
    #bpy.types.Scene.obl_list = bpy.data.scenes["Scene"].oss_list[bpy.context.scene.oss_list_index].set_objects

    bpy.types.WindowManager.autoselect = BoolProperty(default=False)
    bpy.types.WindowManager.select_local = BoolProperty(default=False)

    bpy.types.Scene.object_names = bpy.props.StringProperty \
        (
         get=get_objects,     
         name = "",
         default = "",
         description = "Object in selected set",
         subtype = 'NONE'
        )    
   


def unregister():

    bpy.utils.unregister_class(OSS_PT_MAIN)
    bpy.utils.unregister_class(OSS_PT_ADD)
    bpy.utils.unregister_class(OSS_PT_DEL)
    bpy.utils.unregister_class(OSS_PT_MOVE)
    bpy.utils.unregister_class(OSS_PT_UNION_DOWN)
    bpy.utils.unregister_class(OSS_OP_COL_FROM_SET)
    bpy.utils.unregister_class(OSS_PT_ADD_OBJ)
    bpy.utils.unregister_class(OSS_PT_REMOVE_OBJ)
    bpy.utils.unregister_class(OSS_PT_SEL)
    bpy.utils.unregister_class(SelectionSetsObjects)
    bpy.utils.unregister_class(SelectionSetsItem)
    bpy.utils.unregister_class(OBJECTS_UL_LIST)
    bpy.utils.unregister_class(SETS_UL_LIST)

    del bpy.types.Scene.oss_list
    del bpy.types.Scene.oss_list_index
    del bpy.types.Scene.show_list_objects

    #del bpy.types.Scene.obl_list

    del bpy.types.WindowManager.autoselect
    del bpy.types.WindowManager.select_local
    del bpy.types.Scene.object_names
    
    
    


if __name__ == "__main__":
    register()        