#!/usr/bin/env python

########################################################################
## 05/2018 Justin Rajendra
## some tools to deal with bids data

## system libraries
import sys, os, glob, subprocess, csv, re, argparse, signal, textwrap, json
from afnipy import afni_base, abids_lib
from platform import python_version_tuple


## determine if captured subprocess encoding is needed; requiring after 3.6.0
ENCODING_REQUIRED = python_version_tuple() >= ('3', '6', '0')


########################################################################
## parse command line arguments / build help

## make parser with help
parser = argparse.ArgumentParser(prog=str(sys.argv[0]),add_help=False,
                                 formatter_class=argparse.RawDescriptionHelpFormatter,
                                 description=textwrap.dedent('''\
------------------------------------------
Overview ~1~

    This program does various things with BIDS formatted datasets created
    with dcm2niix_afni or dcm2niix.  The main point as of now is to pull
    information from the matching json file and 3drefit the input dataset.
    If you just want info from the matching json file, use abids_json_info.py.

Caveats ~1~

    This assumes that the nifti dataset was converted from dicoms using
    dcm2niix_afni or dcm2niix with the -b (BIDS) option. So a json file and
    matching dataset in NIFTI format should be present. (NO AFNI FORMAT...)

    The json file should end in .json (lower case) as outputted from dcm2niix.
    The program will try to find a json file that matches the prefix of the
    input dataset.
    Like this:
         my_bids_fmri.nii.gz <-> my_bids_fmri.json

    For most options, 3drefit will be run on the -input dataset(s).
    So the dataset(s) will be overwritten!!
    Make sure you want to do this!!
    All of the caveates for 3drefit apply here...!!
    (https://afni.nimh.nih.gov/pub/dist/doc/htmldoc/programs/3drefit_sphx.html)

    For example, when using -add_TR, if the dataset has slice time offsets,
    these will be scaled by the factor newTR/oldTR. So you may want to
    use -add_TR BEFORE -add_slice_times. However, afni_dcm2niix usually
    adds the correct TR to the dataset header automatically. So you
    should not need -add_TR...

    Also, this has only been tested with 3d+time fMRI data acquired in the
    axial (z or k) direction. If you have problems with data acquired in the
    sagittal or coronal direction, post to the message board.

Example ~1~

    abids_tool.py -add_slice_times -input my_bids_fmri.nii.gz

------------------------------------------

Options ~1~

                                 '''),epilog=textwrap.dedent('''\
------------------------------------------
Justin Rajendra circa 05/2018
For Wolverine, hiya Bub...
Keep on keeping on!
------------------------------------------
                                 '''))

## setup the groups
OneRequired = parser.add_argument_group('Only one of these')
OnlyOne = OneRequired.add_mutually_exclusive_group(required=True)
required = parser.add_argument_group('Required arguments')
parser._optionals.title = 'Optional arguments'
parser._action_groups.reverse()

## required
required.add_argument('-input',type=str,help='At least one 3d+time dataset.',
                      required=True,metavar='DSET',nargs='+')
## optional
parser.add_argument('-help',action='help',help='Show this help and exit.')

## mutally exclusive but one required
OnlyOne.add_argument('-TR_match',action="store_true",default=False,
                     help=('Check if the TR in the json file matches the'+
                           ' TR from input dataset header. (1 if match)'))
OnlyOne.add_argument('-add_TR',action="store_true",default=False,
                     help=('Add the TR from the BIDS json file'+
                           ' to the input dataset using 3drefit.'))
OnlyOne.add_argument('-add_slice_times',action="store_true",default=False,
                     help=('Add the slice times from the BIDS json file'+
                           ' to the input dataset using 3drefit.'))
OnlyOne.add_argument('-copy',type=str,nargs='+',metavar='PREFIX',
                     help=('Copy both the NIFTI dataset(s) AND matching '+
                           '.json file(s) to PREFIX. Must have the same '+
                           'number of prefixes as datasets!'))
## if nothing, show help
if len(sys.argv) == 1:
    parser.print_help()
    sys.exit(1)

########################################################################
## collect the arguments
args = parser.parse_args()
dset_list = args.input
add_slice_times = args.add_slice_times
TR_match = args.TR_match
add_TR = args.add_TR
new_prefix = args.copy

## verify rename lengths
if new_prefix is not None:
    if len(new_prefix) != len(dset_list):
        print("\nError: "+str(len(new_prefix))+" prefix(es) do(es) not match "+
              str(len(dset_list))+" dataset(s)!!\n")
        sys.exit(1)

