# Copyright 2020 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.


import subprocess
import torch
import numpy as np
from matplotlib.backends.backend_agg import FigureCanvasAgg
from matplotlib.figure import Figure
import matplotlib as mpl
from matplotlib import cm
import cv2
import os
from datetime import datetime
import shutil

HUGE_NUMBER = 1e10
TINY_NUMBER = 1e-6      # float32 only has 7 decimal digits precision

img_HWC2CHW = lambda x: x.permute(2, 0, 1)
gray2rgb = lambda x: x.unsqueeze(2).repeat(1, 1, 3)


to8b = lambda x: (255 * np.clip(x, 0, 1)).astype(np.uint8)
mse2psnr = lambda x: -10. * np.log(x+TINY_NUMBER) / np.log(10.)


def get_single(data_dict):
    new_dict = {}
    for key in data_dict:
        if key == 'img_hw':
            new_dict[key] = [data_dict[key][0][:1], data_dict[key][1][:1]]
        else:
            datum = data_dict[key][0]
            if isinstance(datum, torch.Tensor):
                new_dict[key] = datum.unsqueeze(0)
            else:
                new_dict[key] = [datum]
    return new_dict

def get_views(data_dict, src_indices, tgt_indices):
    """Acquire certain source/target views from a given sample
    
    Args:
        data_dict: sample from eval dataset
        src_indices: source view indices [#views]
        tgt_indices: target view indices [#views]
    
    Returns:
        An array of data_dict's
    """
    samples = []

    for i in range(len(src_indices)):
        sample = {
            'rgb_path': data_dict['rgb_path'],
            'img_id': data_dict['img_id'],
            'img_hw': data_dict['img_hw'],
            'depth_range': data_dict['depth_range'],
            'tgt_bbox': data_dict['bbox'][:, tgt_indices[i]],
            'tgt_mask': data_dict['masks'][:, tgt_indices[i]],
            'tgt_rgb': data_dict['rgbs'][:, tgt_indices[i]],
            'tgt_c2w_mat': data_dict['c2w_mats'][:, tgt_indices[i]],
            'tgt_intrinsic': data_dict['intrinsics'][:, tgt_indices[i]],
            'src_masks': data_dict['masks'][:, src_indices[i]][None, :], # HACK assuming one view
            'src_rgbs': data_dict['rgbs'][:, src_indices[i]][None, :],
            'src_c2w_mats': data_dict['c2w_mats'][:, src_indices[i]][None, :],
            'src_intrinsics': data_dict['intrinsics'][:, src_indices[i]][None, :],
        }

        samples.append(sample)

    return samples

def get_views_single(data_dict, src_indices, tgt_indices):
    """Acquire certain source/target views from a given sample
    
    Args:
        data_dict: sample from eval dataset
        src_indices: source view indices [#views]
        tgt_indices: target view indices [#views]
    
    Returns:
        An array of data_dict's
    """
    samples = []

    for i in range(len(src_indices)):
        sample = {
            'rgb_path': data_dict['rgb_path'],
            'img_id': data_dict['img_id'],
            'img_hw': data_dict['img_hw'],
            'depth_range': data_dict['depth_range'],
            'tgt_bbox': data_dict['bbox'][tgt_indices[i]],
            'tgt_mask': data_dict['masks'][tgt_indices[i]],
            'tgt_rgb': data_dict['rgbs'][tgt_indices[i]],
            'tgt_c2w_mat': data_dict['c2w_mats'][tgt_indices[i]],
            'tgt_intrinsic': data_dict['intrinsics'][tgt_indices[i]],
            'src_masks': data_dict['masks'][src_indices[i]][None, :], # HACK assuming one view
            'src_rgbs': data_dict['rgbs'][src_indices[i]][None, :],
            'src_c2w_mats': data_dict['c2w_mats'][src_indices[i]][None, :],
            'src_intrinsics': data_dict['intrinsics'][src_indices[i]][None, :],
        }

        samples.append(sample)

    return samples

def save_current_code(outdir):
    now = datetime.now()  # current date and time
    date_time = now.strftime("%m_%d-%H:%M:%S")
    src_dir = '.'
    dst_dir = os.path.join(outdir, 'code_{}'.format(date_time))
    shutil.copytree(src_dir, dst_dir,
                    ignore=shutil.ignore_patterns('data*', 'pretrained*', 'logs*', 'out*', '*.png', '*.mp4',
                                                  '*__pycache__*', '*.git*', '*.idea*', '*.zip', '*.jpg'))

# Get git commit hash
def get_git_revision_hash():
    return subprocess.check_output(['git', 'rev-parse', 'HEAD'])

