import base64
import collections.abc
from itertools import combinations
from typing import Union, List, Tuple, Callable

import numpy as np
import cv2
import paddle
import paddle.nn.functional as F


def get_reverse_list(ori_shape: list, transforms: Callable) -> list:
    """
    get reverse list of transform.

    Args:
        ori_shape (list): Origin shape of image.
        transforms (list): List of transform.

    Returns:
        list: List of tuple, there are two format:
            ('resize', (h, w)) The image shape before resize,
            ('padding', (h, w)) The image shape before padding.
    """
    reverse_list = []
    h, w = ori_shape[0], ori_shape[1]
    for op in transforms:
        if op.__class__.__name__ in ['Resize']:
            reverse_list.append(('resize', (h, w)))
            h, w = op.target_size[0], op.target_size[1]
        if op.__class__.__name__ in ['Crop']:
            reverse_list.append(('crop', (op.up_h_off, op.down_h_off),
                                 (op.left_w_off, op.right_w_off)))
            h = h - op.up_h_off
            h = h - op.down_h_off
            w = w - op.left_w_off
            w = w - op.right_w_off
        if op.__class__.__name__ in ['ResizeByLong']:
            reverse_list.append(('resize', (h, w)))
            long_edge = max(h, w)
            short_edge = min(h, w)
            short_edge = int(round(short_edge * op.long_size / long_edge))
            long_edge = op.long_size
            if h > w:
                h = long_edge
                w = short_edge
            else:
                w = long_edge
                h = short_edge
        if op.__class__.__name__ in ['ResizeByShort']:
            reverse_list.append(('resize', (h, w)))
            long_edge = max(h, w)
            short_edge = min(h, w)
            long_edge = int(round(long_edge * op.short_size / short_edge))
            short_edge = op.short_size
            if h > w:
                h = long_edge
                w = short_edge
            else:
                w = long_edge
                h = short_edge
        if op.__class__.__name__ in ['Padding']:
            reverse_list.append(('padding', (h, w)))
            w, h = op.target_size[0], op.target_size[1]
        if op.__class__.__name__ in ['PaddingByAspectRatio']:
            reverse_list.append(('padding', (h, w)))
            ratio = w / h
            if ratio == op.aspect_ratio:
                pass
            elif ratio > op.aspect_ratio:
                h = int(w / op.aspect_ratio)
            else:
                w = int(h * op.aspect_ratio)
        if op.__class__.__name__ in ['LimitLong']:
            long_edge = max(h, w)
            short_edge = min(h, w)
            if ((op.max_long is not None) and (long_edge > op.max_long)):
                reverse_list.append(('resize', (h, w)))
                long_edge = op.max_long
                short_edge = int(round(short_edge * op.max_long / long_edge))
            elif ((op.min_long is not None) and (long_edge < op.min_long)):
                reverse_list.append(('resize', (h, w)))
                long_edge = op.min_long
                short_edge = int(round(short_edge * op.min_long / long_edge))
            if h > w:
                h = long_edge
                w = short_edge
            else:
                w = long_edge
                h = short_edge
    return reverse_list


def reverse_transform(pred: paddle.Tensor, ori_shape: list, transforms: Callable, mode: str = 'nearest') -> paddle.Tensor:
    """recover pred to origin shape"""
    reverse_list = get_reverse_list(ori_shape, transforms)
    for item in reverse_list[::-1]:
        if item[0] == 'resize':
            h, w = item[1][0], item[1][1]
            # if paddle.get_device() == 'cpu':
            #     pred = paddle.cast(pred, 'uint8')
            #     pred = F.interpolate(pred, (h, w), mode=mode)
            #     pred = paddle.cast(pred, 'int32')
            # else:
            pred = F.interpolate(pred, (h, w), mode=mode)
        elif item[0] == 'crop':
            up_h_off, down_h_off = item[1][0], item[1][1]
            left_w_off, right_w_off = item[2][0], item[2][1]
            pred = F.pad(
                pred, [left_w_off, right_w_off, up_h_off, down_h_off],
                value=0,
                mode='constant',
                data_format="NCHW")
        elif item[0] == 'padding':
            h, w = item[1][0], item[1][1]
            pred = pred[:, :, 0:h, 0:w]
        else:
            raise Exception("Unexpected info '{}' in im_info".format(item[0]))
    return pred


