# -*- coding: utf-8 -*-
# emacs: -*- mode: python; py-indent-offset: 4; indent-tabs-mode: nil -*-
# vi: set ft=python sts=4 ts=4 sw=4 et:
"""The freesurfer module provides basic functions for interfacing with
   freesurfer tools.

   Change directory to provide relative paths for doctests
   >>> import os
   >>> filepath = os.path.dirname( os.path.realpath( __file__ ) )
   >>> datadir = os.path.realpath(os.path.join(filepath, '../../testing/data'))
   >>> os.chdir(datadir)

"""
from __future__ import print_function, division, unicode_literals, absolute_import

import os

from ...utils.filemanip import fname_presuffix, split_filename
from ..base import (TraitedSpec, File, traits, InputMultiPath,
                    OutputMultiPath, Directory, isdefined)
from .base import FSCommand, FSTraitedSpec
from .utils import copy2subjdir

__docformat__ = 'restructuredtext'

class MRISPreprocInputSpec(FSTraitedSpec):
    out_file = File(argstr='--out %s', genfile=True,
                    desc='output filename')
    target = traits.Str(argstr='--target %s', mandatory=True,
                        desc='target subject name')
    hemi = traits.Enum('lh', 'rh', argstr='--hemi %s',
                       mandatory=True,
                       desc='hemisphere for source and target')
    surf_measure = traits.Str(argstr='--meas %s',
                              xor=('surf_measure', 'surf_measure_file', 'surf_area'),
                              desc='Use subject/surf/hemi.surf_measure as input')
    surf_area = traits.Str(argstr='--area %s',
                           xor=('surf_measure', 'surf_measure_file', 'surf_area'),
                           desc='Extract vertex area from subject/surf/hemi.surfname to use as input.')
    subjects = traits.List(argstr='--s %s...',
                           xor=('subjects', 'fsgd_file', 'subject_file'),
                           desc='subjects from who measures are calculated')
    fsgd_file = File(exists=True, argstr='--fsgd %s',
                     xor=('subjects', 'fsgd_file', 'subject_file'),
                     desc='specify subjects using fsgd file')
    subject_file = File(exists=True, argstr='--f %s',
                        xor=('subjects', 'fsgd_file', 'subject_file'),
                        desc='file specifying subjects separated by white space')
    surf_measure_file = InputMultiPath(File(exists=True), argstr='--is %s...',
                                       xor=('surf_measure', 'surf_measure_file', 'surf_area'),
                                       desc='file alternative to surfmeas, still requires list of subjects')
    source_format = traits.Str(argstr='--srcfmt %s', desc='source format')
    surf_dir = traits.Str(argstr='--surfdir %s',
                          desc='alternative directory (instead of surf)')
    vol_measure_file = InputMultiPath(traits.Tuple(File(exists=True),
                                                   File(exists=True)),
                                      argstr='--iv %s %s...',
                                      desc='list of volume measure and reg file tuples')
    proj_frac = traits.Float(argstr='--projfrac %s',
                             desc='projection fraction for vol2surf')
    fwhm = traits.Float(argstr='--fwhm %f',
                        xor=['num_iters'],
                        desc='smooth by fwhm mm on the target surface')
    num_iters = traits.Int(argstr='--niters %d',
                           xor=['fwhm'],
                           desc='niters : smooth by niters on the target surface')
    fwhm_source = traits.Float(argstr='--fwhm-src %f',
                               xor=['num_iters_source'],
                               desc='smooth by fwhm mm on the source surface')
    num_iters_source = traits.Int(argstr='--niterssrc %d',
                                  xor=['fwhm_source'],
                                  desc='niters : smooth by niters on the source surface')
    smooth_cortex_only = traits.Bool(argstr='--smooth-cortex-only',
                                     desc='only smooth cortex (ie, exclude medial wall)')


class MRISPreprocOutputSpec(TraitedSpec):
    out_file = File(desc='preprocessed output file')


class MRISPreproc(FSCommand):
    """Use FreeSurfer mris_preproc to prepare a group of contrasts for
    a second level analysis

    Examples
    --------

    >>> preproc = MRISPreproc()
    >>> preproc.inputs.target = 'fsaverage'
    >>> preproc.inputs.hemi = 'lh'
    >>> preproc.inputs.vol_measure_file = [('cont1.nii', 'register.dat'), \
                                           ('cont1a.nii', 'register.dat')]
    >>> preproc.inputs.out_file = 'concatenated_file.mgz'
    >>> preproc.cmdline # doctest: +ALLOW_UNICODE
    'mris_preproc --hemi lh --out concatenated_file.mgz --target fsaverage --iv cont1.nii register.dat --iv cont1a.nii register.dat'

    """

    _cmd = 'mris_preproc'
    input_spec = MRISPreprocInputSpec
    output_spec = MRISPreprocOutputSpec

    def _list_outputs(self):
        outputs = self.output_spec().get()
        outfile = self.inputs.out_file
        outputs['out_file'] = outfile
        if not isdefined(outfile):
            outputs['out_file'] = os.path.join(os.getcwd(),
                                               'concat_%s_%s.mgz' % (self.inputs.hemi,
                                                                     self.inputs.target))
        return outputs

    def _gen_filename(self, name):
        if name == 'out_file':
            return self._list_outputs()[name]
        return None


