from __future__ import annotations
import bpy
import blf
import gpu
import bmesh
from bpy_extras import view3d_utils
from . import g
from gpu_extras.batch import batch_for_shader
from gpu_extras.presets import draw_circle_2d
from .utils import functions
import random
from .utils.trans import t
import mathutils
import itertools
import traceback
import time

from .utils import functions
from . import const

if bpy.app.version[0] == 3 or bpy.app.version[0] == 4:
    import bgl

def draw_desc(self, context):
    prefs = functions.get_preferences()

    if prefs.desc_position_y == 100 or prefs.desc_position_x == 100:
        return

    font_id = 0

    region = context.region
    width, height = region.width, region.height

    blf.color(font_id, 1, 1, 1, 0.6)

    font_size = self.get_font_size()
    self.set_font_size(font_size)
    # base_x, base_y = width / 15, height * 7 / 8
    base_x = width * prefs.desc_position_x / 100
    base_y = height * (100 - prefs.desc_position_y) / 100

    def position_generator(base_x, base_y, line_height):
        while True:
            blf.position(font_id, base_x, base_y, 0)
            yield
            base_y -= line_height

    pos_gen = position_generator(base_x, base_y, font_size)

    if g.mode == g.Mode.NORMAL:
        next(pos_gen)
        blf.draw(font_id, f"{t('current_status')}: {t('NORMAL')}")

        next(pos_gen)
        next(pos_gen)
        blf.draw(font_id, f"{t('click_drag_to_draw_line')}")

        next(pos_gen)
        blf.draw(font_id, f"{t('hold_ctrl')} - {t('snap_vert')}")
        next(pos_gen)
        blf.draw(
            font_id, f"{t('hold_ctrl')}+{t('hold_shift')} - {t('snap_exist_path')}"
        )
        next(pos_gen)
        blf.draw(
            font_id,
            f"M {t('key')} - {t('draw_on_axis_of_symmetry')}{'('+t('persist')+')' if g.is_drawing_along_mirror_persist else ''}: {t('enable') if g.is_drawing_along_mirror else t('disable')}",
        )

        next(pos_gen)
        blf.draw(
            font_id,
            f"B {t('key')} - {t('skip_no_solution_pattern')}: {t('enable') if g.is_auto_search_pattern else t('disable')}",
        )

        next(pos_gen)
        blf.draw(
            font_id,
            f"S {t('key')} - {t('snap_surface')}: {t('enable') if g.is_snap_surface else t('disable')}",
        )

        if g.mode == g.Mode.NORMAL:
            next(pos_gen)
            blf.draw(
                font_id,
                f"V {t('key')} - {t('snap_opposite')}: {t('enable') if g.is_snap_opposite else t('disable')}",
            )

        if g.hovering_boundary:
            next(pos_gen)
            blf.draw(font_id, f"X {t('key')} - {t('delete_highlight_edge')}")

        if g.hovering_loop:
            loop = g.hovering_loop
            next(pos_gen)
            next(pos_gen)
            blf.draw(font_id, f"Ctrl+{t('scroll')} - {t('change_segment')}")
            next(pos_gen)
            blf.draw(font_id, f"Shift+{t('scroll')} - {t('change_padding')}")
            next(pos_gen)
            blf.draw(font_id, f"T {t('key')} - {t('switch_pattern')}")
            next(pos_gen)
            blf.draw(font_id, f"R {t('key')} - {t('rotate_pattern')}")

            if g.hovering_loop.pattern:
                next(pos_gen)
                blf.draw(
                    font_id,
                    f"Shift + 1,2,3 - {t('smooth_mesh')}",
                )
                next(pos_gen)
                blf.draw(
                    font_id,
                    f"i {t('key')} - {t('faces_normal_toward_viewport')}",
                )

            if g.hovering_loop.pattern:
                pattern = loop.pattern
                next(pos_gen)
                next(pos_gen)
                blf.draw(
                    font_id,
                    f"{t('solver_solution')}: {[int(i) for i in pattern.solution]}",
                )
                next(pos_gen)
                blf.draw(font_id, f"{t('current_para')}:")
                next(pos_gen)
                blf.draw(font_id, f"{t('pattern')}: {pattern.name}")
                next(pos_gen)
                blf.draw(
                    font_id,
                    f"{t('rotation')}: {pattern.rotation}",
                )
            else:
                next(pos_gen)
                next(pos_gen)
                blf.color(font_id, 1, 0.5, 0.5, 0.6)
                blf.draw(
                    font_id,
                    f"{t('solver_solution')}: {loop.solver_msg}",
                )

            #
            if (
                any([i for i in loop.solver_constraint_list if i is not None])
                or loop.solver_constraint_rotation is not None
                or loop.solver_constraint_pattern
            ):
                blf.color(font_id, 1, 0.5, 0.5, 0.6)

                next(pos_gen)
                next(pos_gen)
                blf.draw(
                    font_id,
                    f"{t('solver_constraint')}:",
                )

                next(pos_gen)
                blf.draw(
                    font_id,
                    f"{t('pattern')}: {loop.solver_constraint_pattern.name}",
                )
                next(pos_gen)
                blf.draw(
                    font_id,
                    f"{t('rotation')}: {loop.solver_constraint_rotation}",
                )
                next(pos_gen)
                blf.draw(
                    font_id,
                    f"{t('var_constraint')}: {[i if i is not None else '' for i in loop.solver_constraint_list]}",
                )

                next(pos_gen)
                blf.draw(
                    font_id,
                    f"C {t('key')} {t('clear_constraint')}",
                )
                next(pos_gen)
                blf.draw(
                    font_id,
                    f"Shift + C {t('only_clear_var')}",
                )
                next(pos_gen)
                blf.draw(
                    font_id,
                    f"Ctrl + C {t('only_clear_rotation')}",
                )

    elif g.mode == g.Mode.DRAWING:
        next(pos_gen)
        blf.draw(font_id, f"{t('current_status')}: {t('DRAWING')}")
        next(pos_gen)
        next(pos_gen)

        next(pos_gen)
        blf.draw(font_id, f"{t('right_click')}: {t('cancel_this_draw')}")

        next(pos_gen)
        blf.draw(font_id, f"{t('left_click_release')}: {t('apply_this_draw')}")

        next(pos_gen)
        blf.draw(font_id, f"{t('scroll')} - {t('change_segment')}")

    elif g.mode == g.Mode.MERGE_BOUNDARY:
        next(pos_gen)
        blf.draw(font_id, f"{t('current_status')}: {t('MERGE_BOUNDARY')}")
        next(pos_gen)
        next(pos_gen)

    blf.color(font_id, 1, 1, 1, 0.6)
    next(pos_gen)
    next(pos_gen)
    next(pos_gen)
    blf.draw(font_id, f"{t('enter')} - {t('apply_mesh')}")
    next(pos_gen)
    blf.draw(font_id, f"ESC - {t('quit')}")


