#!/usr/bin/env python

# python3 status: compatible

# basically, a GUI to write an afni_proc.py command

import sys, os, copy, math

# system libraries : test, then import as local symbols
from afnipy import module_test_lib
testlibs = ['copy', 'signal']
if module_test_lib.num_import_failures(testlibs): sys.exit(1)
import copy

from afnipy import afni_util as UTIL
from afnipy import lib_vars_object as VO
from afnipy import lib_uber_subject as USUBJ
from afnipy import option_list as OPT

g_command_help = """
===========================================================================
uber_subject.py               - graphical interface to afni_proc.py

The expected use of this program is to simply run it without any options.
That will start the graphical user interface (GUI), which has its own set
of help and help tools.

        usage:  uber_subject.py

---

This help describes only the command line options to this program, which
enables one to:

        - run without the GUI
        - initialize subject variables in the GUI
        - initialize control variables for control of execution
        - pass PyQt4 options directly to the GUI

----------------------------------------------------------------------
Examples:

   GUI examples:

      uber_subject.py
      uber_subject.py -qt_opts -style=motif
      uber_subject.py -svar sid FT -svar gid idiots  \\
                      -svar anat FT_anat+orig.HEAD   \\
                      -svar epi FT_epi_r*.HEAD       \\
                      -svar stim AV*.txt             \\
                      -svar stim_basis 'BLOCK(15,1)'
      uber_subject.py -cvar subj_dir my/subject/dir

   Informational examples:

      uber_subject.py -help
      uber_subject.py -help_gui
      uber_subject.py -help_install
      uber_subject.py -hist
      uber_subject.py -show_valid_opts
      uber_subject.py -show_default_vars
      uber_subject.py -todo

   Non-GUI examples (all have -no_gui):

      1. Akin to the GUI example, but use subject variables directly, not
         via -svar.

         uber_subject.py -no_gui -save_ap_command cmd.AP.1 \\
             -sid FT -gid horses                           \\
             -anat FT_anat+orig.HEAD -epi FT_epi_r*.HEAD   \\
             -stim AV*.txt -stim_basis 'BLOCK(15,1)'

      2. Process the EPI data as resting state analysis.

         Pass a subject ID, anat and EPI datasets, and # TRs to remove.
         Also, bandpass via 3dDeconvolve (while censoring), and regress
         motion derivatives (in addition to motion).

         uber_subject.py -no_gui -save_ap_command cmd.rest_state  \\
             -sid FT.rest -tcat_nfirst 2                          \\
             -anat FT/FT_anat+orig -epi FT/FT_epi_r*.HEAD         \\
             -regress_bandpass 0.01 0.1 -regress_mot_deriv yes

----------------------------------------------------------------------
Note, for passing subject variables, use of -svar is safer then using
variable names directly (e.g. "-svar stim AV*.txt" vs. "-stim AV*.txt"),
because if there is a mistake in the variable name, it would be grouped
with the previous variable.

For example, compare these 2 versions of the same mistake:

        -svar stim stim_*.txt -svar eppppi EPI_r*.HEAD
   vs.
        -stim stim_*.txt      -eppppi EPI_r*.HEAD

In the former case, there would be an error about epppi not being a
valid variable.  But in the latter case, the program would not know
that you mean -eppppi as a new variable, so -eppppi and the EPI*.HEAD
files would be taken as more -stim inputs.

In any case, passing variables this way is mostly available for my own
evil purposes.  This is supposed to be a GUI after all...

----------------------------------------------------------------------
"""

g_help_trailer = """

- R Reynolds  Feb, 2011
===========================================================================
"""

def get_valid_opts():
   """return an OptionsList of valid program options"""

   # terminal, informative options
   vopts = OPT.OptionList('uber_subject.py options')
   vopts.add_opt('-help', 0, [], helpstr='show this help')
   vopts.add_opt('-help_gui', 0, [], helpstr='show help for GUI')
   vopts.add_opt('-help_howto_program', 0, [], helpstr='help for programming')
   vopts.add_opt('-help_install', 0, [], helpstr='show install notes')
   vopts.add_opt('-help_install_nokia', 0, [], helpstr='Nokia install help')
   vopts.add_opt('-hist', 0, [], helpstr='show revision history')
   vopts.add_opt('-show_default_vars',0,[],helpstr='show variable defaults')
   vopts.add_opt('-show_valid_opts',0,[],helpstr='show all valid options')
   vopts.add_opt('-show_svar_dict',0,[],helpstr='show subject var dictionary')
   vopts.add_opt('-ver', 0, [], helpstr='show module version')

   vopts.add_opt('-verb', 1, [], helpstr='set verbose level')

   vopts.add_opt('-no_gui', 0, [], helpstr='do not open graphical interface')
   vopts.add_opt('-qt_opts', -1, [], helpstr='pass the given options to PyQt')
   vopts.add_opt('-print_ap_command',0,[],helpstr='show afni_proc.py script')
   vopts.add_opt('-save_ap_command',1,[],helpstr='save afni_proc.py script')
   vopts.add_opt('-exec_ap_command',0,[],helpstr='run afni_proc.py command')
   vopts.add_opt('-exec_proc_script',0,[],helpstr='run proc script')
   vopts.add_opt('-cvar', -2, [], helpstr='set control variable to value')
   vopts.add_opt('-svar', -2, [], helpstr='set subject variable to value')

   # and add all subject vars directly
   keys = list(USUBJ.g_svar_dict.keys())
   keys.sort()
   for name in keys:
      vopts.add_opt('-'+name, -1, [], helpstr=USUBJ.g_svar_dict[name])

   vopts.trailers = 0   # do not allow unknown options

   return vopts