class MRISPreprocReconAllInputSpec(MRISPreprocInputSpec):
    surf_measure_file = File(exists=True, argstr='--meas %s',
                             xor=('surf_measure',
                                  'surf_measure_file', 'surf_area'),
                             desc='file necessary for surfmeas')
    surfreg_files = InputMultiPath(File(exists=True), argstr="--surfreg %s",
                                    requires=['lh_surfreg_target', 'rh_surfreg_target'],
                                    desc="lh and rh input surface registration files")
    lh_surfreg_target = File(desc="Implicit target surface registration file",
                             requires=['surfreg_files'])
    rh_surfreg_target = File(desc="Implicit target surface registration file",
                             requires=['surfreg_files'])
    subject_id = traits.String('subject_id', argstr='--s %s', usedefault=True,
                               xor=('subjects', 'fsgd_file',
                                    'subject_file', 'subject_id'),
                               desc='subject from whom measures are calculated')
    copy_inputs = traits.Bool(desc="If running as a node, set this to True " +
                              "this will copy some implicit inputs to the " +
                              "node directory.")


class MRISPreprocReconAll(MRISPreproc):
    """Extends MRISPreproc to allow it to be used in a recon-all workflow

    Examples
    ========
    >>> preproc = MRISPreprocReconAll()
    >>> preproc.inputs.target = 'fsaverage'
    >>> preproc.inputs.hemi = 'lh'
    >>> preproc.inputs.vol_measure_file = [('cont1.nii', 'register.dat'), \
                                           ('cont1a.nii', 'register.dat')]
    >>> preproc.inputs.out_file = 'concatenated_file.mgz'
    >>> preproc.cmdline # doctest: +ALLOW_UNICODE
    'mris_preproc --hemi lh --out concatenated_file.mgz --s subject_id --target fsaverage --iv cont1.nii register.dat --iv cont1a.nii register.dat'
    """

    input_spec = MRISPreprocReconAllInputSpec

    def run(self, **inputs):
        if self.inputs.copy_inputs:
            self.inputs.subjects_dir = os.getcwd()
            if 'subjects_dir' in inputs:
                inputs['subjects_dir'] = self.inputs.subjects_dir
            if isdefined(self.inputs.surf_dir):
                folder = self.inputs.surf_dir
            else:
                folder = 'surf'
            if isdefined(self.inputs.surfreg_files):
                for surfreg in self.inputs.surfreg_files:
                    basename = os.path.basename(surfreg)
                    copy2subjdir(self, surfreg, folder, basename)
                    if basename.startswith('lh.'):
                        copy2subjdir(self, self.inputs.lh_surfreg_target,
                                     folder, basename,
                                     subject_id=self.inputs.target)
                    else:
                        copy2subjdir(self, self.inputs.rh_surfreg_target,
                                     folder, basename,
                                     subject_id=self.inputs.target)

            if isdefined(self.inputs.surf_measure_file):
                copy2subjdir(self, self.inputs.surf_measure_file, folder)

        return super(MRISPreprocReconAll, self).run(**inputs)

    def _format_arg(self, name, spec, value):
        # mris_preproc looks for these files in the surf dir
        if name  == 'surfreg_files':
            basename = os.path.basename(value[0])
            return spec.argstr % basename.lstrip('rh.').lstrip('lh.')
        if name  == "surf_measure_file":
            basename = os.path.basename(value)
            return spec.argstr % basename.lstrip('rh.').lstrip('lh.')
        return super(MRISPreprocReconAll, self)._format_arg(name, spec, value)


