#!/usr/bin/env python3
"""
Utils functions for phys2cvr.

Attributes
----------
LGR :
    Logger
"""

import datetime
import logging
import os
import sys
from os.path import exists
from pathlib import Path

import numpy as np

LGR = logging.getLogger(__name__)
LGR.setLevel(logging.INFO)


def if_declared_force_type(var, dtype, varname='an input variable', silent=False):
    """
    Make sure `var` is of type `dtype`.

    Parameters
    ----------
    var : str, int, or float
        Variable to change type of
    dtype : str
        Type to change `var` to
    varname : str, optional
        The name of the variable
    silent : bool, optional
        If True, don't return any message

    Returns
    -------
    int, float, str, list, or var
        The given `var` in the given `dtype`, or `var` if '' or None

    Raises
    ------
    NotImplementedError
        If dtype is not 'int', 'float', 'str', or 'list'
    """
    if not var:
        return var

    converters = {
        'int': int,
        'float': float,
        'str': str,
        'list': lambda v: v if isinstance(v, list) else [v],
    }

    if dtype not in converters:
        raise NotImplementedError(f'Type {dtype} not supported')

    tmpvar = converters[dtype](var)

    if not silent and type(tmpvar) is not type(var):
        name = f'variable {varname}' if varname != 'an input variable' else varname
        LGR.warning(f'Changing type of {name} from {type(var)} to {dtype}')

    return tmpvar


def check_ext(all_ext, fname, scan=False, remove=False):
    """Check which extension a file has, and possibly remove it.

    Parameters
    ----------
    all_ext : list
        All possible extensions to check within.
    fname : str or os.PathLike
        The filename to check.
    scan : bool, optional
        Scan the given path to see if there is a file with that extension
        If True and no path declared, check if fname has a path, if not scan '.'
        If False, don't scan any folder.
    remove : bool, optional
        Remove the extension from fname if it has one.

    Returns
    -------
    obj_return : Uses a list to return variable amount of options.
        has_ext : boolean
            True if the extension is found, false otherwise.
        fname : str or os.PathLike
            If ``remove`` is True, return (extensionless) fname.
        ext : str
            If both ``remove`` and ``has_ext`` are True, returns also found extension.
    """
    all_ext = if_declared_force_type(all_ext, 'list', silent=True)
    ext = ''.join(Path(fname).suffixes)
    LGR.debug(f'{fname} ends with extension {ext}')

    has_ext = ext.lower() in all_ext

    if not has_ext and scan:
        all_ext = (
            all_ext + [e.upper() for e in all_ext] + [e.capitalize() for e in all_ext]
        )
        for ext in all_ext:
            if exists(f'{fname}{ext}'):
                fname = f'{fname}{ext}'
                LGR.warning(f'Found {fname}{ext}, using it as input henceforth')
                has_ext = True
                break

    ext = '' if not has_ext else ext

    obj_return = [has_ext]

    if remove:
        obj_return += [
            fname[: -len(ext)],
            None if ext == '' else ext,
        ]
    else:
        obj_return += [fname]

    return obj_return[:]


def check_nifti_dim(fname, data, dim=4):
    """
    Remove extra dimensions.

    Parameters
    ----------
    fname : str
        The name of the file representing `data`
    data : np.ndarray
        The data which dimensionality needs to be checked
    dim : int, optional
        The amount of dimensions expected/desired in the data.

    Returns
    -------
    np.ndarray
        If `len(data.shape)` = `dim`, returns data.
        If `len(data.shape)` > `dim`, returns a version of data without the
        dimensions above `dim`.

    Raises
    ------
    ValueError
        If `data` has less dimensions than `dim`
    """
    if data.ndim < dim:
        raise ValueError(
            f'A {dim}D nifti file is required, but {fname} has {data.ndim}D. '
            'Please check the input file.'
        )

    if data.ndim > dim:
        LGR.warning(f'{fname} has more than {dim} dimensions. Removing D > {dim}.')
        for ax in range(dim, data.ndim):
            data = np.delete(data, np.s_[1:], axis=ax)

    return np.squeeze(data)


def check_array_dim(fname, data, shape=None):
    """Check dimensions of a matrix.

    For future 3D implementation, check MIPLabCH/nigsp's check_array_dim.

    Parameters
    ----------
    fname : str
        The name of the file representing ``data``.
    data : np.ndarray
        The data which dimensionality needs to be checked.
    shape : None | ``'square'`` | ``'rectangle'``
        Shape of matrix, if empty, skip shape check.

    Returns
    -------
    np.ndarray
        If ``data.ndim = 2``, returns data.
        If ``data.ndim = 1`` and ``shape == 'rectangle'``, returns data with added empty
        axis.

    Raises
    ------
    NotImplementedError
        If ``data`` has more than 2 dimensions.
    ValueError
        If ``data`` is empty
        If ``shape == 'square'`` and ``data`` dimensions have different lengths.
    """
    data = data.squeeze()
    LGR.info('Checking data shape.')

    if data.size == 0:
        raise ValueError(f'{fname} is empty!')
    if data.ndim > 2:
        raise NotImplementedError(
            f'Only matrices up to 2D are supported, but given matrix is {data.ndim}D.'
        )

    if shape is not None:
        if data.ndim == 1 and shape == 'rectangle':
            data = data[..., np.newaxis]
            LGR.warning(
                f'Rectangular matrix required, but {fname} is a vector. '
                'Adding empty dimension.'
            )
        if shape == 'square' and data.shape[0] != data.shape[1]:
            raise ValueError(
                f'Square matrix required, but {fname} matrix has shape {data.shape}.'
            )

    return data


def save_bash_call(fname, outdir):
    """
    Save the bash call into file `p2d_call.sh`.

    Parameters
    ----------
    fname : str or path
        Name of or path to functional file
    outdir : str or path
        Output directory
    """
    arg_str = ' '.join(sys.argv[1:])
    call_str = f'phys2cvr {arg_str}'
    if outdir:
        outdir = os.path.abspath(outdir)
    else:
        outdir = os.path.join(os.path.split(fname)[0], 'phys2cvr')
    log_path = os.path.join(outdir, 'logs')
    os.makedirs(log_path, exist_ok=True)
    isotime = datetime.datetime.now().strftime('%Y-%m-%dT%H%M%S')
    _, fname, _ = check_ext('.nii.gz', os.path.basename(fname), remove=True)
    f = open(os.path.join(log_path, f'p2c_call_{fname}_{isotime}.sh'), 'a')
    f.write(f'#!bin/bash \n{call_str}')
    f.close()


"""
Copyright 2021, Stefano Moia.

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

http://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.
"""
