import cv2
import random
import numpy as np

from PIL import Image

def convert_OpenCV_to_PIL(image):
    return Image.fromarray(image[..., ::-1])

def convert_PIL_to_OpenCV(image):
    return np.asarray(image)[..., ::-1]

class RandomResize:
    def __init__(self, min_image_size, max_image_size):
        self.min_image_size = min_image_size
        self.max_image_size = max_image_size

        self.modes = [Image.BICUBIC, Image.NEAREST]
    
    def __call__(self, image, mode=Image.BICUBIC):
        rand_image_size = random.randint(self.min_image_size, self.max_image_size)
        
        w, h = image.size
        if w < h:
            scale = rand_image_size / h
        else:
            scale = rand_image_size / w

        size = (int(round(w*scale)), int(round(h*scale)))
        if size[0] == w and size[1] == h:
            return image

        return image.resize(size, mode)

class RandomResize_For_Segmentation:
    def __init__(self, min_image_size, max_image_size):
        self.min_image_size = min_image_size
        self.max_image_size = max_image_size
        
        self.modes = [Image.BICUBIC, Image.NEAREST]
    
    def __call__(self, data):
        image, mask = data['image'], data['mask']

        rand_image_size = random.randint(self.min_image_size, self.max_image_size)
        
        w, h = image.size
        if w < h:
            scale = rand_image_size / h
        else:
            scale = rand_image_size / w
        
        size = (int(round(w*scale)), int(round(h*scale)))
        if size[0] == w and size[1] == h:
            pass
        else:
            data['image'] = image.resize(size, Image.BICUBIC)
            data['mask'] = mask.resize(size, Image.NEAREST)

        return data

class RandomHorizontalFlip:
    def __init__(self):
        pass

    def __call__(self, image):
        if bool(random.getrandbits(1)):
            return image.transpose(Image.FLIP_LEFT_RIGHT)
        return image

class RandomHorizontalFlip_For_Segmentation:
    def __init__(self):
        pass

    def __call__(self, data):
        image, mask = data['image'], data['mask']

        if bool(random.getrandbits(1)):
            data['image'] = image.transpose(Image.FLIP_LEFT_RIGHT)
            data['mask'] = mask.transpose(Image.FLIP_LEFT_RIGHT)

        return data

class Normalize:
    def __init__(self, mean=(0.485, 0.456, 0.406), std=(0.229, 0.224, 0.225)):
        self.mean = mean
        self.std = std

    def __call__(self, image):
        image = np.asarray(image)
        norm_image = np.empty_like(image, np.float32)

        norm_image[..., 0] = (image[..., 0] / 255. - self.mean[0]) / self.std[0]
        norm_image[..., 1] = (image[..., 1] / 255. - self.mean[1]) / self.std[1]
        norm_image[..., 2] = (image[..., 2] / 255. - self.mean[2]) / self.std[2]
        
        return norm_image

class Normalize_For_Segmentation:
    def __init__(self, mean=(0.485, 0.456, 0.406), std=(0.229, 0.224, 0.225)):
        self.mean = mean
        self.std = std

    def __call__(self, data):
        image, mask = data['image'], data['mask']
        
        image = np.asarray(image, dtype=np.float32)
        mask = np.asarray(mask, dtype=np.int64)

        norm_image = np.empty_like(image, np.float32)

        norm_image[..., 0] = (image[..., 0] / 255. - self.mean[0]) / self.std[0]
        norm_image[..., 1] = (image[..., 1] / 255. - self.mean[1]) / self.std[1]
        norm_image[..., 2] = (image[..., 2] / 255. - self.mean[2]) / self.std[2]

        data['image'] = norm_image
        data['mask'] = mask

        return data

class Top_Left_Crop:
    def __init__(self, crop_size, channels=3):
        self.bg_value = 0
        self.crop_size = crop_size
        self.crop_shape = (self.crop_size, self.crop_size, channels)

    def __call__(self, image):
        h, w, c = image.shape

        ch = min(self.crop_size, h)
        cw = min(self.crop_size, w)

        cropped_image = np.ones(self.crop_shape, image.dtype) * self.bg_value
        cropped_image[:ch, :cw] = image[:ch, :cw]
        
        return cropped_image

