# Custom Templates - Blender Add-On
# Copyright (C) 2024  Francesco Bellini

# 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 3 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, see <https://www.gnu.org/licenses/>.

import os
import bpy
from bpy.types import AddonPreferences, PropertyGroup, Operator
from bpy.props import CollectionProperty, IntProperty, StringProperty


bl_info = {
    "id": "custom_templates",
    "name": "Custom Templates",
    "tagline": "Add your own .blend files as template options for new projects",
    "blender": (4, 2, 0),
    "location": "File > New & File > Defaults",
    "category": "System",
    "support": "COMMUNITY",
    "blender_manifest": "blender_manifest.toml"
}


class TemplateItem(PropertyGroup):
    name: StringProperty(
        name="Name", description="Display name for this template")
    path: StringProperty(
        name="Path", description="Path to the .blend file for this template", subtype='FILE_PATH')


class OT_SelectTemplatePopup(Operator):
    bl_idname = "wm.select_template_popup"
    bl_label = "Select a new custom template"
    bl_description = "Create a new template occurency by selecting an existing .blend file"

    project_name: StringProperty(name="Project Name")
    project_path: StringProperty(name="Project Path", subtype="FILE_PATH")

    def execute(self, context):
        prefs = context.preferences.addons[__package__].preferences
        for p in prefs.projects:
            if p.path == self.project_path:
                already_present = True
                self.report(
                    {'WARNING'}, f'Selected file is already in the templates list as "{p.name}".')
                return {'FINISHED'}

        new_project = prefs.projects.add()
        new_project.name = self.project_name
        new_project.path = self.project_path
        self.report(
            {'INFO'}, f"Template '{self.project_name}' selected and added successfully!")

        return {'FINISHED'}

    def invoke(self, context, event):
        return context.window_manager.invoke_props_dialog(self)


class OT_AddTemplatePopup(Operator):
    bl_idname = "wm.add_template_popup"
    bl_label = "Use as template"
    bl_description = "Use the current .blend file to create a new template occurency"

    project_name: StringProperty(name="Project Name")

    def execute(self, context):
        prefs = context.preferences.addons[__package__].preferences
        current_file_path = bpy.data.filepath
        for p in prefs.projects:
            if p.path == current_file_path:
                already_present = True
                self.report(
                    {'WARNING'}, f'Current file is already in the templates list as "{p.name}".')
                return {'FINISHED'}

        if current_file_path:
            new_project = prefs.projects.add()
            new_project.name = self.project_name
            new_project.path = current_file_path
            self.report(
                {'INFO'}, f"Template '{self.project_name}' added successfully!")
        else:
            self.report({'ERROR'}, "Current file is not saved on disk.")

        return {'FINISHED'}

    def invoke(self, context, event):
        return context.window_manager.invoke_props_dialog(self)


class OT_AddTemplateItem(Operator):
    bl_idname = "custom_templates.add"
    bl_label = "Add Template"
    bl_description = "Add new template"

    def execute(self, context):
        prefs = context.preferences.addons[__package__].preferences
        prefs.projects.add()
        prefs.active_template_index = len(prefs.projects) - 1
        self.report({'INFO'}, f"Empty template added")
        return {'FINISHED'}


class OT_RemoveTemplateItem(Operator):
    bl_idname = "custom_templates.remove"
    bl_label = "Remove Template"
    bl_description = "Remove selected template"

    def execute(self, context):
        prefs = context.preferences.addons[__package__].preferences
        self.report(
            {'INFO'}, f'Template "{prefs.projects[prefs.active_template_index].name}" removed{" (`"+prefs.projects[prefs.active_template_index].path+"`)" if prefs.projects[prefs.active_template_index].path != "" else "" }')
        prefs.projects.remove(prefs.active_template_index)
        prefs.active_template_index = min(
            max(0, prefs.active_template_index - 1), len(prefs.projects) - 1)
        return {'FINISHED'}


class OT_MoveUpTemplateItem(bpy.types.Operator):
    bl_idname = "custom_templates.move_up"
    bl_label = "Move Up"
    bl_description = "Move the selected template up in the list"

    def execute(self, context):
        prefs = context.preferences.addons[__package__].preferences
        index = prefs.active_template_index

        if index > 0:
            prefs.projects.move(index, index - 1)
            prefs.active_template_index -= 1
            self.report({'INFO'}, f"Templates list re-ordered")
        else:
            self.report({'WARNING'}, "Template is already at the top")

        return {'FINISHED'}