class Crop:
    """
    crop an image from four forwards.

    Args:
        up_h_off (int, optional): The cut height for image from up to down. Default: 0.
        down_h_off (int, optional): The cut height for image from down to up . Default: 0.
        left_w_off (int, optional): The cut height for image from left to right. Default: 0.
        right_w_off (int, optional): The cut width for image from right to left. Default: 0.
    """

    def __init__(self, up_h_off: int = 0, down_h_off: int = 0, left_w_off: int = 0, right_w_off: int = 0):
        self.up_h_off = up_h_off
        self.down_h_off = down_h_off
        self.left_w_off = left_w_off
        self.right_w_off = right_w_off

    def __call__(self, im: np.ndarray, label: np.ndarray = None) -> Tuple[np.ndarray]:
        if self.up_h_off < 0 or self.down_h_off < 0 or self.left_w_off < 0 or self.right_w_off < 0:
            raise Exception(
                "up_h_off, down_h_off, left_w_off,  right_w_off must equal or greater zero"
            )

        if self.up_h_off > 0 and self.up_h_off < im.shape[0]:
            im = im[self.up_h_off:, :, :]
            if label is not None:
                label = label[self.up_h_off:, :]

        if self.down_h_off > 0 and self.down_h_off < im.shape[0]:
            im = im[:-self.down_h_off, :, :]
            if label is not None:
                label = label[:-self.down_h_off, :]

        if self.left_w_off > 0 and self.left_w_off < im.shape[1]:
            im = im[:, self.left_w_off:, :]
            if label is not None:
                label = label[:, self.left_w_off:]

        if self.right_w_off > 0 and self.right_w_off < im.shape[1]:
            im = im[:, :-self.right_w_off, :]
            if label is not None:
                label = label[:, :-self.right_w_off]

        if label is None:
            return (im, )
        else:
            return (im, label)

def cv2_to_base64(image: np.ndarray) -> str:
    """
    Convert data from BGR to base64 format.
    """
    data = cv2.imencode('.png', image)[1]
    return base64.b64encode(data.tostring()).decode('utf8')


def base64_to_cv2(b64str: str) -> np.ndarray:
    """
    Convert data from base64 to BGR format.
    """
    data = base64.b64decode(b64str.encode('utf8'))
    data = np.fromstring(data, np.uint8)
    data = cv2.imdecode(data, cv2.IMREAD_COLOR)
    return data

import base64
import collections.abc
from itertools import combinations
from typing import Union, List, Tuple, Callable

import numpy as np
import cv2
import paddle
import paddle.nn.functional as F


def get_reverse_list(ori_shape: list, transforms: Callable) -> list:
    """
    get reverse list of transform.

    Args:
        ori_shape (list): Origin shape of image.
        transforms (list): List of transform.

    Returns:
        list: List of tuple, there are two format:
            ('resize', (h, w)) The image shape before resize,
            ('padding', (h, w)) The image shape before padding.
    """
    reverse_list = []
    h, w = ori_shape[0], ori_shape[1]
    for op in transforms:
        if op.__class__.__name__ in ['Resize']:
            reverse_list.append(('resize', (h, w)))
            h, w = op.target_size[0], op.target_size[1]
        if op.__class__.__name__ in ['Crop']:
            reverse_list.append(('crop', (op.up_h_off, op.down_h_off),
                                 (op.left_w_off, op.right_w_off)))
            h = h - op.up_h_off
            h = h - op.down_h_off
            w = w - op.left_w_off
            w = w - op.right_w_off
        if op.__class__.__name__ in ['ResizeByLong']:
            reverse_list.append(('resize', (h, w)))
            long_edge = max(h, w)
            short_edge = min(h, w)
            short_edge = int(round(short_edge * op.long_size / long_edge))
            long_edge = op.long_size
            if h > w:
                h = long_edge
                w = short_edge
            else:
                w = long_edge
                h = short_edge
        if op.__class__.__name__ in ['ResizeByShort']:
            reverse_list.append(('resize', (h, w)))
            long_edge = max(h, w)
            short_edge = min(h, w)
            long_edge = int(round(long_edge * op.short_size / short_edge))
            short_edge = op.short_size
            if h > w:
                h = long_edge
                w = short_edge
            else:
                w = long_edge
                h = short_edge
        if op.__class__.__name__ in ['Padding']:
            reverse_list.append(('padding', (h, w)))
            w, h = op.target_size[0], op.target_size[1]
        if op.__class__.__name__ in ['PaddingByAspectRatio']:
            reverse_list.append(('padding', (h, w)))
            ratio = w / h
            if ratio == op.aspect_ratio:
                pass
            elif ratio > op.aspect_ratio:
                h = int(w / op.aspect_ratio)
            else:
                w = int(h * op.aspect_ratio)
        if op.__class__.__name__ in ['LimitLong']:
            long_edge = max(h, w)
            short_edge = min(h, w)
            if ((op.max_long is not None) and (long_edge > op.max_long)):
                reverse_list.append(('resize', (h, w)))
                long_edge = op.max_long
                short_edge = int(round(short_edge * op.max_long / long_edge))
            elif ((op.min_long is not None) and (long_edge < op.min_long)):
                reverse_list.append(('resize', (h, w)))
                long_edge = op.min_long
                short_edge = int(round(short_edge * op.min_long / long_edge))
            if h > w:
                h = long_edge
                w = short_edge
            else:
                w = long_edge
                h = short_edge
    return reverse_list


