import numpy as np

import torch

from pixelssl.utils import logger


""" This file provides tool functions for deep learning.
"""


def sigmoid_rampup(current, rampup_length):
    """ Exponential rampup from https://arxiv.org/abs/1610.02242 . 
    """
    if rampup_length == 0:
        return 1.0
    else:
        current = np.clip(current, 0.0, rampup_length)
        phase = 1.0 - current / rampup_length
        return float(np.exp(-5.0 * phase * phase))



def split_tensor_tuple(ttuple, start, end, reduce_dim=False):
    """ Slice each tensor in the input tuple by channel-dim.

    Since the SSL algorithms always split labeled and unlabeled samples in each batch,
    this function is implemented here as a common tool.

    Arguments:
        ttuple (tuple): tuple of a torch.Tensor
        start (int): start index of slicing
        end (int): end index of slicing
        reduce_dim (bool): whether reduce the channel-dim when end - start == 1
    
    Returns:
        tuple: a sliced tensor tuple
    """

    result = []

    if reduce_dim:
        assert end - start == 1

    for t in ttuple:
        if end - start == 1 and reduce_dim:
            result.append(t[start, ...])       
        else:
            result.append(t[start:end, ...])

    return tuple(result)


def create_model(mclass, mname, **kwargs):
    """ Create a nn.Module and setup it on multiple GPUs.
    """
    model = mclass(**kwargs)
    model = torch.nn.DataParallel(model)
    model = model.cuda()
    
    logger.log_info('  ' + '=' * 76 + '\n  {0} parameters \n{1}'.format(mname, model_str(model)))
    return model


def model_str(module):
    """ Output model structure and parameters number as strings.
    """
    row_format = '  {name:<40} {shape:>20} = {total_size:>12,d}'
    lines = ['  ' + '-' * 76,]

    params = list(module.named_parameters())
    for name, param in params:
        lines.append(row_format.format(name=name,
            shape=' * '.join(str(p) for p in param.size()), total_size=param.numel()))

    lines.append('  ' + '-' * 76)
    lines.append(row_format.format(name='all parameters', shape='sum of above',
        total_size=sum(int(param.numel()) for name, param in params)))
    lines.append('  ' + '=' * 76)
    lines.append('')

    return '\n'.join(lines)


def pytorch_support(required_version='1.0.0', info_str=''):
    if torch.__version__ < required_version:
        logger.log_err('{0} required PyTorch >= {1}\n'
                       'However, current PyTorch == {2}\n'
                       .format(info_str, required_version, torch.__version__))
    else:
        return True