from __future__ import annotations
import mathutils
import bmesh
from bpy_extras import view3d_utils
import bpy
import numpy as np

from . import const
from .utils import functions
from . import g


class Boundary:
    def __init__(self, start_vertex, end_vertex, path):
        self.index = -1
        self.verts = []
        self.path = path
        self.start_vertex = start_vertex
        self.end_vertex = end_vertex

        self.temp_length = 3  # for uncreated boundary

        self.mid_co = mathutils.Vector((0, 0, 0))
        self.solid_path = []
        self.is_editable = True
        self.is_hover = False

        self.is_solid_start = (
            not not start_vertex
            and start_vertex not in self.get_boundaries_verts_except_self()
        )
        self.is_solid_end = None

    @classmethod
    def create_temp(cls, start_vertex):
        instance = cls(
            start_vertex,
            None,
            [],
        )
        return instance

    @property
    def length(self):
        if self.edges:
            return len(self.edges)
        else:
            return self.temp_length

    @length.setter
    def length(self, length):
        # 固化边
        if not self.is_editable:
            return
        if length < 1:
            return

        # TODO: 下面两个暂不用考虑，两个boundary为同一个loop并且boundary都为1了，则冲突了

        # 建成和非建成改的长度不一样
        if self.edges:
            # TODO:特殊情况，两个boundary且都为1的时候冲突了
            # if (
            #     length == 1
            #     and len(self.op_instance.patch.boundaries) == 2
            #     and any(
            #         [
            #             boundary.length == 1
            #             for boundary in self.op_instance.patch.boundaries
            #         ]
            #     )
            # ):
            #     return

            # 特殊情况，已建成的边，如果头尾都吸附到了某个edge的两个顶点上（并且不是吸附路径），如果这个时候改变分段为1，则会报错创建已存在的边
            if length == 1:
                start_vertex_related_edges = self.start_vertex.link_edges
                for start_vertex_related_edge in start_vertex_related_edges:
                    if self.end_vertex in start_vertex_related_edge.verts:
                        return

            self.change_length(length)
        else:
            # # TODO:特殊情况，两个边且都为1的时候
            # if (
            #     length == 1
            #     and len(self.op_instance.patch.boundaries) == 1
            #     and self.op_instance.patch.boundaries[0].length == 1
            # ):
            #     return

            # 特殊情况，未建成的边，如果头尾都吸附到了某个edge的两个顶点上（并且不是吸附路径），如果这个时候改变分段为1，则会报错创建已存在的边，强制约束不能小于1
            if length <= 1:
                return

            self.temp_length = length

    def get_actual_verts_co_and_path(self):
        # 第一个是考虑了temp_path，第二个是真实的path
        if self.solid_path:
            path = self.solid_path
            vertex_co_along_path = path
        else:
            path = self.path
            vertex_co_along_path, _ = self.vertices_co_along_path()
        return vertex_co_along_path, path

    def change_length(self, length):
        self.remove_mesh()
        self.create_mesh(length)

        # self.op_instance.patch.update_after_boundary_change()
        functions.on_change_boundary_length(self)

    def append_path_from_2d(
        self,
        context,
        co_2d,
        is_snap_surface=True,
        is_snap_opposite=False,
    ):
        # 獲取base_co
        if len(self.path) != 0:
            base_co = self.path[-1]
        else:
            if self.start_vertex:
                # vertex.co获取的是局部坐标
                start_vertex = g.obj.matrix_world @ self.start_vertex.co
                base_co = start_vertex
                self.path.append(start_vertex)
            else:
                base_co = (0, 0, 0)

        region = context.region
        rv3d = context.region_data

        # 阈值
        if self.path:
            path_last_co_2d = view3d_utils.location_3d_to_region_2d(
                region, rv3d, self.path[-1]
            )  # 视图外会返回None
            if not path_last_co_2d:
                return
            if (
                path_last_co_2d - mathutils.Vector(co_2d)
            ).length < const.draw_path_threshold:
                # print("太小不记录")
                return

        co_3d = []
        last_co = mathutils.Vector(base_co)

        # Get the ray origin and direction
        ray_origin = view3d_utils.region_2d_to_origin_3d(region, rv3d, co_2d)
        ray_direction = view3d_utils.region_2d_to_vector_3d(region, rv3d, co_2d)

        hit = None
        if is_snap_surface:
            snap_bvh_trees = g.snap_bvh_trees
            for snap_bvh_tree in snap_bvh_trees:
                location, normal, index, distance = snap_bvh_tree.ray_cast(
                    ray_origin, ray_direction
                )
                if location:
                    hit = True
                    break

            if hit:
                if (
                    is_snap_opposite or normal.dot(ray_direction.normalized()) < 0
                ):  # 点乘大于0，反向
                    co_3d = location

        if not co_3d:
            # Project base point
            plane_center = last_co
            plane_normal = ray_direction

            intersection_point = mathutils.geometry.intersect_line_plane(
                ray_origin, ray_origin + ray_direction, plane_center, plane_normal
            )
            if intersection_point:
                co_3d = intersection_point

        self.path.append(co_3d)
        return

    def create_mesh(self, length):
        if self.solid_path:
            self.is_editable = False
            # self.edges 和self.verts 已在update_solid中定义，并且不用创建实体

        else:
            vertices_co_along_path_3d, self.verts_correspond_path_idxs = (
                self.vertices_co_along_path(length)
            )

            # create vert
            vertices_along_path = []
            for i in range(len(vertices_co_along_path_3d)):
                coord = vertices_co_along_path_3d[i]
                if i == 0:
                    # 开头
                    if self.start_vertex:
                        vertices_along_path.append(self.start_vertex)
                    else:
                        vertex = functions.create_temp_vertex(coord)
                        vertices_along_path.append(vertex)
                        self.start_vertex = vertex

                elif i == len(vertices_co_along_path_3d) - 1:
                    # 中间
                    if self.end_vertex:
                        vertices_along_path.append(self.end_vertex)
                    else:
                        vertex = functions.create_temp_vertex(coord)
                        vertices_along_path.append(vertex)
                        self.end_vertex = vertex
                else:
                    # 结尾
                    vertex = functions.create_temp_vertex(coord)
                    vertices_along_path.append(vertex)

            # create edge
            boundary_edges = []
            for i in range(len(vertices_along_path) - 1):
                edge = functions.create_temp_edge(
                    vertices_along_path[i], vertices_along_path[i + 1]
                )
                boundary_edges.append(edge)
            bmesh.update_edit_mesh(g.obj_me)

            self.edges = boundary_edges  # ordered edges from start vert
            self.verts = vertices_along_path

        self.update_mid_co()

    @functions.set_block_before_done
    def done_from_temp(self, end_vertex):
        if (
            len(self.path) < 3
        ):  # path代表鼠标路径，即使吸附上了solid边，这个也是鼠标路径
            functions.log("too short, cancel")
            return

        self.end_vertex = end_vertex
        self.is_solid_end = (
            not not end_vertex
            and end_vertex not in self.get_boundaries_verts_except_self()
        )

        if (
            self.start_vertex
            and self.end_vertex
            and self.start_vertex == self.end_vertex
        ):
            functions.log("start and end same, cancel")
            return

        # 开始处理切分↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
        # 处理切分
        # boundary被切分，被切分的boundary的相关的loop要先删pattern及其网格，每切分一个boundary，boundary会被删除1条，新建3条,然后重建loop，然后相关的loop（即新建的boundary，被切分成2个的boundary）要重建网格

        # 截断了另外的boundary，需要把那些boundary拆分并重建，两个可能：1.开始或结束点在boundary上 2.开启了自动截断模式(笔记，自动截断的情况，获取截断点的坐标，分割path，注意要先创建截断点vert(这里单独写个函数)，把他赋予到对应boundary的start和end，再执行create_mesh)
        mid_verts_to_boundary = (
            {}
        )  # 建立中间点(即除了开始和结束)到boundary的映射。考虑不是每次都建立，不然滚轮的时候会频繁创建（开销很小，不考虑）
        for b in g.boundaries:
            if b == self:  # 排除自身
                continue
            for v in b.verts:
                if v == b.start_vertex or v == b.end_vertex:
                    continue

                # # 理论上来说中间点和boundary是一一对应的，不可能有重复的情况
                # if v in mid_verts_to_boundary:
                #     raise "mid vert duplicated"

                mid_verts_to_boundary[v] = b

        # TODO: 暂时无法处理两个切分点都在同一个boundary的中间点上，返回None
        # TODO: 暂时无法处理两个boundary的情况
        # 下面这个约束同时过滤了上面这两个，实际上这两个issue要同时解决才能应用
        for b in g.boundaries:
            if b == self:  # 跳过自身（没必要，因为这个都没创建）
                continue
            if self.start_vertex in b.verts and self.end_vertex in b.verts:
                functions.log("2 boundary case, cancel")
                return

        # 被切割的boundary要先删pattern的mesh,等切割完了，再全部recreate
        to_be_seperate_boundary_by_end = None
        to_be_seperate_boundary_by_end_related_loops = []
        if self.end_vertex in mid_verts_to_boundary:
            to_be_seperate_boundary_by_end = mid_verts_to_boundary[self.end_vertex]
            for loop in functions.get_loops_from_boundary(
                to_be_seperate_boundary_by_end
            ):
                # 删pattern
                loop.remove_pattern()
                to_be_seperate_boundary_by_end_related_loops.append(loop)

        to_be_seperate_boundary_by_start = None
        to_be_seperate_boundary_by_start_related_loops = []
        if self.start_vertex in mid_verts_to_boundary:
            to_be_seperate_boundary_by_start = mid_verts_to_boundary[self.start_vertex]
            for loop in functions.get_loops_from_boundary(
                to_be_seperate_boundary_by_start
            ):
                # 删pattern
                loop.remove_pattern()
                to_be_seperate_boundary_by_start_related_loops.append(loop)

        # 从全局变量删loop
        to_be_seperate_boundary_related_loops = set(
            (
                to_be_seperate_boundary_by_end_related_loops
                + to_be_seperate_boundary_by_start_related_loops
            )
        )  # 取并集，因为有可能会有同一个loop的情况
        for (
            to_be_seperate_boundary_related_loop
        ) in to_be_seperate_boundary_related_loops:
            g.loops.remove(to_be_seperate_boundary_related_loop)

        # 从全局变量删boundary
        if to_be_seperate_boundary_by_end:
            g.boundaries.remove(to_be_seperate_boundary_by_end)
        if to_be_seperate_boundary_by_start:
            g.boundaries.remove(to_be_seperate_boundary_by_start)

            # 切分
        if to_be_seperate_boundary_by_end:
            need_to_be_cut_boundary = mid_verts_to_boundary[self.end_vertex]
            seperate_boundary_1, seperate_boundary_2 = seperate_boundary_from_vert(
                need_to_be_cut_boundary, self.end_vertex
            )
            # 切分为两个的boundary加进全局变量
            for i in [seperate_boundary_1, seperate_boundary_2]:
                # 重建boundary网格
                i.create_mesh(3)
                # 附加到全局变量
                g.boundaries.add(i)
        if to_be_seperate_boundary_by_start:
            need_to_be_cut_boundary = mid_verts_to_boundary[self.start_vertex]
            seperate_boundary_1, seperate_boundary_2 = seperate_boundary_from_vert(
                need_to_be_cut_boundary, self.start_vertex
            )
            # 切分为两个的boundary加进全局变量
            for i in [seperate_boundary_1, seperate_boundary_2]:
                # 重建boundary网格
                i.create_mesh(3)
                # 附加到全局变量
                g.boundaries.add(i)

            # 重建loop和pattern在外部进行了，因为是删loop而不是改loop所以可以自动识别到
        # 处理切分完毕↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑

        # 如果开始结束都在某个loop的控制点中，则要把那个loop删掉，如：正方形，对角切
        for loop in g.loops:
            controll_verts = loop.controll_verts
            if (
                self.start_vertex in controll_verts
                and self.end_vertex in controll_verts
            ):
                g.loops.remove(loop)
                loop.remove_pattern()

        # 创建网格前，处理吸附到中轴线
        if self.is_editable and g.is_drawing_along_mirror:
            functions.process_path_to_mirror_axis(self.path)

        self.create_mesh(self.length)
        g.boundaries.add(self)

        g.is_redraw_boundary = True

        return self

    def remove_mesh(
        self, exclude_verts=[], is_force_remove_edges=False, is_remove_start_end=False
    ):
        if is_force_remove_edges:
            functions.remove_bm_edges(
                g.obj_bm, self.edges
            )  # using bmesh.ops.delete edges will also delete the island vert

        if len(self.edges) == 1:
            # 特殊情况，
            functions.remove_bm_edges(
                g.obj_bm, [self.edges[0]]
            )  # using bmesh.ops.delete edges will also delete the island vert

        if len(self.edges) > 1:
            if is_remove_start_end:
                # 删除网格,包括开始和结束顶点，用于用户在modal的删除
                boundaries_verts_except_self = self.get_boundaries_verts_except_self()
                to_be_delete_verts = []
                remaining_verts = []
                for vert in self.verts_generator():
                    if vert in exclude_verts:
                        continue  # 排除 exclude_verts 中的顶点

                    if self.is_solid_start and vert == self.start_vertex:
                        remaining_verts.append(
                            vert
                        )  # 如果是 solid_start, 跳过start_vertex
                        continue
                    if self.is_solid_end and vert == self.end_vertex:
                        remaining_verts.append(vert)  # 如果是 solid_end, 跳过end_vertex
                        continue
                    # 并且这个vert不能出现在其他boundary中
                    if vert in boundaries_verts_except_self:
                        remaining_verts.append(vert)
                        continue

                    to_be_delete_verts.append(vert)
                functions.delete_bm_verts(g.obj_bm, to_be_delete_verts)

                # 即使经过上面的条件，还是可能漏，如：三角形，外部画一根截断一个边，先删外部的那一根，然后删被截断的下面那根，然后删上面那根，中间会留一个顶点，因为一开始就把他当成solid_vert看了，但是随着boundary被删除，他就不是了
                to_be_delete_verts = []
                for vert in remaining_verts:
                    if len(vert.link_edges) == 0:
                        to_be_delete_verts.append(vert)
                functions.delete_bm_verts(g.obj_bm, to_be_delete_verts)

            else:
                # 删除网格,除了开始和结束顶点，用于改变长度时的删除网格
                verts = [
                    vert
                    for vert in self.verts_generator()
                    if vert != self.start_vertex
                    and vert != self.end_vertex
                    and vert not in exclude_verts
                ]
                functions.delete_bm_verts(g.obj_bm, verts)

        self.edges = []
        bmesh.update_edit_mesh(g.obj_me)

    def get_boundaries_verts_except_self(self):
        boundaries_verts_except_self = set()
        for b in g.boundaries:
            if b == self:
                continue
            boundaries_verts_except_self.update(b.verts)
        return boundaries_verts_except_self

    def verts_generator(self):
        # # find first edge
        # start_vertex = self.start_vertex
        # yield start_vertex
        # for edge in self.edges:
        #     end_vertex = edge.other_vert(start_vertex)
        #     yield end_vertex
        #     start_vertex = end_vertex

        for i in self.verts:  # 搞不懂为什么一开始写成上面这样
            yield i

    @property
    def vertices_co(self):
        return [temp.co for temp in list(self.verts_generator())]

    def vertices_co_along_path(self, edge_length=None):
        if edge_length is None:
            edge_length = self.temp_length
        return functions.uniform_vertices_co_along_path(self.path, edge_length - 1)

    def update_mid_co(self):
        midpoint_of_path = functions.get_midpoint_of_path(g.obj_bm, self.edges)

        self.mid_co = g.obj.matrix_world @ midpoint_of_path
        return

    @functions.set_block_before_done
    def delete(self):
        # boundary删除，loop可能会被删除，loop被删pattern也会被删
        related_loops = functions.get_loops_from_boundary(self)

        # 删除网格
        for related_loop in related_loops:
            related_loop.remove_pattern()

        if not self.solid_path:
            self.remove_mesh(is_remove_start_end=True)

        # 从全局变量中删除loop和boundary
        g.boundaries.remove(self)
        for related_loop in related_loops:
            g.loops.remove(related_loop)

        g.is_redraw_boundary = True
        g.is_redraw_pattern = True

        return


