bl_info = {
    "name": "Mesh Cache Tools",
    "author": "Oscurart",
    "version": (1, 0, 1),
    "blender": (2, 70, 0),
    "location": "Tools > Mesh Cache Tools",
    "description": "Tools for Management Mesh Cache Process",
    "warning": "",
    "wiki_url": "",
    "tracker_url": "https://developer.blender.org/maniphest/task/edit/form/2/",
    "category": "Import-Export"}


import bpy
import os
import struct
from bpy_extras.io_utils import ImportHelper
from bpy.types import (
        Operator,
        Panel,
        PropertyGroup,
        AddonPreferences,
        )
from bpy.props import (
        BoolProperty,
        IntProperty,
        StringProperty,
        PointerProperty,
        CollectionProperty,
        )
from bpy.app.handlers import persistent


class OscurartMeshCacheModifiersSettings(PropertyGroup):
    array: BoolProperty(default=True)
    bevel: BoolProperty(default=True)
    boolean: BoolProperty(default=True)
    build: BoolProperty(default=True)
    decimate: BoolProperty(default=True)
    edge_split: BoolProperty(default=True)
    mask: BoolProperty(default=True)
    mirror: BoolProperty(default=True)
    multires: BoolProperty(default=True)
    remesh: BoolProperty(default=True)
    screw: BoolProperty(default=True)
    skin: BoolProperty(default=True)
    solidify: BoolProperty(default=True)
    subsurf: BoolProperty(default=True)
    triangulate: BoolProperty(default=True)
    wireframe: BoolProperty(default=True)
    cloth: BoolProperty(default=True)


# ----------------- AUTO LOAD PROXY

# bpy.context.scene.pc_auto_load_proxy.remove(0)
class CreaPropiedades(Operator):
    bl_idname = "scene.pc_auto_load_proxy_create"
    bl_label = "Create Auto Load PC Proxy List"

    def execute(self, context):
        for gr in bpy.data.collections:
            if gr.library is not None:
                i = bpy.context.scene.pc_auto_load_proxy.add()
                i.name = gr.name
                i.use_auto_load = False
        return {'FINISHED'}


class RemuevePropiedades(Operator):
    bl_idname = "scene.pc_auto_load_proxy_remove"
    bl_label = "Remove Auto Load PC Proxy List"

    def execute(self, context):
        for i in bpy.context.scene.pc_auto_load_proxy:
            bpy.context.scene.pc_auto_load_proxy.remove(0)
        return {'FINISHED'}


class OscurartMeshCacheSceneAutoLoad(PropertyGroup):
    name: StringProperty(
            name="GroupName",
            default=""
            )
    use_auto_load: BoolProperty(
            name="Bool",
            default=False
            )


@persistent
def CargaAutoLoadPC(dummy):
    for gr in bpy.context.scene.pc_auto_load_proxy:
        if gr.use_auto_load:
            for ob in bpy.data.collections[gr.name].objects:
                for MOD in ob.modifiers:
                    if MOD.type == "MESH_CACHE":
                        MOD.cache_format = "PC2"
                        MOD.forward_axis = "POS_Y"
                        MOD.up_axis = "POS_Z"
                        MOD.flip_axis = set(())
                        MOD.frame_start = bpy.context.scene.pc_pc2_start
                        abspath = os.path.abspath(bpy.path.abspath("//" + bpy.context.scene.pc_pc2_folder))
                        MOD.filepath = "%s/%s.pc2" % (abspath, ob.name)


bpy.app.handlers.load_post.append(CargaAutoLoadPC)


# - PANELS -

class View3DMCPanel():
    bl_space_type = 'VIEW_3D'
    bl_region_type = 'TOOLS'