def img2mse(x, y, mask=None):
    '''
    :param x: img 1, [(...), 3]
    :param y: img 2, [(...), 3]
    :param mask: optional, [(...)]
    :return: mse score
    '''
    if mask is None:
        return torch.mean((x - y) * (x - y))
    else:
        return torch.sum((x - y) * (x - y) * mask.unsqueeze(-1)) / (torch.sum(mask) * x.shape[-1] + TINY_NUMBER)


def img2psnr(x, y, mask=None):
    return mse2psnr(img2mse(x, y, mask).item())


def cycle(iterable):
    while True:
        for x in iterable:
            yield x


def get_vertical_colorbar(h, vmin, vmax, cmap_name='jet', label=None, cbar_precision=2):
    '''
    :param w: pixels
    :param h: pixels
    :param vmin: min value
    :param vmax: max value
    :param cmap_name:
    :param label
    :return:
    '''
    fig = Figure(figsize=(2, 8), dpi=100)
    fig.subplots_adjust(right=1.5)
    canvas = FigureCanvasAgg(fig)

    # Do some plotting.
    ax = fig.add_subplot(111)
    cmap = cm.get_cmap(cmap_name)
    norm = mpl.colors.Normalize(vmin=vmin, vmax=vmax)

    tick_cnt = 6
    tick_loc = np.linspace(vmin, vmax, tick_cnt)
    cb1 = mpl.colorbar.ColorbarBase(ax, cmap=cmap,
                                    norm=norm,
                                    ticks=tick_loc,
                                    orientation='vertical')

    tick_label = [str(np.round(x, cbar_precision)) for x in tick_loc]
    if cbar_precision == 0:
        tick_label = [x[:-2] for x in tick_label]

    cb1.set_ticklabels(tick_label)

    cb1.ax.tick_params(labelsize=18, rotation=0)

    if label is not None:
        cb1.set_label(label)

    fig.tight_layout()

    canvas.draw()
    s, (width, height) = canvas.print_to_buffer()

    im = np.frombuffer(s, np.uint8).reshape((height, width, 4))

    im = im[:, :, :3].astype(np.float32) / 255.
    if h != im.shape[0]:
        w = int(im.shape[1] / im.shape[0] * h)
        im = cv2.resize(im, (w, h), interpolation=cv2.INTER_AREA)

    return im


def colorize_np(x, cmap_name='jet', mask=None, range=None, append_cbar=False, cbar_in_image=False, cbar_precision=2):
    '''
    turn a grayscale image into a color image
    :param x: input grayscale, [H, W]
    :param cmap_name: the colorization method
    :param mask: the mask image, [H, W]
    :param range: the range for scaling, automatic if None, [min, max]
    :param append_cbar: if append the color bar
    :param cbar_in_image: put the color bar inside the image to keep the output image the same size as the input image
    :return: colorized image, [H, W]
    '''
    if range is not None:
        vmin, vmax = range
    elif mask is not None:
        # vmin, vmax = np.percentile(x[mask], (2, 100))
        vmin = np.min(x[mask][np.nonzero(x[mask])])
        vmax = np.max(x[mask])
        # vmin = vmin - np.abs(vmin) * 0.01
        x[np.logical_not(mask)] = vmin
        # print(vmin, vmax)
    else:
        vmin, vmax = np.percentile(x, (1, 100))
        vmax += TINY_NUMBER

    x = np.clip(x, vmin, vmax)
    x = (x - vmin) / (vmax - vmin)
    # x = np.clip(x, 0., 1.)

    cmap = cm.get_cmap(cmap_name)
    x_new = cmap(x)[:, :, :3]

    if mask is not None:
        mask = np.float32(mask[:, :, np.newaxis])
        x_new = x_new * mask + np.ones_like(x_new) * (1. - mask)

    cbar = get_vertical_colorbar(h=x.shape[0], vmin=vmin, vmax=vmax, cmap_name=cmap_name, cbar_precision=cbar_precision)

    if append_cbar:
        if cbar_in_image:
            x_new[:, -cbar.shape[1]:, :] = cbar
        else:
            x_new = np.concatenate((x_new, np.zeros_like(x_new[:, :5, :]), cbar), axis=1)
        return x_new
    else:
        return x_new


# tensor
def colorize(x, cmap_name='jet', mask=None, range=None, append_cbar=False, cbar_in_image=False):
    device = x.device
    x = x.cpu().numpy()
    if mask is not None:
        mask = mask.cpu().numpy() > 0.99
        kernel = np.ones((3, 3), np.uint8)
        mask = cv2.erode(mask.astype(np.uint8), kernel, iterations=1).astype(bool)

    x = colorize_np(x, cmap_name, mask, range, append_cbar, cbar_in_image)
    x = torch.from_numpy(x).to(device)
    return x
