import numpy as np
import overlap
import logging

from polygon_triangulate import polygon_triangulate, PlotTriangulation
from tetrahedralize_triangles import tetrahedralize

logging.basicConfig(filename='warnings.log', level=logging.DEBUG)

def measure_volume(V_tet, T, origin, r):

    sphere = overlap.Sphere(origin, r)
    volume = 0.0

    volumes = []

    for tet in T:
        vertices = V_tet[tet]
        overlap_tet = overlap.Tetrahedron(vertices)
        overlap_volume = overlap.overlap(sphere, overlap_tet)
        volumes.append(overlap_volume)
        if overlap_volume < 0:
            print('Encountered negative volume overlap')
            logging.warning('Encountered negative volume overlap')
        volume += overlap_volume

    #import matplotlib.pyplot as plt
    #plt.title(f'radius {r} total {volume}')
    #plt.plot(np.arange(len(volumes)), volumes)
    #plt.show()

    return volume

def same_point(x, y):
    return np.linalg.norm(x - y) < 1e-10

def remove_duplicate_contour_points(P):
    n = len(P)
    for i in range(n):
        j = (i + 1) % n
        if same_point(P[i], P[j]):
            return remove_duplicate_contour_points(np.delete(P, i, axis=0))
    return P

def remove_line_triangles(P, F):
    n = len(F)
    for i in range(n-1, -1, -1):
        PF = P[F[i]]
        v1 = PF[0] - PF[1]
        v2 = PF[1] - PF[2]
        if np.isclose(np.abs(np.dot(v1/np.linalg.norm(v1),v2/np.linalg.norm(v2))),1):
            F = np.delete(F, i, axis=0)
    return F

def measure_cumulative_volume(z, P, thickness, origin, R):
    '''
    Integration of the intersecting volume of a thick polygon and a sphere of increasing radius.

    Parameters
        ----------
        z : float
            The z depth-wise coordinate of the thick polygon.
        P : (N, 2) array_like
            A real array of ordered contour points of the polygon.
        thickness : float
            The thickness of the polygon.
        origin : (3,) array_like
            The center of the sphere from which to integrate from.
        R : (M,) array_like
            The discrete sphere radii from which to measure the polygon in.
        
        Returns
        -------
        mu: (M,) array_like
            The intersecting volumes of the sphere and the thick polygon at radii determined by R.
    '''

    P = np.array(P)
    origin = np.array(origin)

    P = remove_duplicate_contour_points(P)

    z0 = z - thickness / 2
    z1 = z + thickness / 2
    _, V_tri, F = polygon_triangulate(P)

    F = remove_line_triangles(V_tri, F)

    #import matplotlib.pyplot as plt
    #print(V_tri[:5,:])
    #PlotTriangulation(V_tri, F)
    #plt.show()

    V_tet, T = tetrahedralize(V_tri, F, z0, z1)

    mu = np.zeros(len(R))
    for i, r in enumerate(R):

        if r <= 0:
            continue

        mu[i] = measure_volume(V_tet, T, origin, r)

    return mu

def measure_cumulative_points(z, X, thickness, origin, R):
    '''
    Integration of the points placed in a thick section integrated intersecting with a sphere of increasing size.

    Parameters
        ----------
        z : float
            The z depth-wise coordinate of the section with the points.
        X : (N, 2) array_like
            A real array of points inside the section.
        thickness : float
            The thickness of the section of which the points are in.
        origin : (3,) array_like
            The center of the sphere from which to integrate from.
        R : (M,) array_like
            The discrete sphere radii from which to measure the points inside.
        
        Returns
        -------
        mu: (M,) array_like
            The integrated point masses inside the sphere of radii determined by R.
    '''

    oz = origin[2]
    oxy = origin[:2]

    dxy = np.array([np.linalg.norm(x - oxy) for x in X])

    mu = np.zeros(len(R))

    if z == oz:
        for i, r in enumerate(R):
            s = np.sqrt(np.clip(r**2 - dxy**2, 0, None))
            mu[i] = np.sum(np.clip(2*s/thickness, 0, 1))

        return mu

    # plane is below, mirror it above to simplify calculations
    if z < oz:
        z = z + 2*(oz - z)

    z0 = z - thickness / 2
    z1 = z + thickness / 2

    d0 = z0 - oz

    for i, r in enumerate(R):
        h = np.sqrt(np.clip(r**2 - dxy**2, 0, None))
        s = h - d0
        mu[i] = np.sum(np.clip(s/thickness, 0, 1))

    return mu