def draw_snap_vertex(self, context):
    if (
        not g.drawing_verts_snap_vertex
        or g.drawing_verts_snap_vertex not in g.obj_bm.verts
    ):
        return

    location_2d = view3d_utils.location_3d_to_region_2d(
        context.region,
        context.region_data,
        g.obj.matrix_world @ g.drawing_verts_snap_vertex.co,
    )
    if not location_2d:
        return

    if location_2d:
        font_size = self.get_font_size() // 2
        self.set_line_width(1)
        draw_circle_2d(location_2d, (1, 1, 1, 1), font_size)


def draw_hovering_loop_3d(self):
    if g.mode != g.Mode.NORMAL:
        return

    hovering_loop = g.hovering_loop
    if not hovering_loop:
        return

    verts = hovering_loop.verts
    mid_co = sum((g.obj.matrix_world @ v.co for v in verts), mathutils.Vector()) / len(
        verts
    )

    coords = [mid_co] + [g.obj.matrix_world @ v.co for v in verts]
    indices = []

    for i in range(len(verts)):
        if i == len(verts) - 1:
            indices.append((0, i + 1, 1))
        else:
            indices.append((0, i + 1, i + 2))

    shader = self.shader_3d_uniform_color
    gpu.state.blend_set("ALPHA")
    batch = batch_for_shader(shader, "TRIS", {"pos": coords}, indices=indices)
    shader.bind()
    shader.uniform_float("color", (0, 0.6, 0, 0.3))
    batch.draw(shader)