def process_options(valid_opts, argv):
   """return status and VarsObject structs of subject and control variables

        - given list of valid options, read and process the user options
        - if terminal option or -no_gui, return 0 (successful quit)

      return  1 : on success and terminate
              0 : on success and continue with GUI
             -1 : on error condition
   """

   # a quick out
   if len(argv) == 0: return 0, None, None, None

   # process any optlist_ options
   valid_opts.check_special_opts(argv)

   # ------------------------------------------------------------
   # check for terminal options before processing the rest
   if '-help' in sys.argv:
      print(g_command_help)
      valid_opts.show('', 1, show_count=0)
      print(g_help_trailer)
      return 1, None, None, None

   if '-help_gui' in sys.argv:
      print(USUBJ.helpstr_usubj_gui)
      return 1, None, None, None

   if '-help_howto_program' in sys.argv:
      print(USUBJ.helpstr_howto_program)
      return 1, None, None, None

   if '-help_install' in sys.argv:
      print(g_install_str)
      return 1, None, None, None

   if '-help_install_nokia' in sys.argv:
      print(g_install_nokia)
      return 1, None, None, None

   if '-hist' in sys.argv:
      print(USUBJ.g_history)
      return 1, None, None, None

   if '-show_default_vars' in sys.argv:
      USUBJ.g_ctrl_defs.show('default cvars :')
      USUBJ.g_subj_defs.show('default svars :')
      return 1, None, None, None

   if '-show_valid_opts' in sys.argv:
      valid_opts.show('', 1)
      return 1, None, None, None

   if '-show_svar_dict' in sys.argv:
      dict = USUBJ.g_svar_dict
      keys = list(dict.keys())
      keys.sort()
      for key in keys:
         print('   %-20s : %s' % (key, dict[key]))
      return 1, None, None, None

   if '-todo' in sys.argv:
      print(USUBJ.helpstr_todo)
      return 1, None, None, None

   if '-ver' in sys.argv:
      print('uber_subject.py: version %s' % USUBJ.g_version)
      return 1, None, None, None

   # ------------------------------------------------------------
   # read and process user options (no check for terminal opts)
   uopts = OPT.read_options(argv, valid_opts)
   if not uopts: return -1, None, None, None

   # init subject options struct
   svars = VO.VarsObject('subject vars from command line')
   cvars = VO.VarsObject('control vars from command line')
   guiopts = ['uber_subject.py'] 

   # first set verbose level
   val, err = uopts.get_type_opt(int, '-verb')
   if val != None and not err: verb = val
   else: verb = 1

   USUBJ.set_vstr_from_def('cvars', 'verb', ['%d'%verb], cvars)

   use_gui = 1 # assume GUI unless we hear otherwise
   svar_keys = list(USUBJ.g_svar_dict.keys())

   errs = 0

   # ------------------------------------------------------------
   # first process all setup options (e.g. -anal_type/domain)
   # - since they might go via -svar, we must search
   for opt in uopts.olist:
      # just check for 'rest' here
      if opt.name == '-anal_type':
         val, err = uopts.get_string_opt('', opt=opt)
         if val == None or err: return -1, None, None, None
         if val == 'rest':
            if verb > 1: print('-- init from rest defaults')
            svars.merge(USUBJ.g_rdef_strs)
      elif opt.name == '-anal_domain':
         val, err = uopts.get_string_opt('', opt=opt)
         if val == None or err: return -1, None, None, None
         if val == 'surface':
            print('** uber_subject.py: not ready for surface analysis')
            return -1, None, None, None

      elif opt.name == '-svar':
         val, err = uopts.get_string_list('', opt=opt)
         if val == None or err: return -1, None, None, None
         if val[0] == '-anal_type':
            if val[1] == 'rest':
               if verb > 1: print('-- init from rest defaults')
               svars.merge(USUBJ.g_rdef_strs)
         elif val[0] == '-anal_domain':
            if val[1] == 'surface':
               print('** uber_subject.py: not ready for surface analysis')
               return -1, None, None, None

   # done with analysis init options
   # ------------------------------------------------------------

   for opt in uopts.olist:
      # skip -verb and any terminal option (though they should not be here)
      if   opt.name == '-help':            continue
      elif opt.name == '-help_gui':        continue
      elif opt.name == '-hist':            continue
      elif opt.name == '-show_valid_opts': continue
      elif opt.name == '-todo':            continue
      elif opt.name == '-ver':             continue

      elif opt.name == '-verb':            continue

      # and skip any pre-setup options ...
      elif opt.name == '-anal_type':       continue
      elif opt.name == '-anal_domain':     continue

      # and skip any post-setup options ...
      elif opt.name == '-print_ap_command':continue
      elif opt.name == '-save_ap_command': continue
      elif opt.name == '-exec_ap_command': continue
      elif opt.name == '-exec_proc_script':continue

      vname = opt.name[1:]

      # now go after "normal" options

      if opt.name == '-no_gui':
         use_gui = 0
         continue

      # get any PyQt4 options
      elif opt.name == '-qt_opts':
         val, err = uopts.get_string_list('', opt=opt)
         if val != None and err: return -1, None, None, None
         guiopts.extend(val)

      # cvar requires at least 2 parameters, name and value
      elif opt.name == '-cvar':
         val, err = uopts.get_string_list('', opt=opt)
         if val != None and err: return -1, None, None, None
         # and set it from the form name = [value_list]
         if USUBJ.set_vstr_from_def('cvars', val[0], val[1:],
                                   cvars, verb=verb, spec=1) < 0:
            errs += 1
            continue

      # svar requires at least 2 parameters, name and value
      elif opt.name == '-svar':
         val, err = uopts.get_string_list('', opt=opt)
         if val == None or err: return -1, None, None, None
         # and set it from the form name = [value_list]
         if USUBJ.set_vstr_from_def('svars', val[0], val[1:],
                                   svars, verb=verb, spec=1) < 0:
            errs += 1
            continue

      # go after a direct svar key
      elif vname in svar_keys:
         val, err = uopts.get_string_list('', opt=opt)
         if val == None or err: return -1, None, None, None
         if USUBJ.set_vstr_from_def('svars', vname, val,
                                   svars, verb=verb, spec=1) < 0:
            errs += 1
            continue

      else:
         print('** invalid option: %s' % opt.name)
         errs += 1
         continue

   if not errs:         # then we can handle any processing options
      if uopts.find_opt('-print_ap_command'):
         print_ap_command(svars, cvars)

   if not errs:         # then we can handle any processing options
      opt = uopts.find_opt('-save_ap_command')
      if opt != None:
         val, err = uopts.get_string_opt('', opt=opt)
         if val == None or err: return -1, None, None, None
         save_ap_command(svars, cvars, val)

   if not errs:         # then we can handle any processing options
      if uopts.find_opt('-exec_ap_command'):
         subj = run_ap_command(svars, cvars)
         if subj != None and uopts.find_opt('-exec_proc_script'):
            subj.exec_proc_script()

   if verb > 2: # show applied subject variables
      changestr = cvars.changed_attrs_str(USUBJ.g_cdef_strs, skiplist='name',
                                         showskip=0, showdel=0)
      print('++ applied control variables: %s\n' % changestr)
      changestr = svars.changed_attrs_str(USUBJ.g_sdef_strs, skiplist='name',
                                         showskip=0, showdel=0)
      print('++ applied subject variables: %s\n' % changestr)

   if errs:    return -1, None, None, None
   if use_gui: return  0, svars, cvars, guiopts
   else:       return  1, svars, cvars, guiopts

