import numbers
import numpy as np

LIMIT = 99999999


def clip_bbox(bboxes, min_clip, max_x_clip, max_y_clip):
    '''
    # BBoxes are [x1, y1, x2, y2]
    '''
    bboxes_out = bboxes
    added_axis = False
    if len(bboxes_out.shape) == 1:
        added_axis = True
        bboxes_out = bboxes_out[:, np.newaxis]
    bboxes_out[[0, 2], ...] = np.clip(bboxes_out[[0, 2], ...], min_clip, max_x_clip)
    bboxes_out[[1, 3], ...] = np.clip(bboxes_out[[1, 3], ...], min_clip, max_y_clip)
    if added_axis:
        bboxes_out = bboxes_out[:, 0]
    return bboxes_out


def xyxy_to_xywh(bboxes, clip_min=-LIMIT, clip_width=LIMIT, clip_height=LIMIT, round=False):
    '''
    [x1 y1, x2, y2] to [xMid, yMid, width, height]
    '''
    added_axis = False
    if isinstance(bboxes, list):
        bboxes = np.array(bboxes).astype(np.float32)
    if len(bboxes.shape) == 1:
        added_axis = True
        bboxes = bboxes[:, np.newaxis]
    bboxes_out = np.zeros(bboxes.shape)
    x1 = bboxes[0, ...]
    y1 = bboxes[1, ...]
    x2 = bboxes[2, ...]
    y2 = bboxes[3, ...]
    bboxes_out[0, ...] = (x1 + x2) / 2.0
    bboxes_out[1, ...] = (y1 + y2) / 2.0
    bboxes_out[2, ...] = x2 - x1
    bboxes_out[3, ...] = y2 - y1
    if clip_min != -LIMIT or clip_width != LIMIT or clip_height != LIMIT:
        bboxes_out = clip_bbox(bboxes_out, clip_min, clip_width, clip_height)
    if bboxes_out.shape[0] > 4:
        bboxes_out[4:, ...] = bboxes[4:, ...]
    if added_axis:
        bboxes_out = bboxes_out[:, 0]
    if round:
        bboxes_out = np.round(bboxes_out).astype(int)
    return bboxes_out


def xywh_to_xyxy(bboxes, clip_min=-LIMIT, clip_width=LIMIT, clip_height=LIMIT, round=False):
    '''
    [xMid, yMid, width, height] to [x1 y1, x2, y2]
    '''
    added_axis = False
    if isinstance(bboxes, list):
        bboxes = np.array(bboxes).astype(np.float32)
    if len(bboxes.shape) == 1:
        added_axis = True
        bboxes = bboxes[:, np.newaxis]
    bboxes_out = np.zeros(bboxes.shape)
    x_mid = bboxes[0, ...]
    y_mid = bboxes[1, ...]
    width = bboxes[2, ...]
    height = bboxes[3, ...]
    bboxes_out[0, ...] = x_mid - width / 2.0
    bboxes_out[1, ...] = y_mid - height / 2.0
    bboxes_out[2, ...] = x_mid + width / 2.0
    bboxes_out[3, ...] = y_mid + height / 2.0
    if clip_min != -LIMIT or clip_width != LIMIT or clip_height != LIMIT:
        bboxes_out = clip_bbox(bboxes_out, clip_min, clip_width, clip_height)
    if bboxes_out.shape[0] > 4:
        bboxes_out[4:, ...] = bboxes[4:, ...]
    if added_axis:
        bboxes_out = bboxes_out[:, 0]
    if round:
        bboxes_out = np.round(bboxes_out).astype(int)
    return bboxes_out


def scale_bbox(bboxes, scalars, clip_min=-LIMIT, clip_width=LIMIT, clip_height=LIMIT, round=False, in_place=False):
    '''
    @bboxes {np.array} 4xn array of boxes to be scaled
    @scalars{number or arraylike} scalars for width and height of boxes
    @in_place{bool} If false, creates new bboxes.
    '''
    added_axis = False
    if isinstance(bboxes, list):
        bboxes = np.array(bboxes, dtype=np.float32)
    if len(bboxes.shape) == 1:
        added_axis = True
        bboxes = bboxes[:, np.newaxis]
    if isinstance(scalars, numbers.Number):
        scalars = np.full((2, bboxes.shape[1]), scalars, dtype=np.float32)
    if not isinstance(scalars, np.ndarray):
        scalars = np.array(scalars, dtype=np.float32)
    if len(scalars.shape) == 1:
        scalars = np.tile(scalars[:, np.newaxis], (1, bboxes.shape[1]))

    width = bboxes[2, ...] - bboxes[0, ...]
    height = bboxes[3, ...] - bboxes[1, ...]
    x_mid = (bboxes[0, ...] + bboxes[2, ...]) / 2.0
    y_mid = (bboxes[1, ...] + bboxes[3, ...]) / 2.0
    if not in_place:
        bboxes_out = bboxes.copy()
    else:
        bboxes_out = bboxes

    bboxes_out[0, ...] = x_mid - width * scalars[0, ...] / 2.0
    bboxes_out[1, ...] = y_mid - height * scalars[1, ...] / 2.0
    bboxes_out[2, ...] = x_mid + width * scalars[0, ...] / 2.0
    bboxes_out[3, ...] = y_mid + height * scalars[1, ...] / 2.0

    if clip_min != -LIMIT or clip_width != LIMIT or clip_height != LIMIT:
        bboxes_out = clip_bbox(bboxes_out, clip_min, clip_width, clip_height)
    if added_axis:
        bboxes_out = bboxes_out[:, 0]
    if round:
        bboxes_out = np.round(bboxes_out).astype(int)
    return bboxes_out


def make_square(bboxes, in_place=False):
    if isinstance(bboxes, list):
        bboxes = np.array(bboxes).astype(np.float32)
    if len(bboxes.shape) == 1:
        num_boxes = 1
        width = bboxes[2] - bboxes[0]
        height = bboxes[3] - bboxes[1]
    else:
        num_boxes = bboxes.shape[1]
        width = bboxes[2, ...] - bboxes[0, ...]
        height = bboxes[3, ...] - bboxes[1, ...]
    max_size = np.maximum(width, height)
    scalars = np.zeros((2, num_boxes))
    scalars[0, ...] = max_size * 1.0 / width
    scalars[1, ...] = max_size * 1.0 / height
    return scale_bbox(bboxes, scalars, in_place=in_place)
