from __future__ import print_function
from __future__ import absolute_import
from __future__ import division

import random

from compas.geometry import centroid_points
from compas.geometry import distance_point_point
from compas.geometry import add_vectors
from compas.geometry import bounding_box

from compas.geometry import is_point_in_polygon_xy
from compas.geometry import is_point_in_triangle_xy
from compas.geometry import is_point_in_circle_xy

from compas.geometry import circle_from_points_xy


def delaunay_from_points(points, boundary=None, holes=None, tiny=1e-12):
    """Computes the delaunay triangulation for a list of points.

    Parameters
    ----------
    points : sequence[[float, float, float] | :class:`~compas.geometry.Point`]
        XYZ coordinates of the original points.
    boundary : sequence[[float, float, float] | :class:`~compas.geometry.Point`] | :class:`~compas.geometry.Polygon`, optional
        List of ordered points describing the outer boundary.
    holes : sequence[sequence[[float, float, float] | :class:`~compas.geometry.Point`] | :class:`~compas.geometry.Polygon`], optional
        List of polygons (ordered points describing internal holes.

    Returns
    -------
    list[[int, int, int]]
        The faces of the triangulation.
        Each face is a triplet of indices referring to the list of point coordinates.

    Notes
    -----
    For more info, see [1]_.

    References
    ----------
    .. [1] Sloan, S. W., 1987 *A fast algorithm for constructing Delaunay triangulations in the plane*
           Advances in Engineering Software 9(1): 34-55, 1978.

    Examples
    --------
    >>>

    """
    from compas.datastructures import Mesh
    from compas.datastructures import trimesh_swap_edge

    def super_triangle(coords, ccw=True):
        centpt = centroid_points(coords)
        bbpts = bounding_box(coords)
        dis = distance_point_point(bbpts[0], bbpts[2])
        dis = dis * 300
        v1 = (0 * dis, 2 * dis, 0)
        v2 = (1.73205 * dis, -1.0000000000001 * dis, 0)  # due to numerical issues
        v3 = (-1.73205 * dis, -1 * dis, 0)
        pt1 = add_vectors(centpt, v1)
        pt2 = add_vectors(centpt, v2)
        pt3 = add_vectors(centpt, v3)
        if ccw:
            return pt1, pt3, pt2
        return pt1, pt2, pt3

    mesh = Mesh()

    # to avoid numerical issues for perfectly structured point sets
    points = [
        (
            point[0] + random.uniform(-tiny, tiny),
            point[1] + random.uniform(-tiny, tiny),
            0.0,
        )
        for point in points
    ]

    # create super triangle
    pt1, pt2, pt3 = super_triangle(points)

    # add super triangle vertices to mesh
    n = len(points)
    super_keys = n, n + 1, n + 2

    mesh.add_vertex(super_keys[0], {"x": pt1[0], "y": pt1[1], "z": pt1[2]})
    mesh.add_vertex(super_keys[1], {"x": pt2[0], "y": pt2[1], "z": pt2[2]})
    mesh.add_vertex(super_keys[2], {"x": pt3[0], "y": pt3[1], "z": pt3[2]})

    mesh.add_face(super_keys)

    # iterate over points
    for key, point in enumerate(points):
        # newtris should be intialised here

        # check in which triangle this point falls
        for fkey in list(mesh.faces()):
            abc = mesh.face_coordinates(fkey)

            if is_point_in_triangle_xy(point, abc, True):
                # generate 3 new triangles (faces) and delete surrounding triangle
                key, newtris = mesh.insert_vertex(fkey, key=key, xyz=point, return_fkeys=True)
                break

        while newtris:
            fkey = newtris.pop()

            face = mesh.face_vertices(fkey)
            i = face.index(key)
            u = face[i - 2]
            v = face[i - 1]

            nbr = mesh.halfedge[v][u]

            if nbr is not None:
                a, b, c = mesh.face_coordinates(nbr)
                circle = circle_from_points_xy(a, b, c)

                if is_point_in_circle_xy(point, circle):
                    fkey, nbr = trimesh_swap_edge(mesh, (u, v))
                    newtris.append(fkey)
                    newtris.append(nbr)

    # Delete faces adjacent to supertriangle
    for key in super_keys:
        mesh.delete_vertex(key)

    # Delete faces outside of boundary
    if boundary:
        for fkey in list(mesh.faces()):
            centroid = mesh.face_centroid(fkey)
            if not is_point_in_polygon_xy(centroid, boundary):
                mesh.delete_face(fkey)

    # Delete faces inside of inside boundaries
    if holes:
        for polygon in holes:
            for fkey in list(mesh.faces()):
                centroid = mesh.face_centroid(fkey)
                if is_point_in_polygon_xy(centroid, polygon):
                    mesh.delete_face(fkey)

    return [mesh.face_vertices(fkey) for fkey in mesh.faces()]


# def voronoi_from_delaunay(delaunay):
#     """Construct the Voronoi dual of the triangulation of a set of points.

#     Parameters
#     ----------
#     delaunay : Mesh
#         A delaunay mesh.

#     Returns
#     -------
#     Mesh
#         The corresponding voronoi mesh.

#     Warnings
#     --------
#     This function does not work properly if all vertices of the delaunay
#     are on the boundary.

#     Examples
#     --------
#     .. plot::
#         :include-source:

#         from compas.datastructures import Mesh
#         from compas.datastructures import trimesh_remesh
#         from compas.geometry import delaunay_from_points
#         from compas.geometry import voronoi_from_delaunay
#         from compas.geometry import pointcloud_xy
#         from compas_plotters import MeshPlotter

#         points = pointcloud_xy(10, (0, 10))
#         faces = delaunay_from_points(points)
#         delaunay = Mesh.from_vertices_and_faces(points, faces)

#         trimesh_remesh(delaunay, 1.0, allow_boundary_split=True)

#         points = [delaunay.vertex_coordinates(key) for key in delaunay.vertices()]
#         faces = delaunay_from_points(points)
#         delaunay = Mesh.from_vertices_and_faces(points, faces)

#         voronoi = voronoi_from_delaunay(delaunay)

#         lines = []
#         for u, v in voronoi.edges():
#             lines.append({
#                 'start': voronoi.vertex_coordinates(u, 'xy'),
#                 'end'  : voronoi.vertex_coordinates(v, 'xy'),
#                 'width': 1.0
#             })

#         plotter = MeshPlotter(delaunay)

#         plotter.draw_lines(lines)

#         plotter.draw_vertices(
#             radius=0.075,
#             facecolor={key: '#0092d2' for key in delaunay.vertices() if key not in delaunay.vertices_on_boundary()})

#         plotter.draw_edges(color='#cccccc')

#         plotter.show()

#     """
#     voronoi = mesh_dual(delaunay)

#     for key in voronoi.vertices():
#         a, b, c = delaunay.face_coordinates(key)
#         center, radius, normal = circle_from_points_xy(a, b, c)
#         voronoi.vertex[key]['x'] = center[0]
#         voronoi.vertex[key]['y'] = center[1]
#         voronoi.vertex[key]['z'] = center[2]

#     return voronoi
