# ##### 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 ointersect_pointion) 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 #####
#
# <pep8 compliant>
#
# ----------------------------------------------------------
# Author: Zeffii
# Modified by: Alan Odom (Clockmender) & Rune Morling (ermo)
# ----------------------------------------------------------
#
import bmesh
from mathutils import Vector
from mathutils.geometry import intersect_line_line, intersect_point_line
from .pdt_functions import debug


def point_on_edge(point, edge):
    """Find Point on Edge.

    Args:
        point:    vector
        edge:     tuple containing 2 vectors.

    Returns:
        True if point happens to lie on the edge, False otherwise.
    """

    intersect_point, _percent = intersect_point_line(point, *edge)
    on_line = (intersect_point - point).length < 1.0e-5
    return on_line and (0.0 <= _percent <= 1.0)


def line_from_edge_intersect(edge1, edge2):
    """Get New Line from Intersections.

    Note:
        Prepares input for sending to intersect_line_line

    Args:
        edge1, edge2: tuples containing 2 vectors.

    Returns:
        Output of intersect_line_line.
    """

    [intersect_point1, intersect_point2], [intersect_point3, intersect_point4] = edge1, edge2
    return intersect_line_line(
        intersect_point1, intersect_point2, intersect_point3, intersect_point4
    )


def get_intersection(edge1, edge2):
    """Get Intersections of 2 Edges.

    Args:
        edge1, edge2: tuples containing 2 vectors.

    Returns:
        The point halfway on line. See intersect_line_line.
    """

    line = line_from_edge_intersect(edge1, edge2)
    if line:
        return (line[0] + line[1]) / 2
    return None


def test_coplanar(edge1, edge2):
    """Test 2 Edges are Co-planar.

    Note:
        The line that describes the shortest line between the two edges would be short if the
        lines intersect mathematically. If this line is longer than 1.0e-5 then they are either
        coplanar or parallel

    Args:
        edge1, edge2: tuples containing 2 vectors.

    Returns:
        True if edge1 and edge2 or coplanar, False otherwise.
    """

    line = line_from_edge_intersect(edge1, edge2)
    if line:
        return (line[0] - line[1]).length < 1.0e-5
    return None


def closest_idx(intersect_point, edge):
    """Get Closest Vertex to input point.

    Note:
        If both points in edge are equally far from intersect_point, then v1 is returned.

    Args:
        intersect_point:       vector
        edge:        bmesh edge

    Returns:
        Index of vertex closest to intersect_point.
    """

    if isinstance(edge, bmesh.types.BMEdge):
        edge_verts = edge.verts
        vector_a = edge_verts[0].co
        vector_b = edge_verts[1].co
        distance_test = (vector_a - intersect_point).length <= (vector_b - intersect_point).length
        return edge_verts[0].index if distance_test else edge_verts[1].index

    debug(f"Received {edge}, check expected input in docstring ")
    return None


def closest_vector(intersect_point, edge):
    """Return Closest Vector to input Point.

    Note:
        If both points in e are equally far from intersect_point, then v1 is returned.

    Args:
        intersect_point:       vector
        edge:        tuple containing 2 vectors

    Returns:
        Vector closest to intersect_point.
    """

    if isinstance(edge, tuple) and all([isinstance(co, Vector) for co in edge]):
        vector_a, vector_b = edge
        distance_test = (vector_a - intersect_point).length <= (vector_b - intersect_point).length
        return vector_a if distance_test else vector_b

    debug(f"Received {edge}, check expected input in docstring ")
    return None


def coords_tuple_from_edge_idx(bm, idx):
    """Return Tuple from Vertices.

    Args:
        bm: Object Bmesh
        idx: Index of chosen Edge

    Returns:
        Tuple from Edge Vertices.
    """

    return tuple(v.co for v in bm.edges[idx].verts)


def vectors_from_indices(bm, raw_vert_indices):
    """Return List of vectors from input Vertex Indices.

    Args:
        bm: Object Bmesh
        raw_vert_indices: List of Chosen Vertex Indices

    Returns:
        List of Vertex coordinates.
    """

    return [bm.verts[i].co for i in raw_vert_indices]


def vertex_indices_from_edges_tuple(bm, edge_tuple):
    """Return List of vertices.

    Args:
        bm:           Active object's Bmesh
        edge_tuple:   contains 2 edge indices.

    Returns:
        The vertex indices of edge_tuple as an Integer list.
    """

    def find_verts(ind_v, ind_w):
        return bm.edges[edge_tuple[ind_v]].verts[ind_w].index

    return [find_verts(i >> 1, i % 2) for i in range(4)]


def get_vert_indices_from_bmedges(edges):
    """Return List of Edges for evaluation.

    Args:
        edges:      a list of 2 bm edges

    Returns:
        The vertex indices of edge_tuple as a flat list.
    """

    temp_edges = []
    debug(edges)
    for e in edges:
        for v in e.verts:
            temp_edges.append(v.index)
    return temp_edges


def num_edges_point_lies_on(intersect_point, edges):
    """Returns the number of edges that a point lies on.

    Args:
        intersection_point: Vector describing 3D coordinates of intersection point
        edges: List of Bmesh edges

    Returns:
        Number of Intersecting Edges (Integer).
    """

    res = [point_on_edge(intersect_point, edge) for edge in [edges[:2], edges[2:]]]
    return len([i for i in res if i])


def find_intersecting_edges(bm, intersect_point, idx1, idx2):
    """Find Intercecting Edges.

    Args:
        intersect_point: Vector describing 3D coordinates of intersection point
        idx1, idx2:    edge indices

    Returns:
        The list of edge indices where intersect_point is on those edges.
    """

    if not intersect_point:
        return []
    idxs = [idx1, idx2]
    edges = [coords_tuple_from_edge_idx(bm, idx) for idx in idxs]
    return [idx for edge, idx in zip(edges, idxs) if point_on_edge(intersect_point, edge)]


def vert_idxs_from_edge_idx(bm, idx):
    """Find Vertex Indices form Edge Indices.

    Args:
        bm: Object's Bmesh
        idx: Selection Index

    Returns:
        Vertex Indices of Edge.
    """

    edge = bm.edges[idx]
    return edge.verts[0].index, edge.verts[1].index