def run_ap_command(svars, cvars):
   """create and run the afni_proc.py command
      return the subject object, or None on failure"""

   # get command (writes warnings and errors to terminal)
   subj, cmd = get_ap_command(svars, cvars)

   # write command to file
   if subj.write_ap_command():
      print('** failed to write afni_proc.py command to disk')
      return None

   # in any case, execute the command, showing the output
   status, mesg = subj.exec_ap_command()

   if status:
      print('%s\nAP Errors:\n\n%s\n' % (75*'*', mesg))
      return None
   else:
      print('%s\noutput from afni_proc.py:\n\n%s\n' % (75*'-', mesg))
      return subj

   return subj

def print_ap_command(svars, cvars):
   """run and optionally display the afni_proc.py command"""

   subj, cmd = get_ap_command(svars, cvars)
   print(cmd)

def save_ap_command(svars, cvars, fname):
   """run and optionally display the afni_proc.py command"""

   subj, cmd = get_ap_command(svars, cvars)
   if subj.write_ap_command(fname=fname):
      print('** failed to write afni_proc.py command to disk')

def get_ap_command(svars, cvars):
   """return the subject and command string
      (print warnings and errors to screen)"""

   # create the subject, check warnings and either a command or errors
   apsubj = USUBJ.AP_Subject(svars=svars, cvars=cvars)

   nwarn, wstr = apsubj.get_ap_warnings()
   status, mesg = apsubj.get_ap_command()

   if status:  # then only mention errors
      print('%s\nERRORS:\n\n%s\n' % (75*'*', mesg))
      cmd = ''
   else:
      if wstr: print('%s\n**** Warnings:\n\n%s\n%s\n' % (75*'-',wstr,75*'-'))
      cmd = '### afni_proc.py script:\n\n%s\n' % mesg

   return apsubj, cmd