########################################################################
## go for all
for i in range(0,len(dset_list)):

    ########################################################################
    ## check input dataset

    ## name the dset
    dset = afni_base.afni_name(dset_list[i])

    ## get some info
    afni_cmd = ("3dinfo -exists -ni -nj -nk -TR "+dset.rppv())
    if ENCODING_REQUIRED:
        check_info = subprocess.check_output(
            afni_cmd,shell=True,encoding="ascii"
        ).split()
    else:
        check_info = subprocess.check_output(afni_cmd,shell=True).split()

    ## is the input dataset there?
    if check_info[0] == "0":
        print("\nError: "+dset.pv()+" does not exist or is not loadable!!\n")
        sys.exit(1)

    ########################################################################
    ## check for the json file and read it in

    ## automatically find matching .json ad check it
    json_file = dset.realp()+dset.prefix+".json"
    if not os.path.isfile(json_file):
        print("\nError: "+os.path.basename(json_file)+
              " not found!!\n")
        sys.exit(1)

    ## read in the json file
    json_data = abids_lib.json_info(json_file)

    ########################################################################
    ## add slice timing
    if add_slice_times:
        ## get the slice times
        slice_list = json_data.field("SliceTiming")

        ## make sure it is there
        if slice_list is None:
            print("\nError: "+os.path.basename(json_file)+
                  " has no SliceTiming field!!\n")
            sys.exit(1)

        ## get the number of slices from the json file
        json_slices = len(slice_list)

        ## get the number of slices in the k direction from the nii dataset
        k_slices = int(check_info[3])

        ## only one direction (hopefully k) should match the number of timings
        ## find all matches (may take this out later)
        match = [i for i, x in enumerate(check_info[1:4]) if x == str(json_slices)]

        ## no match boo
        if len(match) == 0:
            print("\nERROR: No voxel dimension ("+str(check_info[1:4])+
                  ") matches the number of slices in the json file ("+
                  str(json_slices)+")!!!\n")
            sys.exit(1)

        ## more than one match boo
        if len(match) > 1:
            print("\nERROR: Multiple voxel dimensions ("+
                  str([check_info[1:4][i] for i in match])+
                  ") match the number of slices"+" in the json file ("+
                  str(json_slices)+")!!!\n")
            sys.exit(1)

        ## hard check for k direction assumption
        if k_slices != json_slices:
            print("\nERROR: There are "+str(json_slices)+
                  " slice times in the json file:\n       "+
                  os.path.basename(json_file)+"\n       "+
                  "and there are "+str(k_slices)+
                  " slices in the k direction of the dataset:\n       "+
                  dset.pv()+"!!!\n")
            sys.exit(1)

        ########################################################################
        ## all should be good now... add the slice timings to the header

        ## save the integers as one big string
        slice_string = ' '.join(map(str,slice_list))

        ## add the slices to the nii header
        afni_cmd = ("3drefit -Tslices "+slice_string+" "+dset.rppv())
        abids_lib.exec_or_error(afni_cmd,"ERROR: Failed to add slice times!!")
        print("Added slice times:\n"+slice_string+"\nto "+dset.pv()+".\n")

    ########################################################################
    ## see if TR from the json matches the TR from the dataset
    if TR_match:
        ## get TR from json file and make sure it is there
        json_TR = json_data.tr
        if json_TR is None:
            print("\nError: "+os.path.basename(json_file)+
                  " has no RepetitionTime field!!\n")
            sys.exit(1)

        ## get TR from dataset
        dset_TR = float(check_info[4])

        ## return values
        val = "1" if json_TR == dset_TR else "0"
        print(val)

    ########################################################################
    ## add TR from json file to the dataset header
    if add_TR:
        ## get TR from json file and make sure it is there
        json_TR = json_data.tr
        if json_TR is None:
            print("\nError: "+os.path.basename(json_file)+
                  " has no RepetitionTime field!!\n")
            sys.exit(1)

        ## add the slices to the nii header
        afni_cmd = ("3drefit -TR "+str(json_TR)+" "+dset.rppv())
        abids_lib.exec_or_error(afni_cmd,"ERROR: Failed to add TR!!")
        print("Added TR of "+str(json_TR)+" seconds to "+dset.pv()+".\n")

    ########################################################################
    ## rename both the dataset and json file
    if new_prefix is not None:
        if os.path.isfile(new_prefix[i]+".json"):
            print("\nError: "+new_prefix[i]+".json already exists. "+
                 "Not overwriting!!\n")
            sys.exit(1)
        else:
            os.system("cp "+json_file+" "+new_prefix[i]+".json")

        afni_cmd = ("3dcopy "+dset.rppv()+" "+new_prefix[i]+".nii.gz")
        abids_lib.exec_or_error(afni_cmd,"ERROR: Failed 3dcopy!!")

## end dset loop
sys.exit(0)