def draw_hovering_boundary(self):
    if g.mode != g.Mode.NORMAL:
        return

    hovering_boundary = g.hovering_boundary
    if not hovering_boundary:
        return

    path = [g.obj.matrix_world @ v.co for v in hovering_boundary.verts]
    line_co_pairs = []
    for i in range(len(path) - 1):
        line_co_pairs.append((path[i], path[i + 1]))
    draw_lines_3d(self, line_co_pairs, (1, 1, 1, 1), 5)


def draw_loop(self):
    for loop in g.loops:
        for idx, (boundary, is_forward) in enumerate(loop.b_and_d_list):

            region = bpy.context.region
            rv3d = bpy.context.region_data
            coord_2d = view3d_utils.location_3d_to_region_2d(
                region, rv3d, boundary.mid_co
            )
            if coord_2d is not None:
                text = str(idx)
                if not is_forward:
                    text += " R"

                # 绘制索引
                blf.size(0, 50)
                blf.position(0, coord_2d[0], coord_2d[1], 0)
                blf.color(0, 0, 1, 0, 1)
                blf.draw(0, text)


def draw_temp_patch_boundary(self):
    boundary = g.drawing_path_boundary
    if not boundary:
        return

    # draw path
    vertex_co_along_path, path = boundary.get_actual_verts_co_and_path()

    shader = self.shader_3d_uniform_color
    gpu.state.blend_set("ALPHA")
    self.set_line_width(5)
    batch = batch_for_shader(shader, "LINE_STRIP", {"pos": path})
    shader.bind()
    shader.uniform_float("color", (0.0, 0.0, 1, 0.5))
    batch.draw(shader)

    # draw vertex
    # vertex_co_along_path = patch_boundary.vertices_co_along_path()
    for vertex_co in vertex_co_along_path:
        # TODO: vulkun 用这个shader的时候，gpu.state.point_size_set 失效了，等待修复或别的方案。因为新shader POINT_UNIFORM_COLOR，只能画2d顶点
        batch = batch_for_shader(shader, "POINTS", {"pos": [vertex_co]})
        shader.uniform_float("color", (1, 1, 1, 1))
        batch.draw(shader)


def draw_boundaries_3d(self):
    if not g.boundaries:
        return

    # edges
    if g.is_redraw_boundary or not g.last_boundary_data:
        draw_boundary_data_list = []
        for boundary in g.boundaries:
            line_color = functions.hsv_to_rgb(
                functions.stable_random_from_obj(boundary), 1, 1, 1
            )
            boundaries_verts_co = boundary.vertices_co
            boundaries_verts_co = [g.obj.matrix_world @ i for i in boundaries_verts_co]

            line_co_pairs = []
            vert_coords = boundaries_verts_co
            for i in range(len(boundaries_verts_co) - 1):
                line_co_pair = (boundaries_verts_co[i], boundaries_verts_co[i + 1])

                line_co_pairs.append(line_co_pair)

            draw_boundary_data_list.append((line_co_pairs, line_color, vert_coords))

            draw_lines_3d(self, line_co_pairs, line_color, line_width=2.5)
            draw_points_3d(self, vert_coords, (1, 1, 1, 1), point_size=5)

        g.last_boundary_data = draw_boundary_data_list
        g.is_redraw_boundary = False
    else:
        for boundary_data in g.last_boundary_data:
            line_co_pairs, line_color, vert_coords = boundary_data
            draw_lines_3d(self, line_co_pairs, line_color, line_width=2.5)
            draw_points_3d(self, vert_coords, (1, 1, 1, 1), point_size=5)


