# SPDX-FileCopyrightText: 2019-2022 Blender Foundation
#
# SPDX-License-Identifier: GPL-2.0-or-later

# Note: Property group was moved to __init__

bl_info = {
    "name": "Vertex Align",
    "author": "",
    "version": (0, 1, 7),
    "blender": (2, 61, 0),
    "location": "View3D > Tool Shelf",
    "description": "",
    "warning": "",
    "doc_url": "",
    "category": "Mesh",
}


import bpy
from bpy.props import (
        BoolVectorProperty,
        FloatVectorProperty,
        )
from mathutils import Vector
from bpy.types import Operator


# Edit Mode Toggle
def edit_mode_out():
    bpy.ops.object.mode_set(mode='OBJECT')


def edit_mode_in():
    bpy.ops.object.mode_set(mode='EDIT')


def get_mesh_data_():
    edit_mode_out()
    ob_act = bpy.context.active_object
    me = ob_act.data
    edit_mode_in()
    return me


def list_clear_(l):
    l[:] = []
    return l


class va_buf():
    list_v = []
    list_0 = []


# Store The Vertex coordinates
class Vertex_align_store(Operator):
    bl_idname = "vertex_align.store_id"
    bl_label = "Active Vertex"
    bl_description = ("Store Selected Vertex coordinates as an align point\n"
                      "Single Selected Vertex only")

    @classmethod
    def poll(cls, context):
        obj = context.active_object
        return (obj and obj.type == 'MESH' and context.mode == 'EDIT_MESH')

    def execute(self, context):
        try:
            me = get_mesh_data_()
            list_0 = [v.index for v in me.vertices if v.select]

            if len(list_0) == 1:
                list_clear_(va_buf.list_v)
                for v in me.vertices:
                    if v.select:
                        va_buf.list_v.append(v.index)
                        bpy.ops.mesh.select_all(action='DESELECT')
            else:
                self.report({'WARNING'}, "Please select just One Vertex")
                return {'CANCELLED'}
        except:
            self.report({'WARNING'}, "Storing selection could not be completed")
            return {'CANCELLED'}

        self.report({'INFO'}, "Selected Vertex coordinates are stored")

        return {'FINISHED'}


# Align to original
class Vertex_align_original(Operator):
    bl_idname = "vertex_align.align_original"
    bl_label = "Align to original"
    bl_description = "Align selection to stored single vertex coordinates"
    bl_options = {'REGISTER', 'UNDO'}

    @classmethod
    def poll(cls, context):
        obj = context.active_object
        return (obj and obj.type == 'MESH' and context.mode == 'EDIT_MESH')

    def draw(self, context):
        layout = self.layout
        layout.label(text="Axis:")

        row = layout.row(align=True)
        row.prop(context.scene.mesh_extra_tools, "vert_align_axis",
                 text="X", index=0, toggle=True)
        row.prop(context.scene.mesh_extra_tools, "vert_align_axis",
                 text="Y", index=1, toggle=True)
        row.prop(context.scene.mesh_extra_tools, "vert_align_axis",
                 text="Z", index=2, toggle=True)

    def execute(self, context):
        edit_mode_out()
        ob_act = context.active_object
        me = ob_act.data
        cen1 = context.scene.mesh_extra_tools.vert_align_axis
        list_0 = [v.index for v in me.vertices if v.select]

        if len(va_buf.list_v) == 0:
            self.report({'INFO'},
                        "Original vertex not stored in memory. Operation Cancelled")
            edit_mode_in()
            return {'CANCELLED'}

        elif len(va_buf.list_v) != 0:
            if len(list_0) == 0:
                self.report({'INFO'}, "No vertices selected. Operation Cancelled")
                edit_mode_in()
                return {'CANCELLED'}

            elif len(list_0) != 0:
                vo = (me.vertices[va_buf.list_v[0]].co).copy()
                if cen1[0] is True:
                    for i in list_0:
                        v = (me.vertices[i].co).copy()
                        me.vertices[i].co = Vector((vo[0], v[1], v[2]))
                if cen1[1] is True:
                    for i in list_0:
                        v = (me.vertices[i].co).copy()
                        me.vertices[i].co = Vector((v[0], vo[1], v[2]))
                if cen1[2] is True:
                    for i in list_0:
                        v = (me.vertices[i].co).copy()
                        me.vertices[i].co = Vector((v[0], v[1], vo[2]))
        edit_mode_in()

        return {'FINISHED'}


