"""
interval.py
--------------

Deal with 1D intervals which are defined by:
  [start position, end position]
"""

import numpy as np


def check(a, b, digits):
    """
    Check input ranges, convert them to vector form,
    and get a fixed precision integer version of them.

    Parameters
    --------------
    a : (2, ) or (2, n) float
      Start and end of a 1D interval
    b : (2, ) or (2, n) float
      Start and end of a 1D interval
    digits : int
      How many digits to consider

    Returns
    --------------
    a : (2, n) float
      Ranges as vector
    b : (2, n) float
      Ranges as vector
    a_int : (2, n) int64
      Ranges rounded to digits, as vector
    b_int : (2, n) int64
      Ranges rounded to digits, as vector
    is_1D : bool
      If True, input was single pair of ranges
    """
    a = np.array(a, dtype=np.float64)
    b = np.array(b, dtype=np.float64)

    if a.shape != b.shape or a.shape[-1] != 2:
        raise ValueError('ranges must be identical and (2,)!')

    # if input was single interval reshape it here
    is_1D = False
    if len(a.shape) == 1:
        a = a.reshape((-1, 2))
        b = b.reshape((-1, 2))
        is_1D = True

    # make sure ranges are sorted
    a.sort(axis=1)
    b.sort(axis=1)

    # compare in fixed point as integers
    a_int = (a * 10**digits).round().astype(np.int64)
    b_int = (b * 10**digits).round().astype(np.int64)

    return a, b, a_int, b_int, is_1D


def intersection(a, b, digits=8):
    """
    Given a pair of ranges, merge them in to
    one range if they overlap at all

    Parameters
    --------------
    a : (2, ) float
      Start and end of a 1D interval
    b : (2, ) float
      Start and end of a 1D interval
    digits : int
      How many digits to consider

    Returns
    --------------
    intersects : bool or (n,) bool
      Indicates if the ranges overlap at all
    new_range : (2, ) or (2, 2) float
      The unioned range from the two inputs,
      or both of the original ranges if not overlapping
    """
    # check shape and convert
    a, b, a_int, b_int, is_1D = check(a, b, digits)

    # what are the starting and ending points of the overlap
    overlap = np.zeros(a.shape, dtype=np.float64)

    # A fully overlaps B
    current = np.logical_and(a_int[:, 0] <= b_int[:, 0],
                             a_int[:, 1] >= b_int[:, 1])
    overlap[current] = b[current]

    # B fully overlaps A
    current = np.logical_and(a_int[:, 0] >= b_int[:, 0],
                             a_int[:, 1] <= b_int[:, 1])
    overlap[current] = a[current]

    # A starts B ends
    # A:, 0   B:, 0     A:, 1        B:, 1
    current = np.logical_and(
        np.logical_and(a_int[:, 0] <= b_int[:, 0],
                       b_int[:, 0] < a_int[:, 1]),
        a_int[:, 1] < b_int[:, 1])
    overlap[current] = np.column_stack([b[current][:, 0],
                                        a[current][:, 1]])

    # B starts A ends
    # B:, 0  A:, 0    B:, 1  A:, 1
    current = np.logical_and(
        np.logical_and(b_int[:, 0] <= a_int[:, 0],
                       a_int[:, 0] < b_int[:, 1]),
        b_int[:, 1] < a_int[:, 1])
    overlap[current] = np.column_stack([a[current][:, 0],
                                        b[current][:, 1]])

    # is range overlapping at all
    intersects = overlap.ptp(axis=1) > 10**-digits

    if is_1D:
        return intersects[0], overlap[0]

    return intersects, overlap