def draw_boundaries_text_2d(self):
    font_id = 0
    font_size = self.get_font_size()

    context = bpy.context
    rv3d = context.region_data
    region = context.region

    # 绘制边数
    for boundary in g.boundaries:
        coord_2d = view3d_utils.location_3d_to_region_2d(region, rv3d, boundary.mid_co)
        if coord_2d is None:
            return

        x, y = coord_2d
        blf.position(font_id, x, y, 0)
        blf.color(font_id, 1, 1, 1, 1)
        self.set_font_size(font_size)
        blf.draw(font_id, f"{boundary.length}")

    # 绘制建议文本
    for loop in g.loops:
        for i, boundary in enumerate(loop.boundaries):
            boundary_mid_co_2d = view3d_utils.location_3d_to_region_2d(
                region, rv3d, boundary.mid_co
            )
            loop_mid_co_2d = view3d_utils.location_3d_to_region_2d(
                region, rv3d, loop.mid_co
            )

            offset = mathutils.Vector((0, 0))
            if (
                boundary_mid_co_2d.x <= loop_mid_co_2d.x
                and boundary_mid_co_2d.y <= loop_mid_co_2d.y
            ):
                result = mathutils.Vector((1, 1))  # a在b的左下角
            elif (
                boundary_mid_co_2d.x >= loop_mid_co_2d.x
                and boundary_mid_co_2d.y <= loop_mid_co_2d.y
            ):
                result = mathutils.Vector((-3, 1))  # a在b的右下角
            elif (
                boundary_mid_co_2d.x <= loop_mid_co_2d.x
                and boundary_mid_co_2d.y >= loop_mid_co_2d.y
            ):
                result = mathutils.Vector((1, -1))  # a在b的左上角
            elif (
                boundary_mid_co_2d.x >= loop_mid_co_2d.x
                and boundary_mid_co_2d.y >= loop_mid_co_2d.y
            ):
                result = mathutils.Vector((-3, -1))  # a在b的右上角

            offset_value = 20
            offset = result * offset_value

            # 建议文本
            suggest_text = None
            if loop.suggest_solution:
                pos_value = loop.suggest_solution[i]
                neg_value = loop.suggest_solution[i + len(loop.boundaries)]
                if pos_value > 0:
                    suggest_text = f"+{int(pos_value)}"
                if neg_value > 0:
                    suggest_text = f"{-int(neg_value)}"

            suggest_text_co_3d = boundary.mid_co
            suggest_text_co_2d = view3d_utils.location_3d_to_region_2d(
                region, rv3d, suggest_text_co_3d
            )
            if not suggest_text_co_2d:
                continue
            suggest_text_co_2d = suggest_text_co_2d + offset

            constraint_text = None
            constraint_value = loop.solver_constraint_list[i]
            if loop.solver_constraint_list[i] is not None:
                constraint_text = f"{t('padding')}:{int(constraint_value)}"
            draw_patch_boundary_text(
                self,
                suggest_text_co_2d,
                suggest_text=suggest_text,
                constraint_text=constraint_text,
            )


def draw_patch_pattern(self):
    font_id = 0

    if g.is_redraw_pattern:
        for loop in g.loops:
            pattern = loop.pattern
            if not loop.pattern:
                continue

            color = (1, 1, 1, 0.5)
            verts = pattern.interior_verts
            edges = pattern.interior_edges
            if verts:
                pass
            # 减轻压力
            # draw_verts_2d(self, verts, color, 5)

            line_co_pairs = []
            for edge in edges:
                line_co_pair = (
                    g.obj.matrix_world @ edge.verts[0].co,
                    g.obj.matrix_world @ edge.verts[1].co,
                )
                line_co_pairs.append(line_co_pair)

            if line_co_pairs:
                draw_lines_3d(self, line_co_pairs, color, line_width=2)


def draw_lines_3d(self, line_co_pairs, color, line_width=2):
    if not line_co_pairs:
        return

    line_co_pair_flatten = list(itertools.chain(*line_co_pairs))

    shader = self.shader_3d_uniform_color
    gpu.state.blend_set("ALPHA")
    self.set_line_width(line_width)
    batch = batch_for_shader(shader, "LINES", {"pos": line_co_pair_flatten})
    shader.bind()
    shader.uniform_float("color", color)
    batch.draw(shader)


