from typing import Tuple, Union

import numpy as np
import torch

from .convert_convention import convert_camera_matrix


def convert_perspective_to_weakperspective(
        K: Union[torch.Tensor, np.ndarray],
        zmean: Union[torch.Tensor, np.ndarray, float, int],
        resolution: Union[int, Tuple[int, int], torch.Tensor,
                          np.ndarray] = None,
        in_ndc: bool = False,
        convention: str = 'opencv') -> Union[torch.Tensor, np.ndarray]:
    """Convert perspective to weakperspective intrinsic matrix.

    Args:
        K (Union[torch.Tensor, np.ndarray]): input intrinsic matrix, shape
            should be (batch, 4, 4) or (batch, 3, 3).
        zmean (Union[torch.Tensor, np.ndarray, int, float]): zmean for object.
            shape should be (batch, ) or singleton number.
        resolution (Union[int, Tuple[int, int], torch.Tensor, np.ndarray],
            optional): (height, width) of image. Defaults to None.
        in_ndc (bool, optional): whether defined in ndc. Defaults to False.
        convention (str, optional): camera convention. Defaults to 'opencv'.

    Returns:
        Union[torch.Tensor, np.ndarray]: output weakperspective pred_cam,
            shape is (batch, 4)
    """
    assert K is not None, 'K is required.'
    K, _, _ = convert_camera_matrix(
        K=K,
        convention_src=convention,
        convention_dst='pytorch3d',
        is_perspective=True,
        in_ndc_src=in_ndc,
        in_ndc_dst=True,
        resolution_src=resolution)
    if isinstance(zmean, np.ndarray):
        zmean = torch.Tensor(zmean)
    elif isinstance(zmean, (float, int)):
        zmean = torch.Tensor([zmean])
    zmean = zmean.view(-1)
    num_frame = max(zmean.shape[0], K.shape[0])
    new_K = torch.eye(4, 4)[None].repeat(num_frame, 1, 1)
    fx = K[:, 0, 0]
    fy = K[:, 0, 0]
    cx = K[:, 0, 2]
    cy = K[:, 1, 2]
    new_K[:, 0, 0] = fx / zmean
    new_K[:, 1, 1] = fy / zmean
    new_K[:, 0, 3] = cx
    new_K[:, 1, 3] = cy
    return new_K


def convert_weakperspective_to_perspective(
        K: Union[torch.Tensor, np.ndarray],
        zmean: Union[torch.Tensor, np.ndarray, int, float],
        resolution: Union[int, Tuple[int, int], torch.Tensor,
                          np.ndarray] = None,
        in_ndc: bool = False,
        convention: str = 'opencv') -> Union[torch.Tensor, np.ndarray]:
    """Convert perspective to weakperspective intrinsic matrix.

    Args:
        K (Union[torch.Tensor, np.ndarray]): input intrinsic matrix, shape
            should be (batch, 4, 4) or (batch, 3, 3).
        zmean (Union[torch.Tensor, np.ndarray, int, float]): zmean for object.
            shape should be (batch, ) or singleton number.
        resolution (Union[int, Tuple[int, int], torch.Tensor, np.ndarray],
            optional): (height, width) of image. Defaults to None.
        in_ndc (bool, optional): whether defined in ndc. Defaults to False.
        convention (str, optional): camera convention. Defaults to 'opencv'.

    Returns:
        Union[torch.Tensor, np.ndarray]: output weakperspective pred_cam,
            shape is (batch, 4)
    """
    if K.ndim == 2:
        K = K[None]
    if isinstance(zmean, np.ndarray):
        zmean = torch.Tensor(zmean)
    elif isinstance(zmean, (float, int)):
        zmean = torch.Tensor([zmean])
    zmean = zmean.view(-1)
    _N = max(K.shape[0], zmean.shape[0])
    s1 = K[:, 0, 0]
    s2 = K[:, 1, 1]
    c1 = K[:, 0, 3]
    c2 = K[:, 1, 3]
    new_K = torch.zeros(_N, 4, 4)
    new_K[:, 0, 0] = zmean * s1
    new_K[:, 1, 1] = zmean * s2
    new_K[:, 0, 2] = c1
    new_K[:, 1, 2] = c2
    new_K[:, 2, 3] = 1
    new_K[:, 3, 2] = 1

    new_K, _, _ = convert_camera_matrix(
        K=new_K,
        convention_src=convention,
        convention_dst='pytorch3d',
        is_perspective=True,
        in_ndc_src=in_ndc,
        in_ndc_dst=True,
        resolution_src=resolution)
    return new_K
