import cv2
import khandy
import numpy as np


def get_2d_rotation_matrix(angle, cx=0, cy=0, scale=1, 
                           degrees=True, dtype=np.float32):
    """
    References:
        `cv2.getRotationMatrix2D` in OpenCV
    """
    if degrees:
        angle = np.deg2rad(angle)
    c = scale * np.cos(angle)
    s = scale * np.sin(angle)

    tx = cx - cx * c + cy * s
    ty = cy - cx * s - cy * c
    return np.array([[ c, -s, tx],
                     [ s,  c, ty],
                     [ 0,  0, 1]], dtype=dtype)
    
    
def rotate_image(image, angle, scale=1.0, center=None, 
                 degrees=True, border_value=0, auto_bound=False):
    """Rotate an image.

    Args:
        image : ndarray
            Image to be rotated.
        angle : float
            Rotation angle in degrees, positive values mean clockwise rotation.
        center : tuple
            Center of the rotation in the source image, by default
            it is the center of the image.
        scale : float
            Isotropic scale factor.
        degrees : bool
        border_value : int
            Border value.
        auto_bound : bool
            Whether to adjust the image size to cover the whole rotated image.

    Returns:
        ndarray: The rotated image.
        
    References:
        mmcv.imrotate
    """
    assert khandy.is_numpy_image(image)
    image_height, image_width = image.shape[:2]
    if auto_bound:
        center = None
    if center is None:
        center = ((image_width - 1) * 0.5, (image_height - 1) * 0.5)
    assert isinstance(center, tuple)

    rotation_matrix = get_2d_rotation_matrix(angle, center[0], center[1], scale, degrees)
    if auto_bound:
        scale_cos = np.abs(rotation_matrix[0, 0])
        scale_sin = np.abs(rotation_matrix[0, 1])
        new_width = image_width * scale_cos + image_height * scale_sin
        new_height = image_width * scale_sin + image_height * scale_cos
        
        rotation_matrix[0, 2] += (new_width - image_width) * 0.5
        rotation_matrix[1, 2] += (new_height - image_height) * 0.5
        
        image_width = int(np.round(new_width))
        image_height = int(np.round(new_height))
    rotated = cv2.warpAffine(image, rotation_matrix[:2,:], (image_width, image_height), 
                             borderValue=border_value)
    return rotated