class OscEPc2ExporterPanel(View3DMCPanel, Panel):
    bl_category = "Tools"
    bl_label = "Mesh Cache Tools"
    bl_options = {'DEFAULT_CLOSED'}

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

        row = layout.column(align=1)
        row.prop(scene, "pc_pc2_folder", text="Folder")
        row.operator("buttons.set_meshcache_folder", icon='FILEBROWSER', text="Select Folder Path")
        row = layout.box().column(align=1)
        row.label(text="EXPORTER:")
        row.operator("group.linked_group_to_local", text="Linked To Local", icon="LINKED")
        row.operator("object.remove_subsurf_modifier", text="Remove Gen Modifiers", icon="MOD_SUBSURF")
        row.prop(scene.mesh_cache_tools_settings, "array", text="Array")
        row.prop(scene.mesh_cache_tools_settings, "bevel", text="Bevel")
        row.prop(scene.mesh_cache_tools_settings, "boolean", text="Boolean")
        row.prop(scene.mesh_cache_tools_settings, "build", text="Build")
        row.prop(scene.mesh_cache_tools_settings, "decimate", text="Decimate")
        row.prop(scene.mesh_cache_tools_settings, "edge_split", text="Edge Split")
        row.prop(scene.mesh_cache_tools_settings, "mask", text="Mask")
        row.prop(scene.mesh_cache_tools_settings, "mirror", text="Mirror")
        row.prop(scene.mesh_cache_tools_settings, "multires", text="Multires")
        row.prop(scene.mesh_cache_tools_settings, "remesh", text="Remesh")
        row.prop(scene.mesh_cache_tools_settings, "screw", text="Screw")
        row.prop(scene.mesh_cache_tools_settings, "skin", text="Skin")
        row.prop(scene.mesh_cache_tools_settings, "solidify", text="Solidify")
        row.prop(scene.mesh_cache_tools_settings, "subsurf", text="Subsurf")
        row.prop(scene.mesh_cache_tools_settings, "triangulate", text="Triangulate")
        row.prop(scene.mesh_cache_tools_settings, "wireframe", text="Wireframe")

        # row = layout.column(align=1)
        row.prop(scene, "pc_pc2_start", text="Frame Start")
        row.prop(scene, "pc_pc2_end", text="Frame End")
        row.prop(scene, "pc_pc2_exclude", text="Exclude Token:")
        row.prop_search(scene, "pc_pc2_group", bpy.data, "collections", text="")
        row.operator("export_shape.pc2_selection", text="Export!", icon="POSE_DATA")
        row.prop(scene, "pc_pc2_world_space", text="World Space")
        row = layout.box().column(align=1)
        row.label(text="IMPORTER:")
        row.operator("import_shape.pc2_selection", text="Import", icon="POSE_DATA")
        row.operator("object.modifier_mesh_cache_up", text="MC Top", icon="TRIA_UP")
        row = layout.box().column(align=1)
        row.label(text="PROXY AUTO LOAD:")
        row.operator("scene.pc_auto_load_proxy_create", text="Create List", icon="GROUP")
        row.operator("scene.pc_auto_load_proxy_remove", text="Remove List", icon="X")

        for i in scene.pc_auto_load_proxy:
            if bpy.data.collections[i.name].library is not None:
                row = layout.row()
                row.prop(bpy.data.collections[i.name], "name", text="")
                row.prop(i, "use_auto_load", text="")


def OscSetFolder(self, context, filepath):
    fp = filepath if os.path.isdir(filepath) else os.path.dirname(filepath)
    try:
        os.chdir(os.path.dirname(bpy.data.filepath))
    except Exception as e:
        self.report({'WARNING'}, "Folder could not be set: {}".format(e))
        return {'CANCELLED'}

    rfp = os.path.relpath(fp)
    for sc in bpy.data.scenes:
        sc.pc_pc2_folder = rfp
    return {'FINISHED'}


class OscMeshCacheButtonSet(Operator, ImportHelper):
    bl_idname = "buttons.set_meshcache_folder"
    bl_label = "Set Mesh Cache Folder"
    filename_ext = ".txt"

    def execute(self, context):
        return OscSetFolder(self, context, self.filepath)


def OscFuncExportPc2(self):
    start = bpy.context.scene.pc_pc2_start
    end = bpy.context.scene.pc_pc2_end
    folderpath = bpy.context.scene.pc_pc2_folder
    framerange = end - start

    for ob in bpy.data.collections[bpy.context.scene.pc_pc2_group].objects[:]:
        if any(token not in ob.name for token in bpy.context.scene.pc_pc2_exclude.split(",")):
            bpy.context.window_manager.progress_begin(0, 100)  # progressbar
            if ob.type == "MESH":
                with open("%s/%s.pc2" % (os.path.normpath(folderpath), ob.name), mode="wb") as file:
                    # header
                    headerFormat = '<12siiffi'
                    headerStr = struct.pack(headerFormat,
                             b'POINTCACHE2\0', 1, len(ob.data.vertices[:]), 0, 1.0, (end + 1) - start)
                    file.write(headerStr)
                    # bake
                    obmat = ob.matrix_world
                    for i, frame in enumerate(range(start, end + 1)):
                        print("Percentage of %s bake: %s " % (ob.name, i * 100 / framerange))
                        bpy.context.window_manager.progress_update(i * 100 / framerange)  # progressbarUpdate
                        bpy.context.scene.frame_set(frame)
                        me = bpy.data.meshes.new_from_object(
                                    scene=bpy.context.scene,
                                    object=ob,
                                    apply_modifiers=True,
                                    settings="RENDER",
                                    calc_tessface=True,
                                    calc_undeformed=False
                                    )
                        # rotate
                        if bpy.context.scene.pc_pc2_world_space:
                            me.transform(obmat)
                            me.calc_normals()
                        # create archive
                        for vert in me.vertices[:]:
                            file.write(struct.pack("<3f", *vert.co))
                        # drain mesh
                        bpy.data.meshes.remove(me)

                    print("%s Bake finished!" % (ob.name))

            bpy.context.window_manager.progress_end()  # progressBarClose
    print("Bake Totally Finished!")