class Top_Left_Crop_For_Segmentation:
    def __init__(self, crop_size, channels=3):
        self.bg_value = 0
        self.crop_size = crop_size
        self.crop_shape = (self.crop_size, self.crop_size, channels)
        self.crop_shape_for_mask = (self.crop_size, self.crop_size)

    def __call__(self, data):
        image, mask = data['image'], data['mask']

        h, w, c = image.shape

        ch = min(self.crop_size, h)
        cw = min(self.crop_size, w)

        cropped_image = np.ones(self.crop_shape, image.dtype) * self.bg_value
        cropped_image[:ch, :cw] = image[:ch, :cw]
        
        cropped_mask = np.ones(self.crop_shape_for_mask, mask.dtype) * 255
        cropped_mask[:ch, :cw] = mask[:ch, :cw]

        data['image'] = cropped_image
        data['mask'] = cropped_mask

        return data

class RandomCrop:
    def __init__(self, crop_size, channels=3, with_bbox=False):
        self.bg_value = 0
        self.with_bbox = with_bbox
        self.crop_size = crop_size
        self.crop_shape = (self.crop_size, self.crop_size, channels)

    def get_random_crop_box(self, image):
        h, w, c = image.shape

        ch = min(self.crop_size, h)
        cw = min(self.crop_size, w)

        w_space = w - self.crop_size
        h_space = h - self.crop_size

        if w_space > 0:
            cont_left = 0
            img_left = random.randrange(w_space + 1)
        else:
            cont_left = random.randrange(-w_space + 1)
            img_left = 0

        if h_space > 0:
            cont_top = 0
            img_top = random.randrange(h_space + 1)
        else:
            cont_top = random.randrange(-h_space + 1)
            img_top = 0

        dst_bbox = {
            'xmin' : cont_left, 'ymin' : cont_top,
            'xmax' : cont_left+cw, 'ymax' : cont_top+ch
        }
        src_bbox = {
            'xmin' : img_left, 'ymin' : img_top,
            'xmax' : img_left+cw, 'ymax' : img_top+ch
        }

        return dst_bbox, src_bbox
    
    def __call__(self, image, bbox_dic=None):
        if bbox_dic is None:
            dst_bbox, src_bbox = self.get_random_crop_box(image)
        else:
            dst_bbox, src_bbox = bbox_dic['dst_bbox'], bbox_dic['src_bbox']
        
        cropped_image = np.ones(self.crop_shape, image.dtype) * self.bg_value
        cropped_image[dst_bbox['ymin']:dst_bbox['ymax'], dst_bbox['xmin']:dst_bbox['xmax']] = \
            image[src_bbox['ymin']:src_bbox['ymax'], src_bbox['xmin']:src_bbox['xmax']]

        if self.with_bbox:
            return cropped_image, {'dst_bbox':dst_bbox, 'src_bbox':src_bbox}
        else:
            return cropped_image

class RandomCrop_For_Segmentation(RandomCrop):
    def __init__(self, crop_size):
        super().__init__(crop_size)

        self.crop_shape_for_mask = (self.crop_size, self.crop_size)

    def __call__(self, data):
        image, mask = data['image'], data['mask']

        dst_bbox, src_bbox = self.get_random_crop_box(image)
        
        cropped_image = np.ones(self.crop_shape, image.dtype) * self.bg_value
        cropped_image[dst_bbox['ymin']:dst_bbox['ymax'], dst_bbox['xmin']:dst_bbox['xmax']] = \
            image[src_bbox['ymin']:src_bbox['ymax'], src_bbox['xmin']:src_bbox['xmax']]

        cropped_mask = np.ones(self.crop_shape_for_mask, mask.dtype) * 255
        cropped_mask[dst_bbox['ymin']:dst_bbox['ymax'], dst_bbox['xmin']:dst_bbox['xmax']] = \
            mask[src_bbox['ymin']:src_bbox['ymax'], src_bbox['xmin']:src_bbox['xmax']]
        
        data['image'] = cropped_image
        data['mask'] = cropped_mask
        
        return data

class Transpose:
    def __init__(self):
        pass
        
    def __call__(self, image):
        return image.transpose((2, 0, 1))

class Transpose_For_Segmentation:
    def __init__(self):
        pass
        
    def __call__(self, data):
        # h, w, c -> c, h, w
        data['image'] = data['image'].transpose((2, 0, 1))
        return data

class Resize_For_Mask:
    def __init__(self, size):
        self.size = (size, size)
    
    def __call__(self, data):
        mask = Image.fromarray(data['mask'].astype(np.uint8))
        mask = mask.resize(self.size, Image.NEAREST)
        data['mask'] = np.asarray(mask, dtype=np.uint64)
        return data
