"""
Utilities.py Software that is used to support WraptMor.

Copyright 2020 William Zaylor and Jason P. Halloran

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 3 of the License, or
(at your option) 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, see <https://www.gnu.org/licenses/>.
"""

import numpy as np
import vtk

def loadInsertionPoints3DSlicer(fileName):
    """
    Parse insertion points from the given file.

    .. NOTE:: It is assumed that this file has a format similar to that used by 3D Slicer (https://www.slicer.org/).
    It is assumed that the (x,y,z) coordinates are in columns (1,2,3) of the file.
    It is also assumed that the point associated with surfaceA is on the fourth lines of the file, and the point associated with surfaceB is on the fifth line of the file.

    :param fileName: string, The name of comma separated file containing the insertion points.
    :return: array 2x3, The coordinates of the insertion points that are associated with surfaceA and surfaceB (in that order).
    """
    insertionPoints = np.genfromtxt(fileName, delimiter=',', skip_header=3, usecols=(1,2,3))
    return insertionPoints

def loadStl(fileName):
    """
    Load the given surface (.stl) file

    :param fileName: string, The name of the surface (.stl) file that is being loaded.
    :return: vtkPolyData, The surface in the vtkPolyData format.
    """
    reader = vtk.vtkSTLReader()
    reader.SetFileName(fileName)
    reader.Update()
    polydata = reader.GetOutput()
    return polydata

def getClippedSurface(point0, point1, surface, clippingDist):
    """
    This function clips the given surface between point0 and point1.
    The clipping plane is ``clippingDist`` (in mm) away from point0, towards point1. This plane cuts ``surface``.

    ..NOTE:: It is assumed that ``point0``, ``point1``, and ``surface`` all defined with respect to the same coordinate system.

    :param point0: array 1x3, The coordinates of a point that is associated with the given surface. The surface will be clipped ``clippingDist`` away from point0 (and towards point1)
    :param point1: array 1x3, The coordinates of a point that is used to define the clipping plane.
    :param surface: vtkPolyData, The surface that is being clipped.
    :param clippingDist: float, The distance away from point0 (and towards point1) that the clipping surface is defined.
    :return: vtkPolyData,  The clipped surface.
    """
    planeNormal = (point1 - point0) / np.linalg.norm(point1 - point0) # The normal of the clipping plane
    planeOrigin = point0 + clippingDist*planeNormal  # Move the cut plane ``clippingDist`` above point0, towards point1

    clippingPlane = vtk.vtkPlane()
    clippingPlane.SetNormal(planeNormal)
    clippingPlane.SetOrigin(planeOrigin)

    clipper = vtk.vtkClipPolyData()
    clipper.SetInputData(surface)
    clipper.SetClipFunction(clippingPlane)
    # clipper.InsideOutOn()
    clipper.Update()
    clippedSurface = clipper.GetOutput()
    return clippedSurface

def checkIntersection(point0, point1, surface, obbTree=None):
    """
    Check if the line connecting ``point0`` to ``point1`` intersects the given ``surface``.

    .. NOTE:: ``obbTree`` is an optional input. This can be defined before this function is called to reduce the amount of time it takes to evaluate this function.

    :param point0: array 1x3, The coordinates of a point that is used to define the line. The line that is defined is used to check if it intersects the given surface.
    :param point1: array 1x3, The coordinates of a point that is used to define the line. The line that is defined is used to check if it intersects the given surface.
    :param surface: vtkPolyData, The surface that is used to check if there is an intersection with a line that connects ``point0`` and ``point1``.
    :param obbTree: vtkOBBTree, The tree that is used to check if the line connecting the given points passes through the surface.
    :return: bool , Returns True if there is an intersection with ``surface``, and False if there is an intersection with ``surface``
    """
    if obbTree is None:
        obbTree = vtk.vtkOBBTree()
        obbTree.SetDataSet(surface)
        obbTree.BuildLocator()

    pointsNull = vtk.vtkPoints()
    intersection = obbTree.IntersectWithLine(point0, point1, pointsNull, None)

    if intersection == 1 or intersection == -1:
        return True
    else:
        return False