class OT_MoveDownTemplateItem(bpy.types.Operator):
    bl_idname = "custom_templates.move_down"
    bl_label = "Move Down"
    bl_description = "Move the selected template down in the list"

    def execute(self, context):
        prefs = context.preferences.addons[__package__].preferences
        index = prefs.active_template_index

        if index < len(prefs.projects) - 1:
            prefs.projects.move(index, index + 1)
            prefs.active_template_index += 1
            self.report({'INFO'}, f"Templates list re-ordered")
        else:
            self.report({'WARNING'}, "Template is already at the bottom")

        return {'FINISHED'}
    
class OT_OpenAddonPreferences(bpy.types.Operator):
    bl_idname = "custom_templates.open_preferences"
    bl_label = "Open Custom Templates Preferences"
    bl_description = "Open the preferences for the Custom Templates add-on"

    def execute(self, context):
        bpy.ops.screen.userpref_show('INVOKE_DEFAULT')
        context.preferences.active_section = 'ADDONS'
        context.window_manager.addon_search = "Custom Templates"
        return {'FINISHED'}

# Add-On Preferences
class CustomTemplatesPreferences(AddonPreferences):
    bl_idname = __package__

    projects: CollectionProperty(type=TemplateItem)
    active_template_index: IntProperty(
        description="Index of the selected template")

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

        layout.label(
            text="Here you can setup your own .blend files that will be shown in the `File > New` menu.")
        layout.label(
            text="You can also use `File > Defaults > Select new custom template` and `File > Defaults > Use current as new template` to update this preferences.")

        row = layout.row()
        row.template_list("UI_UL_list", "custom_templates",
                          self, "projects", self, "active_template_index")

        col = row.column(align=True)
        col.operator("custom_templates.add", icon='ADD', text="")
        col.operator("custom_templates.remove", icon='REMOVE', text="")
        col.operator("custom_templates.move_up", icon='TRIA_UP', text="")
        col.operator("custom_templates.move_down", icon='TRIA_DOWN', text="")

        if self.projects:
            project = self.projects[self.active_template_index]
            layout.prop(project, "name")
            layout.prop(project, "path")



def draw_addon_separator(layout):
    layout.separator()
    layout.label(text="Custom Templates")
    layout.separator()


def draw_new_menu(self, context):
    layout = self.layout
    prefs = context.preferences.addons[__package__].preferences

    if len(prefs.projects) > 0:
        draw_addon_separator(layout)

    for project in prefs.projects:
        layout.operator(
            "wm.read_homefile", text=project.name).filepath = project.path


# File > Defaults > Add-On Buttons
def draw_add_template(self, context):
    layout = self.layout

    draw_addon_separator(layout)

    # Manage Template
    layout.operator("custom_templates.open_preferences",
                    text="Manage templates")
    # Select new custom template
    layout.operator("wm.select_template_popup",
                    text="Select new custom template")
    if bpy.data.filepath != "":
        # Use current as new template (only with an active saved .blend file opened)
        layout.operator("wm.add_template_popup",
                        text="Use current as new template")


def register():
    bpy.utils.register_class(TemplateItem)
    bpy.utils.register_class(OT_MoveUpTemplateItem)
    bpy.utils.register_class(OT_MoveDownTemplateItem)
    bpy.utils.register_class(OT_AddTemplateItem)
    bpy.utils.register_class(OT_RemoveTemplateItem)
    bpy.utils.register_class(OT_AddTemplatePopup)
    bpy.utils.register_class(OT_SelectTemplatePopup)
    bpy.utils.register_class(OT_OpenAddonPreferences)
    bpy.utils.register_class(CustomTemplatesPreferences)
    bpy.types.TOPBAR_MT_file_new.remove(draw_new_menu)
    bpy.types.TOPBAR_MT_file_new.remove(draw_add_template)
    bpy.types.TOPBAR_MT_file_new.append(draw_new_menu)
    bpy.types.TOPBAR_MT_file_defaults.append(draw_add_template)


def unregister():
    bpy.utils.unregister_class(CustomTemplatesPreferences)
    bpy.utils.unregister_class(OT_MoveUpTemplateItem)
    bpy.utils.unregister_class(OT_MoveDownTemplateItem)
    bpy.utils.unregister_class(OT_AddTemplateItem)
    bpy.utils.unregister_class(OT_AddTemplatePopup)
    bpy.utils.unregister_class(OT_RemoveTemplateItem)
    bpy.utils.unregister_class(OT_SelectTemplatePopup)
    bpy.utils.unregister_class(OT_OpenAddonPreferences)
    bpy.utils.unregister_class(TemplateItem)
    bpy.types.TOPBAR_MT_file_new.remove(draw_new_menu)
    bpy.types.TOPBAR_MT_file_defaults.remove(draw_add_template)


if __name__ == "__main__":
    register()