def seperate_from_path_idxs(path_idxs):
    # 从多个
    pass


def seperate_boundary_from_vert(boundary, seperate_vert):
    # 从boundary已有的顶点，拆分boundary
    # 如果path不够长，则只有头尾直线

    # # 首先要删除boundaries列表（外部删了）
    # g.boundaries.remove(boundary)

    seperate_vert_co_world = g.obj.matrix_world @ seperate_vert.co

    # 删除boundary网格（不包括开始和结束点和vert）
    boundary.remove_mesh(exclude_verts=[seperate_vert], is_force_remove_edges=True)

    # 拆分
    ori_start_vert = boundary.start_vertex
    ori_end_vert = boundary.end_vertex

    # 通过verts_correspond_path_idxs，来获取已有顶点到path索引的映射，因为path是不均匀的，所以要在计算均匀顶点的时候顺便计算
    vert_idx_in_verts = boundary.verts.index(seperate_vert)
    vert_idx_in_path = boundary.verts_correspond_path_idxs[
        vert_idx_in_verts
    ]  # 如果path 10个，则范围是0~9,tou是0，尾是9,中间是0~8
    if len(boundary.path) < vert_idx_in_path + 2:  # 理论不会发生
        raise f"vert_idx_in_path exceed path length: {len(boundary.path)} {vert_idx_in_path}"
    path_1 = boundary.path[0 : vert_idx_in_path + 1] + [seperate_vert_co_world]
    path_2 = [seperate_vert_co_world] + boundary.path[
        vert_idx_in_path + 1 : len(boundary.path)
    ]

    boundary_1 = Boundary(ori_start_vert, seperate_vert, path_1)
    boundary_2 = Boundary(seperate_vert, ori_end_vert, path_2)

    return boundary_1, boundary_2
