import warnings

import cv2
import khandy
import numpy as np


interp_codes = {
    'nearest': cv2.INTER_NEAREST,
    'bilinear': cv2.INTER_LINEAR,
    'bicubic': cv2.INTER_CUBIC,
    'area': cv2.INTER_AREA,
    'lanczos': cv2.INTER_LANCZOS4
}


def scale_image(image, x_scale, y_scale, interpolation='bilinear'):
    """Scale image.
    
    Reference:
        mmcv.imrescale
    """
    assert khandy.is_numpy_image(image)
    src_height, src_width = image.shape[:2]
    dst_width = int(round(x_scale * src_width))
    dst_height = int(round(y_scale * src_height))
    
    resized_image = cv2.resize(image, (dst_width, dst_height), 
                               interpolation=interp_codes[interpolation])
    return resized_image


def resize_image(image, dst_width, dst_height, return_scale=False, interpolation='bilinear'):
    """Resize image to a given size.

    Args:
        image (ndarray): The input image.
        dst_width (int): Target width.
        dst_height (int): Target height.
        return_scale (bool): Whether to return `x_scale` and `y_scale`.
        interpolation (str): Interpolation method, accepted values are
            "nearest", "bilinear", "bicubic", "area", "lanczos".

    Returns:
        tuple or ndarray: (`resized_image`, `x_scale`, `y_scale`) or `resized_image`.
        
    Reference:
        mmcv.imresize
    """
    assert khandy.is_numpy_image(image)
    resized_image = cv2.resize(image, (dst_width, dst_height), 
                               interpolation=interp_codes[interpolation])
    if not return_scale:
        return resized_image
    else:
        src_height, src_width = image.shape[:2]
        x_scale = dst_width / src_width
        y_scale = dst_height / src_height
        return resized_image, x_scale, y_scale
    
    
def resize_image_short(image, dst_size, return_scale=False, interpolation='bilinear'):
    """Resize an image so that the length of shorter side is dst_size while 
    preserving the original aspect ratio.
    
    References:
        `resize_min` in `https://github.com/pjreddie/darknet/blob/master/src/image.c`
    """
    assert khandy.is_numpy_image(image)
    src_height, src_width = image.shape[:2]
    scale = max(dst_size / src_width, dst_size / src_height)
    dst_width = int(round(scale * src_width))
    dst_height = int(round(scale * src_height))
    
    resized_image = cv2.resize(image, (dst_width, dst_height), 
                               interpolation=interp_codes[interpolation])
    if not return_scale:
        return resized_image
    else:
        return resized_image, scale
    
    
def resize_image_long(image, dst_size, return_scale=False, interpolation='bilinear'):
    """Resize an image so that the length of longer side is dst_size while 
    preserving the original aspect ratio.
    
    References:
        `resize_max` in `https://github.com/pjreddie/darknet/blob/master/src/image.c`
    """
    assert khandy.is_numpy_image(image)
    src_height, src_width = image.shape[:2]
    scale = min(dst_size / src_width, dst_size / src_height)
    dst_width = int(round(scale * src_width))
    dst_height = int(round(scale * src_height))
    
    resized_image = cv2.resize(image, (dst_width, dst_height), 
                               interpolation=interp_codes[interpolation])
    if not return_scale:
        return resized_image
    else:
        return resized_image, scale
        
        
def resize_image_to_range(image, min_length, max_length, return_scale=False, interpolation='bilinear'):
    """Resizes an image so its dimensions are within the provided value.
    
    Rescale the shortest side of the image up to `min_length` pixels 
    while keeping the largest side below `max_length` pixels without 
    changing the aspect ratio. Often used in object detection (e.g. RCNN and SSH.)
    
    The output size can be described by two cases:
    1. If the image can be rescaled so its shortest side is equal to the
        `min_length` without the other side exceeding `max_length`, then do so.
    2. Otherwise, resize so the longest side is equal to `max_length`.
    
    Returns:
        resized_image: resized image so that
            min(dst_height, dst_width) == min_length or
            max(dst_height, dst_width) == max_length.
          
    References:
        `resize_to_range` in `models-master/research/object_detection/core/preprocessor.py`
        `prep_im_for_blob` in `py-faster-rcnn-master/lib/utils/blob.py`
        mmcv.imrescale
    """
    assert khandy.is_numpy_image(image)
    assert min_length < max_length
    src_height, src_width = image.shape[:2]
    
    min_side_length = min(src_width, src_height)
    max_side_length = max(src_width, src_height)
    scale = min_length / min_side_length
    if round(scale * max_side_length) > max_length:
        scale = max_length / max_side_length
    dst_width = int(round(scale * src_width))
    dst_height = int(round(scale * src_height))
    
    resized_image = cv2.resize(image, (dst_width, dst_height), 
                               interpolation=interp_codes[interpolation])
    if not return_scale:
        return resized_image
    else:
        return resized_image, scale
        
        
def letterbox_image(image, dst_width, dst_height, border_value=0,
                    return_scale=False, interpolation='bilinear'):
    """Resize an image preserving the original aspect ratio using padding.
    
    References:
        `letterbox_image` in `https://github.com/pjreddie/darknet/blob/master/src/image.c`
    """
    assert khandy.is_numpy_image(image)
    src_height, src_width = image.shape[:2]
    scale = min(dst_width / src_width, dst_height / src_height)
    resize_w = int(round(scale * src_width))
    resize_h = int(round(scale * src_height))

    resized_image = cv2.resize(image, (resize_w, resize_h), 
                               interpolation=interp_codes[interpolation])
    pad_top = (dst_height - resize_h) // 2
    pad_bottom = (dst_height - resize_h) - pad_top
    pad_left = (dst_width - resize_w) // 2
    pad_right = (dst_width - resize_w) - pad_left
    padded_image = cv2.copyMakeBorder(resized_image, pad_top, pad_bottom, pad_left, pad_right, 
                                      cv2.BORDER_CONSTANT, value=border_value)
    if not return_scale:
        return padded_image
    else:
        return padded_image, scale, pad_left, pad_top
        

def letterbox_resize_image(image, dst_width, dst_height, border_value=0,
                           return_scale=False, interpolation='bilinear'):
    warnings.warn('letterbox_resize_image will be deprecated, use letterbox_image instead!')
    return letterbox_image(image, dst_width, dst_height, border_value,
                           return_scale, interpolation)