class GLMFitInputSpec(FSTraitedSpec):
    glm_dir = traits.Str(argstr='--glmdir %s', desc='save outputs to dir',
                         genfile=True)
    in_file = File(desc='input 4D file', argstr='--y %s', mandatory=True,
                   copyfile=False)
    _design_xor = ('fsgd', 'design', 'one_sample')
    fsgd = traits.Tuple(File(exists=True), traits.Enum('doss', 'dods'),
                        argstr='--fsgd %s %s', xor=_design_xor,
                        desc='freesurfer descriptor file')
    design = File(exists=True, argstr='--X %s', xor=_design_xor,
                  desc='design matrix file')
    contrast = InputMultiPath(File(exists=True), argstr='--C %s...',
                              desc='contrast file')

    one_sample = traits.Bool(argstr='--osgm',
                             xor=('one_sample', 'fsgd', 'design', 'contrast'),
                             desc='construct X and C as a one-sample group mean')
    no_contrast_ok = traits.Bool(argstr='--no-contrasts-ok',
                                  desc='do not fail if no contrasts specified')
    per_voxel_reg = InputMultiPath(File(exists=True), argstr='--pvr %s...',
                                   desc='per-voxel regressors')
    self_reg = traits.Tuple(traits.Int, traits.Int, traits.Int,
                            argstr='--selfreg %d %d %d',
                            desc='self-regressor from index col row slice')
    weighted_ls = File(exists=True, argstr='--wls %s',
                       xor=('weight_file', 'weight_inv', 'weight_sqrt'),
                       desc='weighted least squares')
    fixed_fx_var = File(exists=True, argstr='--yffxvar %s',
                        desc='for fixed effects analysis')
    fixed_fx_dof = traits.Int(argstr='--ffxdof %d',
                              xor=['fixed_fx_dof_file'],
                              desc='dof for fixed effects analysis')
    fixed_fx_dof_file = File(argstr='--ffxdofdat %d',
                             xor=['fixed_fx_dof'],
                             desc='text file with dof for fixed effects analysis')
    weight_file = File(exists=True, xor=['weighted_ls'],
                       desc='weight for each input at each voxel')
    weight_inv = traits.Bool(argstr='--w-inv', desc='invert weights',
                             xor=['weighted_ls'])
    weight_sqrt = traits.Bool(argstr='--w-sqrt', desc='sqrt of weights',
                              xor=['weighted_ls'])
    fwhm = traits.Range(low=0.0, argstr='--fwhm %f',
                        desc='smooth input by fwhm')
    var_fwhm = traits.Range(low=0.0, argstr='--var-fwhm %f',
                            desc='smooth variance by fwhm')
    no_mask_smooth = traits.Bool(argstr='--no-mask-smooth',
                                 desc='do not mask when smoothing')
    no_est_fwhm = traits.Bool(argstr='--no-est-fwhm',
                              desc='turn off FWHM output estimation')
    mask_file = File(exists=True, argstr='--mask %s', desc='binary mask')
    label_file = File(exists=True, argstr='--label %s',
                      xor=['cortex'],
                      desc='use label as mask, surfaces only')
    cortex = traits.Bool(argstr='--cortex',
                         xor=['label_file'],
                         desc='use subjects ?h.cortex.label as label')
    invert_mask = traits.Bool(argstr='--mask-inv',
                              desc='invert mask')
    prune = traits.Bool(argstr='--prune',
                        desc='remove voxels that do not have a non-zero value at each frame (def)')
    no_prune = traits.Bool(argstr='--no-prune',
                           xor=['prunethresh'],
                           desc='do not prune')
    prune_thresh = traits.Float(argstr='--prune_thr %f',
                                xor=['noprune'],
                                desc='prune threshold. Default is FLT_MIN')
    compute_log_y = traits.Bool(argstr='--logy',
                                desc='compute natural log of y prior to analysis')
    save_estimate = traits.Bool(argstr='--yhat-save',
                                desc='save signal estimate (yhat)')
    save_residual = traits.Bool(argstr='--eres-save',
                                desc='save residual error (eres)')
    save_res_corr_mtx = traits.Bool(argstr='--eres-scm',
                                    desc='save residual error spatial correlation matrix (eres.scm). Big!')
    surf = traits.Bool(argstr="--surf %s %s %s",
                       requires=["subject_id", "hemi"],
                       desc="analysis is on a surface mesh")
    subject_id = traits.Str(desc="subject id for surface geometry")
    hemi = traits.Enum("lh", "rh", desc="surface hemisphere")
    surf_geo = traits.Str("white", usedefault=True,
                          desc="surface geometry name (e.g. white, pial)")
    simulation = traits.Tuple(traits.Enum('perm', 'mc-full', 'mc-z'),
                              traits.Int(min=1), traits.Float, traits.Str,
                              argstr='--sim %s %d %f %s',
                              desc='nulltype nsim thresh csdbasename')
    sim_sign = traits.Enum('abs', 'pos', 'neg', argstr='--sim-sign %s',
                           desc='abs, pos, or neg')
    uniform = traits.Tuple(traits.Float, traits.Float,
                           argstr='--uniform %f %f',
                           desc='use uniform distribution instead of gaussian')
    pca = traits.Bool(argstr='--pca',
                      desc='perform pca/svd analysis on residual')
    calc_AR1 = traits.Bool(argstr='--tar1',
                           desc='compute and save temporal AR1 of residual')
    save_cond = traits.Bool(argstr='--save-cond',
                            desc='flag to save design matrix condition at each voxel')