def draw_points_3d(self, vert_coords, color, point_size=3):
    if not vert_coords:
        return
    
    if bpy.app.version < (4, 5, 0):
        shader = self.shader_3d_uniform_color
        gpu.state.blend_set("ALPHA")
        self.set_point_size(point_size)
        batch = batch_for_shader(shader, "POINTS", {"pos": vert_coords})
        shader.bind()
        shader.uniform_float("color", color)
        batch.draw(shader)
    else:
        shader = self.shader_point_uniform_color
        batch = batch_for_shader(shader, "POINTS", {"pos": vert_coords})
        shader.uniform_float("color", color)
        gpu.state.point_size_set(point_size)
        batch.draw(shader)


def draw_edges_2d(self, edges, color):
    if not edges:
        return

    edges_co = [
        tuple(g.obj.matrix_world @ vert.co for vert in edge.verts) for edge in edges
    ]
    context = bpy.context
    region = context.region
    rv3d = context.region_data

    coords_3d = edges_co
    coords_2d = [
        functions.coords_3d_to_2d(region, rv3d, sublist) for sublist in coords_3d
    ]
    if None in coords_2d:
        return

    coords_2d = list(itertools.chain.from_iterable(coords_2d))

    shader = self.shader_2d_uniform_color
    batch = batch_for_shader(shader, "LINES", {"pos": coords_2d})
    shader.bind()
    shader.uniform_float("color", color)
    batch.draw(shader)


def draw_path_2d(self, path, color, line_width=1):
    if not path:
        return

    edges_co = [(path[i], path[i + 1]) for i in range(len(path) - 1)]

    context = bpy.context
    region = context.region
    rv3d = context.region_data

    coords_3d = edges_co
    coords_2d = [
        [
            (view3d_utils.location_3d_to_region_2d(region, rv3d, item) or (None, None))
            for item in sublist
            if view3d_utils.location_3d_to_region_2d(region, rv3d, item) is not None
        ]
        for sublist in coords_3d
    ]
    if None in coords_2d:
        return

    coords_2d = list(itertools.chain.from_iterable(coords_2d))

    self.set_line_width(line_width)
    shader = self.shader_2d_uniform_color
    batch = batch_for_shader(shader, "LINES", {"pos": coords_2d})
    shader.bind()
    shader.uniform_float("color", color)
    batch.draw(shader)


def draw_patch_boundary_text(self, coord_2d, suggest_text, constraint_text):

    font_id = 0
    font_size = self.get_font_size()

    # text
    if coord_2d is None:
        return
    x, y = coord_2d

    self.set_font_size(font_size * (2 / 3))
    offset = font_size
    if suggest_text is not None:
        blf.position(font_id, x, y - offset, 0)
        blf.color(font_id, 0, 1, 0, 1)
        blf.draw(font_id, str(suggest_text))
        offset += font_size

    if constraint_text is not None:
        blf.position(font_id, x, y, 0)
        blf.color(font_id, 1, 0, 0, 1)
        blf.draw(font_id, str(constraint_text))

    self.set_font_size(font_size)


