import numpy as np
from blmath.numerics import vx

def apex(points, axis):
    '''
    Find the most extreme point in the direction of the axis provided.

    axis: A vector, which is an 3x1 np.array.

    '''
    coords_on_axis = points.dot(axis)
    return points[np.argmax(coords_on_axis)]

def inflection_points(points, axis, span):
    '''
    Find the list of vertices that preceed inflection points in a curve. The curve is differentiated
    with respect to the coordinate system defined by axis and span.

    axis: A vector representing the vertical axis of the coordinate system.
    span: A vector representing the the horiztonal axis of the coordinate system.

    returns: a list of points in space corresponding to the vertices that
    immediately preceed inflection points in the curve
    '''

    coords_on_span = points.dot(span)
    dx = np.gradient(coords_on_span)
    coords_on_axis = points.dot(axis)

    # Take the second order finite difference of the curve with respect to the
    # defined coordinate system
    finite_difference_2 = np.gradient(np.gradient(coords_on_axis, dx), dx)

    # Compare the product of all neighboring pairs of points in the second derivative
    # If a pair of points has a negative product, then the second derivative changes sign
    # at one of those points, signalling an inflection point
    is_inflection_point = [finite_difference_2[i] * finite_difference_2[i + 1] <= 0 for i in range(len(finite_difference_2) - 1)]

    inflection_point_indices = [i for i, b in enumerate(is_inflection_point) if b]

    if len(inflection_point_indices) == 0: # pylint: disable=len-as-condition
        return []

    return points[inflection_point_indices]

def farthest(from_point, to_points):
    '''
    Find the farthest point among the inputs, to the given point.

    Return a tuple: farthest_point, index_of_farthest_point.
    '''
    absolute_distances = vx.magnitude(to_points - from_point)

    index_of_farthest_point = np.argmax(absolute_distances)
    farthest_point = to_points[index_of_farthest_point]

    return farthest_point, index_of_farthest_point

[((27, 9, 27, 36), 'numpy.gradient', 'np.gradient', ({(27, 21, 27, 35): 'coords_on_span'}, {}), '(coords_on_span)', True, 'import numpy as np\n'), ((52, 25, 52, 61), 'blmath.numerics.vx.magnitude', 'vx.magnitude', ({(52, 38, 52, 60): 'to_points - from_point'}, {}), '(to_points - from_point)', False, 'from blmath.numerics import vx\n'), ((54, 30, 54, 59), 'numpy.argmax', 'np.argmax', ({(54, 40, 54, 58): 'absolute_distances'}, {}), '(absolute_distances)', True, 'import numpy as np\n'), ((32, 38, 32, 69), 'numpy.gradient', 'np.gradient', ({(32, 50, 32, 64): 'coords_on_axis', (32, 66, 32, 68): 'dx'}, {}), '(coords_on_axis, dx)', True, 'import numpy as np\n'), ((12, 18, 12, 43), 'numpy.argmax', 'np.argmax', ({(12, 28, 12, 42): 'coords_on_axis'}, {}), '(coords_on_axis)', True, 'import numpy as np\n')]