g_install_str = """
   ------------------------------------------------------------------
   A. Linux

      A1. Fedora

         yum install PyQt4

   ----------

      A2. Debian/Ubuntu

         apt-get install python-qt4

   ----------

   B. OS X

      B1. via homebrew

         0. homebrew should be installed

               https://afni.nimh.nih.gov/pub/dist/doc/htmldoc/background_install/install_instructs/steps_mac.html

         1. install pyqt

               brew install pyqt

            Note: for this to apply, /sw/bin needs to be before /usr/bin in
                  the PATH.

         2. possibly update PYTHONPATH to point to new site-packages directory
            (put this in .cshrc)

               setenv PYTHONPATH /usr/local/lib/python2.7/site-packages

      B2. do we still need fink instructions?


   For extra details, see the output from:

        afni_system_check.py -check_all
"""

g_install_nokia = """
   ------------------------------------------------------------------

      B2. directly from nokia and riverbank computing (NO LONGER RECOMMENDED):

      ** this has not been working well lately, so now fink is recommended

         { NO LONGER RECOMMENDED, but MIGHT work on: OS X 10.5, 10.6 }

         0. XCode and python (2.6) should already be installed

         1. Qt SDK for mac (large: 1.1 GB download):
            - http://qt.nokia.com/downloads
            - download LGP version of Qt SDK

         2. SIP (interface between C++ and python - small)
            - http://www.riverbankcomputing.co.uk/software/sip/download
            - tar xf sip-4.12.1.tar        ('tar xfz' if .tar.gz)
            - cd sip-4.12.1                (for example)
            - python configure.py -d /Library/Python/2.6/site-packages
            - make
            - sudo make install

         3. PyQt (small, but may take 15-20 minutes to compile):
            - http://www.riverbankcomputing.co.uk/software/pyqt/download
            - download OS/X source package (e.g. PyQt-mac-gpl-4.8.3.tar.gz)
            - cd PyQt-mac-gpl-4.8.3        (for example)
            - python configure.py -d /Library/Python/2.6/site-packages
            - make
            - sudo make install


         ** OS X 10.5 systems:

            For python version 2.5, replace /Library/Python/2.6/site-packages
            in the 2 'configure' commands below with directory Python/2.5,
            instead (i.e. use /Library/Python/2.5/site-packages).
   ------------------------------------------------------------------
"""

def run_gui(svars=None, cvars=None, guiopts=[]):
   try: from PyQt4 import QtGui
   except:
      print('\n**** failed to import PyQt4.QtGui ****\n\n'                \
            '   PyQt4 must be installed to run the uber_subject.py GUI\n' \
            '   --> see the output of: uber_subject.py -help_install\n')
      return 1

   # if the above worked, let any GUI import errors show normally
   from afnipy import gui_uber_subj as GUS

   app = QtGui.QApplication(guiopts)
   dialog = GUS.SingleSubjectWindow(subj_vars=svars, ctrl_vars=cvars,
                                    set_sdir=1)
   QtGui.QApplication.setStyle(QtGui.QStyleFactory.create(dialog.gvars.style))
   dialog.show()
   app.exec_()

   return 0

def main():

   # - set any subject variables or options
   # - run the gui unless -no_gui is given

   valid_opts = get_valid_opts()
   rv, svars, cvars, guiopts  = process_options(valid_opts, sys.argv)

   if   rv > 0: return 0        # terminal success
   elif rv < 0: return 1        # terminal failure
   # else rv == 0, so continue with GUI

   return run_gui(svars, cvars, guiopts)

if __name__ == '__main__':
   sys.exit(main())