# Align to custom coordinates
class Vertex_align_coord_list(Operator):
    bl_idname = "vertex_align.coord_list_id"
    bl_label = ""
    bl_description = "Align to custom coordinates"

    @classmethod
    def poll(cls, context):
        obj = context.active_object
        return (obj and obj.type == 'MESH' and context.mode == 'EDIT_MESH')

    def execute(self, context):
        edit_mode_out()
        ob_act = context.active_object
        me = ob_act.data
        list_clear_(va_buf.list_0)
        va_buf.list_0 = [v.index for v in me.vertices if v.select][:]

        if len(va_buf.list_0) == 0:
            self.report({'INFO'}, "No vertices selected. Operation Cancelled")
            edit_mode_in()
            return {'CANCELLED'}

        elif len(va_buf.list_0) != 0:
            bpy.ops.vertex_align.coord_menu_id('INVOKE_DEFAULT')

        edit_mode_in()

        return {'FINISHED'}


# Align to custom coordinates menu
class Vertex_align_coord_menu(Operator):
    bl_idname = "vertex_align.coord_menu_id"
    bl_label = "Tweak custom coordinates"
    bl_description = "Change the custom coordinates for aligning"
    bl_options = {'REGISTER', 'UNDO'}

    def_axis_coord: FloatVectorProperty(
            name="",
            description="Enter the values of coordinates",
            default=(0.0, 0.0, 0.0),
            min=-100.0, max=100.0,
            step=1, size=3,
            subtype='XYZ',
            precision=3
            )
    use_axis_coord = BoolVectorProperty(
            name="Axis",
            description="Choose Custom Coordinates axis",
            default=(False,) * 3,
            size=3,
            )
    is_not_undo = False

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

    def using_store(self, context):
        scene = context.scene
        return scene.mesh_extra_tools.vert_align_use_stored

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

        if self.using_store(context) and self.is_not_undo:
            layout.label(text="Using Stored Coordinates", icon="INFO")

        row = layout.split(0.25)
        row.prop(self, "use_axis_coord", index=0, text="X")
        row.prop(self, "def_axis_coord", index=0)

        row = layout.split(0.25)
        row.prop(self, "use_axis_coord", index=1, text="Y")
        row.prop(self, "def_axis_coord", index=1)

        row = layout.split(0.25)
        row.prop(self, "use_axis_coord", index=2, text="Z")
        row.prop(self, "def_axis_coord", index=2)

    def invoke(self, context, event):
        self.is_not_undo = True
        scene = context.scene
        if self.using_store(context):
            self.def_axis_coord = scene.mesh_extra_tools.vert_align_store_axis

        return context.window_manager.invoke_props_dialog(self, width=200)

    def execute(self, context):
        self.is_not_undo = False
        edit_mode_out()
        ob_act = context.active_object
        me = ob_act.data

        for i in va_buf.list_0:
            v = (me.vertices[i].co).copy()
            tmp = Vector((v[0], v[1], v[2]))

            if self.use_axis_coord[0] is True:
                tmp[0] = self.def_axis_coord[0]
            if self.use_axis_coord[1] is True:
                tmp[1] = self.def_axis_coord[1]
            if self.use_axis_coord[2] is True:
                tmp[2] = self.def_axis_coord[2]
            me.vertices[i].co = tmp

        edit_mode_in()

        return {'FINISHED'}


#  Register
classes = (
    Vertex_align_store,
    Vertex_align_original,
    Vertex_align_coord_list,
    Vertex_align_coord_menu,
    )


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


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


if __name__ == "__main__":
    register()