class OscPc2ExporterBatch(Operator):
    bl_idname = "export_shape.pc2_selection"
    bl_label = "Export pc2 for selected Objects"
    bl_description = "Export pc2 for selected Objects"
    bl_options = {'REGISTER', 'UNDO'}

    @classmethod
    def poll(cls, context):
        return(bpy.context.scene.pc_pc2_group != "" and bpy.context.scene.pc_pc2_folder != 'Set me Please!')

    def execute(self, context):
        OscFuncExportPc2(self)
        return {'FINISHED'}


class OscRemoveSubsurf(Operator):
    bl_idname = "object.remove_subsurf_modifier"
    bl_label = "Remove Subdivision Surface Modifier"
    bl_description = "Remove Subdivision Surface Modifier"
    bl_options = {'REGISTER', 'UNDO'}

    @classmethod
    def poll(cls, context):
        return(bpy.context.scene.pc_pc2_group != "")

    def execute(self, context):
        GENERATE = [
                'MULTIRES', 'ARRAY', 'BEVEL', 'BOOLEAN', 'BUILD',
                'DECIMATE', 'MASK', 'MIRROR', 'REMESH', 'SCREW',
                'SKIN', 'SOLIDIFY', 'SUBSURF', 'TRIANGULATE'
                ]
        for OBJ in bpy.data.collections[bpy.context.scene.pc_pc2_group].objects[:]:
            for MOD in OBJ.modifiers[:]:
                if MOD.type in GENERATE:
                    if eval("bpy.context.scene.mesh_cache_tools_settings.%s" % (MOD.type.lower())):
                        OBJ.modifiers.remove(MOD)

        return {'FINISHED'}


class OscPc2iMporterBatch(Operator):
    bl_idname = "import_shape.pc2_selection"
    bl_label = "Import pc2 for selected Objects"
    bl_description = "Import pc2 for selected Objects"
    bl_options = {'REGISTER', 'UNDO'}

    @classmethod
    def poll(cls, context):
        return(bpy.context.scene.pc_pc2_folder != 'Set me Please!')

    def execute(self, context):
        for OBJ in bpy.context.selected_objects[:]:
            MOD = OBJ.modifiers.new("MeshCache", 'MESH_CACHE')
            MOD.filepath = "//%s%s%s.pc2" % (bpy.context.scene.pc_pc2_folder, os.sep, OBJ.name)
            MOD.cache_format = "PC2"
            MOD.forward_axis = "POS_Y"
            MOD.up_axis = "POS_Z"
            MOD.flip_axis = set(())
            MOD.frame_start = bpy.context.scene.pc_pc2_start

        return {'FINISHED'}


def OscLinkedGroupToLocal():
    try:
        ACTOBJ = bpy.context.active_object

        if not ACTOBJ.id_data.instance_collection:
            return False

        GROBJS = [ob for ob in ACTOBJ.id_data.instance_collection.objects[:] if ob.type == "MESH"]

        for ob in ACTOBJ.id_data.instance_collection.objects[:]:
            bpy.context.collection.objects.link(ob)
        NEWGROUP = bpy.data.collections.new("%s_CLEAN" % (ACTOBJ.name))
        bpy.context.collection.objects.unlink(ACTOBJ)
        NEWOBJ = []
        for ob in GROBJS:
            NEWGROUP.objects.link(ob)
            NEWOBJ.append(ob)
    except:
        return False

    return True


class OscGroupLinkedToLocal(Operator):
    bl_idname = "group.linked_group_to_local"
    bl_label = "Group Linked To Local"
    bl_description = "Group Linked To Local"
    bl_options = {'REGISTER', 'UNDO'}

    def execute(self, context):
        group_check = OscLinkedGroupToLocal()
        if not group_check:
            self.report({'WARNING'},
                        "There is no objects to link or the object already linked. Operation Cancelled")
            return {'CANCELLED'}

        return {'FINISHED'}