def reverse_transform(pred: paddle.Tensor, ori_shape: list, transforms: Callable, mode: str = 'nearest') -> paddle.Tensor:
    """recover pred to origin shape"""
    reverse_list = get_reverse_list(ori_shape, transforms)
    for item in reverse_list[::-1]:
        if item[0] == 'resize':
            h, w = item[1][0], item[1][1]
            # if paddle.get_device() == 'cpu':
            #     pred = paddle.cast(pred, 'uint8')
            #     pred = F.interpolate(pred, (h, w), mode=mode)
            #     pred = paddle.cast(pred, 'int32')
            # else:
            pred = F.interpolate(pred, (h, w), mode=mode)
        elif item[0] == 'crop':
            up_h_off, down_h_off = item[1][0], item[1][1]
            left_w_off, right_w_off = item[2][0], item[2][1]
            pred = F.pad(
                pred, [left_w_off, right_w_off, up_h_off, down_h_off],
                value=0,
                mode='constant',
                data_format="NCHW")
        elif item[0] == 'padding':
            h, w = item[1][0], item[1][1]
            pred = pred[:, :, 0:h, 0:w]
        else:
            raise Exception("Unexpected info '{}' in im_info".format(item[0]))
    return pred


class Crop:
    """
    crop an image from four forwards.

    Args:
        up_h_off (int, optional): The cut height for image from up to down. Default: 0.
        down_h_off (int, optional): The cut height for image from down to up . Default: 0.
        left_w_off (int, optional): The cut height for image from left to right. Default: 0.
        right_w_off (int, optional): The cut width for image from right to left. Default: 0.
    """

    def __init__(self, up_h_off: int = 0, down_h_off: int = 0, left_w_off: int = 0, right_w_off: int = 0):
        self.up_h_off = up_h_off
        self.down_h_off = down_h_off
        self.left_w_off = left_w_off
        self.right_w_off = right_w_off

    def __call__(self, im: np.ndarray, label: np.ndarray = None) -> Tuple[np.ndarray]:
        if self.up_h_off < 0 or self.down_h_off < 0 or self.left_w_off < 0 or self.right_w_off < 0:
            raise Exception(
                "up_h_off, down_h_off, left_w_off,  right_w_off must equal or greater zero"
            )

        if self.up_h_off > 0 and self.up_h_off < im.shape[0]:
            im = im[self.up_h_off:, :, :]
            if label is not None:
                label = label[self.up_h_off:, :]

        if self.down_h_off > 0 and self.down_h_off < im.shape[0]:
            im = im[:-self.down_h_off, :, :]
            if label is not None:
                label = label[:-self.down_h_off, :]

        if self.left_w_off > 0 and self.left_w_off < im.shape[1]:
            im = im[:, self.left_w_off:, :]
            if label is not None:
                label = label[:, self.left_w_off:]

        if self.right_w_off > 0 and self.right_w_off < im.shape[1]:
            im = im[:, :-self.right_w_off, :]
            if label is not None:
                label = label[:, :-self.right_w_off]

        if label is None:
            return (im, )
        else:
            return (im, label)

def cv2_to_base64(image: np.ndarray) -> str:
    """
    Convert data from BGR to base64 format.
    """
    data = cv2.imencode('.png', image)[1]
    return base64.b64encode(data.tostring()).decode('utf8')


def base64_to_cv2(b64str: str) -> np.ndarray:
    """
    Convert data from base64 to BGR format.
    """
    data = base64.b64decode(b64str.encode('utf8'))
    data = np.fromstring(data, np.uint8)
    data = cv2.imdecode(data, cv2.IMREAD_COLOR)
    return data

get reverse list of transform.      Args:         ori_shape (list): Origin shape of image.         transforms (list): List of transform.      Returns:         list: List of tuple, there are two format:             ('resize', (h, w)) The image shape before resize,             ('padding', (h, w)) The image shape before padding. recover pred to origin shape # if paddle.get_device() == 'cpu': #     pred = paddle.cast(pred, 'uint8') #     pred = F.interpolate(pred, (h, w), mode=mode) #     pred = paddle.cast(pred, 'int32') # else: crop an image from four forwards.      Args:         up_h_off (int, optional): The cut height for image from up to down. Default: 0.         down_h_off (int, optional): The cut height for image from down to up . Default: 0.         left_w_off (int, optional): The cut height for image from left to right. Default: 0.         right_w_off (int, optional): The cut width for image from right to left. Default: 0. Convert data from BGR to base64 format. Convert data from base64 to BGR format.