# <pep8-80 compliant>

# ##### 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 #####

__author__ = "kgeogeo, mem, Nutti <nutti.metro@gmail.com>"
__status__ = "production"
__version__ = "6.5"
__date__ = "6 Mar 2021"

import bpy
from bpy.props import BoolProperty
import bmesh
from mathutils import Vector

from .. import common
from ..utils.bl_class_registry import BlClassRegistry
from ..utils.property_class_registry import PropertyClassRegistry


def _is_valid_context(context):
    # Multiple objects editing mode is not supported in this feature.
    objs = common.get_uv_editable_objects(context)
    if len(objs) != 1:
        return False

    # only edit mode is allowed to execute
    if context.object.mode != 'EDIT':
        return False

    # only 'VIEW_3D' space is allowed to execute
    if not common.is_valid_space(context, ['VIEW_3D']):
        return False

    return True


@PropertyClassRegistry()
class _Properties:
    idname = "move_uv"

    @classmethod
    def init_props(cls, scene):
        scene.muv_move_uv_enabled = BoolProperty(
            name="Move UV Enabled",
            description="Move UV is enabled",
            default=False
        )

    @classmethod
    def del_props(cls, scene):
        del scene.muv_move_uv_enabled


@BlClassRegistry()
class MUV_OT_MoveUV(bpy.types.Operator):
    """
    Operator class: Move UV
    """

    bl_idname = "uv.muv_move_uv"
    bl_label = "Move UV"
    bl_options = {'REGISTER', 'UNDO'}

    __running = False

    def __init__(self):
        self.__topology_dict = []
        self.__prev_mouse = Vector((0.0, 0.0))
        self.__offset_uv = Vector((0.0, 0.0))
        self.__prev_offset_uv = Vector((0.0, 0.0))
        self.__first_time = True
        self.__ini_uvs = []
        self.__operating = False

        # Creation of BMesh is high cost, so cache related objects.
        self.__cache = {}

    @classmethod
    def poll(cls, context):
        # we can not get area/space/region from console
        if common.is_console_mode():
            return False
        if cls.is_running(context):
            return False
        return _is_valid_context(context)

    @classmethod
    def is_running(cls, _):
        return cls.__running

    def _find_uv(self, bm, active_uv):
        topology_dict = []
        uvs = []
        for fidx, f in enumerate(bm.faces):
            for vidx, v in enumerate(f.verts):
                if v.select:
                    uvs.append(f.loops[vidx][active_uv].uv.copy())
                    topology_dict.append([fidx, vidx])

        return topology_dict, uvs

    def modal(self, _, event):
        if self.__first_time is True:
            self.__prev_mouse = Vector((
                event.mouse_region_x, event.mouse_region_y))
            self.__first_time = False
            return {'RUNNING_MODAL'}

        # move UV
        div = 10000
        self.__offset_uv += Vector((
            (event.mouse_region_x - self.__prev_mouse.x) / div,
            (event.mouse_region_y - self.__prev_mouse.y) / div))
        ouv = self.__offset_uv
        pouv = self.__prev_offset_uv
        vec = Vector((ouv.x - ouv.y, ouv.x + ouv.y))
        dv = vec - pouv
        self.__prev_offset_uv = vec
        self.__prev_mouse = Vector((
            event.mouse_region_x, event.mouse_region_y))

        # check if operation is started
        if not self.__operating:
            if event.type == 'LEFTMOUSE' and event.value == 'RELEASE':
                self.__operating = True
            return {'RUNNING_MODAL'}

        # update UV
        obj = self.__cache["active_object"]
        bm = self.__cache["bmesh"]
        active_uv = self.__cache["active_uv"]
        for uv in self.__cache["target_uv"]:
            uv += dv
        bmesh.update_edit_mesh(obj.data)

        # check mouse preference
        confirm_btn = 'LEFTMOUSE'
        cancel_btn = 'RIGHTMOUSE'

        # cancelled
        if event.type == cancel_btn and event.value == 'PRESS':
            for (fidx, vidx), uv in zip(self.__topology_dict, self.__ini_uvs):
                bm.faces[fidx].loops[vidx][active_uv].uv = uv
            MUV_OT_MoveUV.__running = False
            self.__cache = {}
            return {'FINISHED'}
        # confirmed
        if event.type == confirm_btn and event.value == 'PRESS':
            MUV_OT_MoveUV.__running = False
            self.__cache = {}
            return {'FINISHED'}

        return {'RUNNING_MODAL'}

    def execute(self, context):
        MUV_OT_MoveUV.__running = True
        self.__operating = False
        self.__first_time = True

        context.window_manager.modal_handler_add(self)

        objs = common.get_uv_editable_objects(context)
        # poll() method ensures that only one object is selected.
        obj = objs[0]
        bm = bmesh.from_edit_mesh(obj.data)
        active_uv = bm.loops.layers.uv.active
        self.__topology_dict, self.__ini_uvs = self._find_uv(bm, active_uv)

        # Optimization: Store temporary variables which cause heavy
        #               calculation.
        self.__cache["active_object"] = obj
        self.__cache["bmesh"] = bm
        self.__cache["active_uv"] = active_uv
        self.__cache["target_uv"] = []
        for fidx, vidx in self.__topology_dict:
            l = bm.faces[fidx].loops[vidx]
            self.__cache["target_uv"].append(l[active_uv].uv)

        if context.area:
            context.area.tag_redraw()

        return {'RUNNING_MODAL'}