class Draw:
    def __init__(self):
        self.title_handler = None
        self.temp_handler = None
        self.snap_vertex_handler = None

        self._draw_2d_handler = None
        self._draw_3d_handler = None

    def get_font_size(self):
        region = bpy.context.region
        width, height = region.width, region.height
        font_size = max(10, min(width, height) // 50)
        return font_size

    def draw_2d(self, context):
        if repr(self.op_instance).endswith("invalid>"):
            self.clear()
            return

        if g.is_changing_mesh:
            return

        if not g.is_running:
            self.clear()
            return

        try:
            start_time = time.time()
            # draw_hovering_boundary_2d(self)
            draw_hovering_boundary_2d_time = time.time() - start_time

            draw_desc(self, context)

            start_time = time.time()
            draw_snap_vertex(self, context)
            draw_snap_vertex_time = time.time() - start_time

            start_time = time.time()
            # draw_boundaries(self)
            draw_boundaries_time = time.time() - start_time

            draw_boundaries_text_2d(self)

            start_time = time.time()
            # draw_patch_pattern(self)
            draw_patch_pattern_time = time.time() - start_time

            # last_echo_time = g.last_echo_time if g.last_echo_time else 0
            # if time.time() - last_echo_time > 0.1:
            #     print(
            #         f"{draw_hovering_boundary_2d_time:.3f}",
            #         f"{draw_snap_vertex_time:.3f}",
            #         f"{draw_boundaries_time:.3f}",
            #         f"{draw_patch_pattern_time:.3f}",
            #     )
            #     g.last_echo_time = time.time()
        except Exception as e:
            self.clear()
            self.process_exception(e)

        # draw_loop(self)  # debug

    def draw_3d(self, context):
        if repr(self.op_instance).endswith("invalid>"):
            self.clear()
            return

        if g.is_changing_mesh:
            return

        if not g.is_running:
            self.clear()
            return

        try:
            draw_hovering_boundary(self)

            draw_hovering_loop_3d(self)
            draw_temp_patch_boundary(self)

            draw_patch_pattern(self)
            draw_boundaries_3d(self)

        except Exception as e:
            self.clear()
            self.process_exception(e)
            # raise e

    def process_exception(self, e):
        error_info = traceback.format_exc()
        functions.log(error_info)

        # tab退出时，bm删除，这里执行循环很快，还来不及清空，会抛出ReferenceError，这个情况不向ui抛出错误
        if isinstance(e, ReferenceError):
            error_info = traceback.format_exc()

            pass
        else:
            g.running_exception = e
            # raise e
            pass

    def set_line_width(self, line_width):
        if bpy.app.version[0] == 3:
            # 3.x
            bgl.glLineWidth(line_width)
        else:
            # 4.x 5.x
            gpu.state.line_width_set(line_width)

    def set_point_size(self, point_size):
        if bpy.app.version[0] == 3:
            # 3.x
            bgl.glPointSize(point_size)
        else:
            # 4.x 5.x
            gpu.state.point_size_set(point_size)

    def set_font_size(self, font_size):
        font_id = 0
        if bpy.app.version[0] == 3:
            # # 3.x got 3
            blf.size(font_id, font_size, 72)
        else:
            # 4.0 got 2
            blf.size(font_id, font_size)

    def start(self, op_instance):
        functions.log("start draw")

        self.op_instance = op_instance

        if bpy.app.version[0] == 3:
            if bpy.app.version[0] >= 6:
                # 3.6
                self.shader_3d_uniform_color = gpu.shader.from_builtin("UNIFORM_COLOR")
                self.shader_2d_uniform_color = gpu.shader.from_builtin(
                    "2D_UNIFORM_COLOR"
                )
            else:
                # 3.3
                self.shader_3d_uniform_color = gpu.shader.from_builtin(
                    "3D_UNIFORM_COLOR"
                )
                self.shader_2d_uniform_color = gpu.shader.from_builtin(
                    "2D_UNIFORM_COLOR"
                )

        else:
            # 4.0 5.x
            self.shader_3d_uniform_color = gpu.shader.from_builtin("UNIFORM_COLOR")
            self.shader_2d_uniform_color = gpu.shader.from_builtin("UNIFORM_COLOR")

        if bpy.app.version >= (4, 5, 0):
            self.shader_point_uniform_color = gpu.shader.from_builtin("POINT_UNIFORM_COLOR")

        if not self._draw_2d_handler:
            self._draw_2d_handler = bpy.types.SpaceView3D.draw_handler_add(
                self.draw_2d,
                (bpy.context,),
                "WINDOW",
                "POST_PIXEL",
            )
        if not self._draw_3d_handler:
            self._draw_3d_handler = bpy.types.SpaceView3D.draw_handler_add(
                self.draw_3d,
                (bpy.context,),
                "WINDOW",
                "POST_VIEW",
            )

    def clear(self):
        functions.log("darw cleared")
        if self._draw_2d_handler:
            bpy.types.SpaceView3D.draw_handler_remove(self._draw_2d_handler, "WINDOW")
            self._draw_2d_handler = None

        if self._draw_3d_handler:
            bpy.types.SpaceView3D.draw_handler_remove(self._draw_3d_handler, "WINDOW")
            self._draw_3d_handler = None


draw = Draw()
