import numbers
import warnings

import khandy
import numpy as np


def crop_image(image, x_min, y_min, x_max, y_max, border_value=0):
    """Crop the given image at specified rectangular area.
    
    See Also:
        translate_image
        
    References:
        PIL.Image.crop
        tf.image.resize_image_with_crop_or_pad
    """
    assert khandy.is_numpy_image(image)
    assert isinstance(x_min, numbers.Integral) and isinstance(y_min, numbers.Integral)
    assert isinstance(x_max, numbers.Integral) and isinstance(y_max, numbers.Integral)
    assert (x_min < x_max) and (y_min < y_max)
    
    src_height, src_width = image.shape[:2]
    dst_height, dst_width = y_max - y_min, x_max - x_min
    channels = 1 if image.ndim == 2 else image.shape[2]
    
    if isinstance(border_value, (tuple, list)):
        assert len(border_value) == channels, \
            'Expected the num of elements in tuple equals the channels ' \
            'of input image. Found {} vs {}'.format(
                len(border_value), channels)
    else:
        border_value = (border_value,) * channels
    dst_image = khandy.create_solid_color_image(
        dst_width, dst_height, border_value, dtype=image.dtype)

    src_x_begin = max(x_min, 0)
    src_x_end   = min(x_max, src_width)
    dst_x_begin = src_x_begin - x_min
    dst_x_end   = src_x_end - x_min

    src_y_begin = max(y_min, 0)
    src_y_end   = min(y_max, src_height)
    dst_y_begin = src_y_begin - y_min
    dst_y_end   = src_y_end - y_min
    
    if (src_x_begin >= src_x_end) or (src_y_begin >= src_y_end):
        return dst_image
    dst_image[dst_y_begin: dst_y_end, dst_x_begin: dst_x_end, ...] = \
        image[src_y_begin: src_y_end, src_x_begin: src_x_end, ...]
    return dst_image
    

def crop_or_pad(image, x_min, y_min, x_max, y_max, border_value=0):
    warnings.warn('crop_or_pad will be deprecated, use crop_image instead!')
    return crop_image(image, x_min, y_min, x_max, y_max, border_value)


def crop_coords(boxes, image_width, image_height):
    """
    References:
        `mmcv.impad`
        `pad` in https://github.com/kpzhang93/MTCNN_face_detection_alignment
        `MtcnnDetector.pad` in https://github.com/AITTSMD/MTCNN-Tensorflow
    """
    x_mins = boxes[:, 0]
    y_mins = boxes[:, 1]
    x_maxs = boxes[:, 2]
    y_maxs = boxes[:, 3]
    dst_widths = x_maxs - x_mins
    dst_heights = y_maxs - y_mins
    
    src_x_begin = np.maximum(x_mins, 0)
    src_x_end   = np.minimum(x_maxs, image_width)
    dst_x_begin = src_x_begin - x_mins
    dst_x_end   = src_x_end - x_mins
    
    src_y_begin = np.maximum(y_mins, 0)
    src_y_end   = np.minimum(y_maxs, image_height)
    dst_y_begin = src_y_begin - y_mins
    dst_y_end   = src_y_end - y_mins

    coords = np.stack([dst_y_begin, dst_y_end, dst_x_begin, dst_x_end, 
                       src_y_begin, src_y_end, src_x_begin, src_x_end, 
                       dst_heights, dst_widths], axis=0)
    return coords
    

def crop_or_pad_coords(boxes, image_width, image_height):
    warnings.warn('crop_or_pad_coords will be deprecated, use crop_coords instead!')
    return crop_coords(boxes, image_width, image_height)


def center_crop_image(image, dst_width, dst_height, strict=True):
    """
    strict: 
        when True, raise error if src size is less than dst size. 
        when False, remain unchanged if src size is less than dst size, otherwise center crop.
    """
    assert khandy.is_numpy_image(image)
    assert isinstance(dst_width, numbers.Integral) and isinstance(dst_height, numbers.Integral)
    src_height, src_width = image.shape[:2]
    if strict:
        assert (src_height >= dst_height) and (src_width >= dst_width)

    crop_top = max((src_height - dst_height) // 2, 0)
    crop_left = max((src_width - dst_width) // 2, 0)
    cropped = image[crop_top: dst_height + crop_top, 
                    crop_left: dst_width + crop_left, ...]
    return cropped


def center_crop(image, dst_width, dst_height, strict=True):
    warnings.warn('center_crop will be deprecated, use center_crop_image instead!')
    return center_crop_image(image, dst_width, dst_height, strict)


def center_pad_image(image, dst_width, dst_height, strict=True):
    """
    strict: 
        when True, raise error if src size is greater than dst size. 
        when False, remain unchanged if src size is greater than dst size, otherwise center pad.
    """
    assert khandy.is_numpy_image(image)
    assert isinstance(dst_width, numbers.Integral) and isinstance(dst_height, numbers.Integral)
    
    src_height, src_width = image.shape[:2]
    if strict:
        assert (src_height <= dst_height) and (src_width <= dst_width)
    
    padding_x = max(dst_width - src_width, 0)
    padding_y = max(dst_height - src_height, 0)
    padding_top = padding_y // 2
    padding_left = padding_x // 2
    if image.ndim == 2:
        padding = ((padding_top, padding_y - padding_top), 
                   (padding_left, padding_x - padding_left))
    else:
        padding = ((padding_top, padding_y - padding_top), 
                   (padding_left, padding_x - padding_left), (0, 0))
    return np.pad(image, padding, 'constant')

    
def center_pad(image, dst_width, dst_height, strict=True):
    warnings.warn('center_pad will be deprecated, use center_pad_image instead!')
    return center_pad_image(image, dst_width, dst_height, strict)