class OscMeshCacheUp(Operator):
    bl_idname = "object.modifier_mesh_cache_up"
    bl_label = "Mesh Cache To Top"
    bl_description = "Send Mesh Cache Modifiers top"
    bl_options = {'REGISTER', 'UNDO'}

    @classmethod
    def poll(cls, context):
        obj = context.object
        return (obj and obj.type == "MESH")

    def execute(self, context):

        actob = bpy.context.view_layer.objects.active

        for ob in bpy.context.selected_objects[:]:
            bpy.context.view_layer.objects.active = ob
            for mod in ob.modifiers[:]:
                if mod.type == "MESH_CACHE":
                    for up in range(ob.modifiers.keys().index(mod.name)):
                        bpy.ops.object.modifier_move_up(modifier=mod.name)

        bpy.context.view_layer.objects.active = actob

        return {'FINISHED'}


# Add-ons Preferences Update Panel

# Define Panel classes for updating
panels = (
        OscEPc2ExporterPanel,
        )


def update_panel(self, context):
    message = "Mesh Cache Tools: Updating Panel locations has failed"
    try:
        for panel in panels:
            if "bl_rna" in panel.__dict__:
                bpy.utils.unregister_class(panel)

        for panel in panels:
            panel.bl_category = context.preferences.addons[__name__].preferences.category
            bpy.utils.register_class(panel)

    except Exception as e:
        print("\n[{}]\n{}\n\nError:\n{}".format(__name__, message, e))
        pass


class OscurartMeshCacheToolsAddonPreferences(AddonPreferences):
    # this must match the addon name, use '__package__'
    # when defining this in a submodule of a python package.
    bl_idname = __name__

    category: StringProperty(
            name="Category",
            description="Choose a name for the category of the panel",
            default="Tools",
            update=update_panel,
            )

    def draw(self, context):
        layout = self.layout
        row = layout.row()
        col = row.column()
        col.label(text="Category:")
        col.prop(self, "category", text="")


classes = (
        OscurartMeshCacheModifiersSettings,
        OscGroupLinkedToLocal,
        OscMeshCacheButtonSet,
        OscMeshCacheUp,
        OscPc2ExporterBatch,
        OscPc2iMporterBatch,
        OscRemoveSubsurf,
        OscurartMeshCacheToolsAddonPreferences,
        RemuevePropiedades,
        OscurartMeshCacheSceneAutoLoad,
        CreaPropiedades,
        OscEPc2ExporterPanel,
        )


# Register

def register():
    for cls in classes:
        bpy.utils.register_class(cls)

    from bpy.types import Scene
    Scene.mesh_cache_tools_settings = PointerProperty(
                                            type=OscurartMeshCacheModifiersSettings
                                            )
    Scene.pc_auto_load_proxy = CollectionProperty(
                                            type=OscurartMeshCacheSceneAutoLoad
                                            )

    Scene.pc_pc2_rotx = BoolProperty(default=True, name="Rotx = 90")
    Scene.pc_pc2_world_space = BoolProperty(default=True, name="World Space")
    Scene.pc_pc2_modifiers = BoolProperty(default=True, name="Apply Modifiers")
    Scene.pc_pc2_subsurf = BoolProperty(default=True, name="Turn Off SubSurf")
    Scene.pc_pc2_start = IntProperty(default=0, name="Frame Start")
    Scene.pc_pc2_end = IntProperty(default=100, name="Frame End")
    Scene.pc_pc2_group = StringProperty()
    Scene.pc_pc2_folder = StringProperty(default="Set me Please!")
    Scene.pc_pc2_exclude = StringProperty(default="*")

    update_panel(None, bpy.context)


def unregister():
    for cls in classes:
        bpy.utils.unregister_class(cls)

    from bpy.types import Scene
    del Scene.mesh_cache_tools_settings
    del Scene.pc_auto_load_proxy
    del Scene.pc_pc2_rotx
    del Scene.pc_pc2_world_space
    del Scene.pc_pc2_modifiers
    del Scene.pc_pc2_subsurf
    del Scene.pc_pc2_start
    del Scene.pc_pc2_end
    del Scene.pc_pc2_group
    del Scene.pc_pc2_folder
    del Scene.pc_pc2_exclude


if __name__ == "__main__":
    register()
