#!/usr/bin/env python

# This library contains functions for creating part of the
# afni_proc.py QC HTML.  Specifically, these functions focus primarily
# on the 'vstat' QC block, parsing the stats dset for task-based cases
# and deciding which volumes should be included in the QC HTML.
#
auth = 'PA Taylor'
#
#########################################################################
#
#ver = '1.0' ; date = 'Jan 19, 2022'
#
ver = '1.1' ; date = 'Jan 6, 2023'
# [PT] update to integrate in user vstat label selection
#
#
#
#########################################################################

import os, copy, sys

from afnipy import afni_base           as ab

# ----------------------------------------------------------------------

# for parsing stats_dset labels and setting up vstat testing
class vstat_obj:

    def __init__(self) :

        self.label      = ""             # what user enters
        self.stats_dset = ""             # dset searched within
        self.label_in_dset = False       # does the label exist meaningfully?

        self.olay_label = ""             # subbrick label/selector of stats_dset
        self.olay_index = -1             # subbrick index selector of stats_dset
        self.olay_type  = ""             # 'Coef', 'Tstat', 'Fstat'
        self.olay_posonly = False        # For colorbar
        self.olay_pbar  = ""             # Name of pbar, decided by posonly

        self.thr_label  = ""             # subbrick label/selector of stats_dset
        self.thr_index  = ""             # subbrick index selector of stats_dset
        self.thr_type   = ""             # 'Tstat', 'Fstat'
        self.thr_sided  = "2sided"       # '1sided', '2sided', 'bisided'
        self.thr_sided_txt = "two-sided" # 'one-sided', 'two-sided', 'bisided'
        self.thr_mode   = "pvalue"       # 'percentile' or 'pvalue'

    def set_label(self, ss):
        self.label = ss

    def set_stats_dset(self, ss):
        self.stats_dset = ss

    def found_label(self):
        self.label_in_dset = True
        
    def set_olay_label(self, ss):
        self.olay_label = ss

    def set_olay_index(self, ii):
        self.olay_index = int(ii)

    def set_olay_type(self, ss):
        self.olay_type = ss

    def set_olay_posonly(self):
        self.olay_posonly = True

    def set_olay_pbar(self, ss):
        self.olay_pbar = ss

    def set_thr_label(self, ss):
        self.thr_label = ss

    def set_thr_index(self, ii):
        self.thr_index = int(ii)

    def set_thr_type(self, ss):
        self.thr_type = ss

    def set_thr_sided(self, ss):
        self.thr_sided = ss
        if ss == '1sided' :
            self.thr_sided_txt = 'one-sided'
        elif ss == '2sided' :
            self.thr_sided_txt = 'two-sided'
        elif ss == 'bisided' :
            self.thr_sided_txt = 'bi-sided'
        else :
            print("*+ WARNING: unknown sidedness of testing: {}"
                  "".format(ss))

    def set_thr_mode(self, ss):
        self.thr_mode = ss

    ### semi-omnibus settings for olay/thr
    ##  olay can be Coef or Fstat
    #   thr can be Tstat or Fstat
    def set_olay_all_coef(self, label, ind):
        self.found_label()
        self.set_olay_label(label)
        self.set_olay_index(ind)
        self.set_olay_type('Coef')
        self.set_olay_pbar('Reds_and_Blues_Inv')

    def set_olay_all_fstat(self, label, ind):
        self.found_label()
        self.set_olay_label(label)
        self.set_olay_index(ind)
        self.set_olay_type('Fstat')
        self.set_olay_pbar('Plasma')
        self.set_olay_posonly()

    def set_thr_all_tstat(self, label, ind):
        self.set_thr_label(label)
        self.set_thr_index(ind)
        self.set_thr_type('Tstat')
        self.set_thr_sided("2sided")

    def set_thr_all_fstat(self, label, ind):
        self.set_thr_label(label)
        self.set_thr_index(ind)
        self.set_thr_type('Fstat')
        self.set_thr_sided("1sided")
        if self.thr_label == 'Full_Fstat' :
            self.set_thr_mode("percentile")

    def disp_olay_all(self):
        print("label         : " + self.label)
        print("stats_dset    : " + self.stats_dset)
        print("label_in_dset : " + str(self.label_in_dset))

        print("olay_label    : " + self.olay_label)
        print("olay_index    : " + str(self.olay_index))
        print("olay_type     : " + self.olay_type)
        print("olay_posonly  : " + str(self.olay_posonly))
        print("olay_pbar     : " + self.olay_pbar)
                     
    def disp_thr_all(self):
        print("label         : " + self.label)
        print("stats_dset    : " + self.stats_dset)
        print("label_in_dset : " + str(self.label_in_dset))

        print("thr_label     : " + self.thr_label)
        print("thr_index     : " + str(self.thr_index))
        print("thr_type      : " + self.thr_type )
        print("thr_sided     : " + self.thr_sided)
        print("thr_sided_txt : " + self.thr_sided_txt)
        print("thr_mode      : " + self.thr_mode )

# ----------------------------------------------------------------------
# ----------------------------------------------------------------------
# helper function(s) for reading dset header 

def get_dset_label_list(fname, sb_delim='|'):
    '''Get list of labels from dset header.

    Parameters
    ----------
    fname       : (str) filename of dset
    sb_delim    : (str) delimiter to use to ask for labels in dset
                  (should *not* be a char in any labels themselves);
                  cannot have both ' and " in it (why would you,
                  anyways?)

    Return
    ------
    olist       : (list) list of labels (each a str)

    '''

    if  not sb_delim.__contains__('"') :
        sb_delimq = '"{}"'.format(sb_delim)
    elif  not sb_delim.__contains__("'") :
        sb_delimq = "'{}'".format(sb_delim)
    else:
        print('''** ERROR: Can't have both ' and " in the sb_delim''')
        sys.exit(6)

    cmd_get_label = '''3dinfo -sb_delim {} -label {}'''.format(sb_delimq, 
                                                               fname)

    sint, so, se = ab.simple_shell_exec(cmd_get_label, capture=1)
    sos          = so.split(sb_delim)
    llabels      = [x.strip() for x in sos]
    Nlabels      = len(llabels)

    cmd_get_nv   = '''3dinfo -nv {}'''.format(fname)

    sint, so, se = ab.simple_shell_exec(cmd_get_nv, capture=1)
    Nvols        = int(so.strip())

    if Nlabels != Nvols :
        print("** ERROR: Nlabels ({}) does not match Nvols ({})\n"
              "   in stats file {}.\n"
              "   Possible sb_delim char ({}) in str labels?\n"
              "".format( Nlabels, Nvols, fname, sb_delim ))
        sys.exit(5)

    return llabels

# ----------------------------------------------------------------------
# ----------------------------------------------------------------------
# series of helper functions for checking if a particular entry in
# stats dset list of labels is of a certain category

def is_label_Full_Fstat(idx, list_label):
    '''Check a label in a stats dset for being of a certain category.

    Here, check if it is Full_Fstat (admittedly pretty easy).
    
    Parameters
    ----------
    idx          : (int) index within the list of labels
    list_label   : (list) list of stats dset labels

    Return
    ------
    yn           : (int) 1 if label is desired stat, 0 otherwise
    
    '''

    nlabel = len(list_label)
    if idx >= nlabel :
        return 0

    if list_label[idx] == "Full_Fstat" :
        return 1
    
    return 0

def is_label_some_Coef_with_Tstat(idx, list_label):
    '''Check a label in a stats dset for being of a certain category.

    Here, check if it is any Coef, followed by a Tstat.  *This*
    function is likely just a supplementary check, because we will
    also care about whether it is a GLT item, a stim coef, or one of a
    train of TENT coefs.  This function does NOT check for an
    associated Fstat (at present, he writes ominously...).
    
    Parameters
    ----------
    idx          : (int) index within the list of labels
    list_label   : (list) list of stats dset labels

    Return
    ------
    yn           : (int) 1 if label is desired stat, 0 otherwise

    '''

    nlabel = len(list_label)
    if idx >= nlabel :
        return 0

    # convert pythonic neg index to pos, as python would/does
    if idx < 0 :
        idx = nlabel + idx
    ilab = list_label[idx]

    if '#' in ilab and ilab.endswith("_Coef"):
        jdx = idx+1
        if jdx < nlabel:
            jlab = list_label[jdx]
            if '#' in jlab and jlab.endswith("_Tstat"):
                if ilab[:-4] == jlab[:-5] :
                    return 1
    return 0


def is_label_SERIES_Coef_with_Tstat(idx, list_label):
    '''Check a label in a stats dset for being of a certain category.

    Here, check if the current label is a Coef, followed by a Tstat,
    that is part of a series of related Coef+Tstat pairs---this can
    happen when using TENT or when GLT includes '|', for example.
    **Because these are special**, also return the index of the Fstat
    at the end of the train
    
    Parameters
    ----------
    idx          : (int) index within the list of labels
    list_label   : (list) list of stats dset labels

    Return
    ------
    yn           : (int) 1 if label is desired stat, 0 otherwise
    fidx         : (int) index of Fstat at end of train; if can't be found,
                   is -1

    '''

    nlabel = len(list_label)
    if idx >= nlabel :
        return 0, -1

    # first check: is coef+tstat pair?
    if not(is_label_some_Coef_with_Tstat(idx, list_label)) :
        return 0, -1

    # convert pythonic neg index to pos, as python would/does
    if idx < 0 :
        idx = nlabel + idx
    ilab = list_label[idx]

    # now check forward and backward for dsets with same prefix
    FOUND_MORE = 0
    pre = ilab.split("#")[0]

    # forward pass
    kdx = idx+2
    if kdx < nlabel:
        klab = list_label[kdx]
        if klab.endswith("_Coef") and klab.split("#")[0] == pre :
            FOUND_MORE = 1

    # backward pass, if necessary
    if not(FOUND_MORE) :
        kdx = idx-2
        if kdx >= 0:
            klab = list_label[kdx]
            if klab.endswith("_Coef") and klab.split("#")[0] == pre :
                FOUND_MORE = 1

    if not(FOUND_MORE) :
        return 0, -1

    fidx  = idx + 2
    name = pre + "_Fstat"
    while fidx < nlabel:
        if list_label[fidx] == name :
            break
        fidx+=1 

    # must have found its Fstat
    if fidx < nlabel :
        return 1, fidx
    else:
        # should not happen
        print("+* WARNING: looks like part of TENT train, but cannot find "
              "Fstat in label list?")
        return 1, -1

    return 0, -1

def is_label_Fstat_and_lone(idx, list_label):
    '''Check a label in a stats dset for being of a certain category.

    Here, check if the current label is an Fstat, and also if it is
    'lone' (= has no partner Coef).  The Full_Fstat would be a lone
    Fstat, for example.
    
    Parameters
    ----------
    idx          : (int) index within the list of labels
    list_label   : (list) list of stats dset labels

    Return
    ------
    yn           : (int) 1 if label at idx is an Fstat, 0 otherwise.
    is_lone      : (int) 1 if Fstat is 'lone', 0 otherwise.

    '''

    nlabel = len(list_label)
    if idx >= nlabel :
        return 0, 0

    # convert pythonic neg index to pos, as python would/does
    if idx < 0 :
        idx = nlabel + idx
    ilab = list_label[idx]

    if not(ilab.endswith('_Fstat')) :
        return 0, 0

    # at this point, we assume this idx DOES have an Fstat

    # search backwards for Coef
    ipre  = ilab[:-6]
    cidx  = idx-1
    while cidx > 0:
        clab = list_label[cidx]
        cpre = clab.split("#")[0]
        if cpre == ipre and clab.endswith("_Coef") :
            return 1, 0
        cidx-= 1

    return 1, 1


def is_label_REG_Coef_with_Tstat(idx, list_label):
    '''Check a label in a stats dset for being of a certain category.

    Here, check if the current label is a 'regular' Coef, followed by
    a Tstat (= not part of a 'series' of Coef/Tstat pairs, like for
    TENT or '|'-GLT).  Also see if there is an associated Fstat.
    
    Parameters
    ----------
    idx          : (int) index within the list of labels
    list_label   : (list) list of stats dset labels

    Return
    ------
    yn           : (int) 1 if label at idx is a Coef and at idx+1 is an
                   associated Tstat
    fidx         : (int) idx of associated Fstat, -1 otherwise

    '''

    nlabel = len(list_label)
    if idx >= nlabel :
        return 0, -1

    # first check: is coef+tstat pair?
    if not(is_label_some_Coef_with_Tstat(idx, list_label)) :
        return 0, -1

    # is it a SERIES?
    yesno, tmp = is_label_SERIES_Coef_with_Tstat(idx, list_label)
    if yesno :
        return 0, -1

    # If we make it here, then by process of elimination, item idx
    # must be 'regular' Coef+Tstat

    # check for associated F
    fidx = get_Fstat_index_for_this_Coef(idx, list_label)

    return 1, fidx

def get_Fstat_index_for_this_Coef(idx, list_label):
    '''Check a label in a stats dset for being of a certain category.

    Here, we *assume* that we are already sitting at a Coef index, and want
    to know if there is an associated Fstat with it. If so, return
    that index, which will be in range [0, nlabel); otherwise, return
    -1.
    
    Parameters
    ----------
    idx          : (int) index within the list of labels
    list_label   : (list) list of stats dset labels

    Return
    ------
    fidx         : (int) idx of associated Fstat, -1 otherwise

    '''

    nlabel = len(list_label)

    fidx = -1

    kdx  = idx+2
    pre  = list_label[idx].split("#")[0]
    name = pre + "_Fstat"

    if kdx < nlabel:
        if list_label[kdx] == name :
            fidx = kdx

    return fidx

# ----------------------------------------------------------------------
# ----------------------------------------------------------------------
# more primary functions

# a primary function (probably wrapped with stats_dset fname as input,
# though)
def get_vstat_stats_default(list_label, nmax_selfchosen=5, verb=0):
    '''From a list of labels in a stats dset, determine a list of them to
include for APQC vstat visualization, up to some max.

The output 


    Parameters
    ----------
    list_label   : (list) list of full labels from stats dset header
    verb         : (int) verbosity level

    Return
    ------
    nvso_final     : (int) number of vstat objects (vsos) in the output list
                     -> essentially how many vstat images we will have
    list_vso_final : (list) list of vstat objects (vsos) to use
    good_labs      : (list) list of good labels---superset (which may match)
                     of labels within vso list (if you want to see list of 
                     labels used, loop over list_vso_final)
                     -> these contained vsos become the main output to use
    bad_labs       : (list) list of bad labels that caused trouble in 
                     searching through the stats header
 
    '''
    
    nlabel = len(list_label)

    # label lists
    good_labs = []
    bad_labs  = []

    # vso lists
    list_vso_init       = []
    list_vso_final      = []
    mini_Full_Fstat     = []
    mini_GLT            = []
    mini_nonGLT         = []
    mini_other_Fstat    = []

    idx = 0
    while idx < nlabel :
        ilab = list_label[idx]
        ipre = ilab.split("_GLT")[0].split("#")[0].split("_Fstat")[0]

        vso = vstat_obj()
        vso.set_label(ipre)

        # these conditions are in decreasing order of priority for
        # display (like cases...)
        if is_label_Full_Fstat(idx, list_label):
            # Full_Fstat
            vso.set_olay_all_fstat(ilab, idx)
            vso.set_thr_all_fstat(ilab, idx)
            good_labs.append(ilab)
            mini_Full_Fstat.append([idx, vso])

        elif '_GLT#' in ilab :
            if is_label_REG_Coef_with_Tstat(idx, list_label)[0] :
                # here, is single pair, maybe with ftstat
                vso.set_olay_all_coef(ilab, idx)
                vso.set_thr_all_tstat(list_label[idx+1], idx+1)
                good_labs.append(ilab)
                mini_GLT.append([idx, vso])
                # check for fstat; jump index there, if it exists
                TF, fidx = is_label_REG_Coef_with_Tstat(idx, list_label)
                if fidx >= 0 :
                    idx = fidx
                else:
                    idx+=1   # bc we use both Coef and Tstat here

            elif is_label_SERIES_Coef_with_Tstat(idx, list_label)[0] :
                # here, is series of pairs, with probable fstat
                yesno, fidx = is_label_SERIES_Coef_with_Tstat(idx, list_label)
                if fidx >= 0 :
                    flab = list_label[fidx]
                    vso.set_olay_all_fstat(flab, fidx)
                    vso.set_thr_all_fstat(flab, fidx)
                    good_labs.append(flab)
                    mini_GLT.append([idx, vso])
                    # jump index
                    idx = fidx
                else:
                    bad_labs.append(ilab)

            else:
                print("+* WARNING: GLT label with unmatched properties?\n"
                      "   Observed for: {}"
                          "".format(ilab))
                bad_labs.append(ilab)

        elif is_label_some_Coef_with_Tstat(idx, list_label) :
            if is_label_REG_Coef_with_Tstat(idx, list_label)[0] :
                # here, is single pair, maybe with ftstat
                vso.set_olay_all_coef(ilab, idx)
                vso.set_thr_all_tstat(list_label[idx+1], idx+1)
                good_labs.append(ilab)
                mini_nonGLT.append([idx, vso])
                # check for fstat; jump index there, if it exists
                TF, fidx = is_label_REG_Coef_with_Tstat(idx, list_label)
                if fidx >= 0 :
                    idx = fidx
                else:
                    idx+=1   # bc we use both Coef and Tstat here

            elif is_label_SERIES_Coef_with_Tstat(idx, list_label)[0] :
                # here, is series of pairs, with probable fstat
                yesno, fidx = is_label_SERIES_Coef_with_Tstat(idx, list_label)
                if fidx >= 0 :
                    flab = list_label[fidx]
                    vso.set_olay_all_fstat(flab, fidx)
                    vso.set_thr_all_fstat(flab, fidx)
                    good_labs.append(flab)
                    mini_nonGLT.append([idx, vso])
                    # jump index
                    idx = fidx
                else:
                    bad_labs.append(list_label[idx])

            else:
                print("+* WARNING: nonGLT label with unmatched properties?\n"
                      "   Observed for: {}"
                          "".format(ilab))
                bad_labs.append(ilab)

        elif is_label_Fstat_and_lone(idx, list_label)[0] :
            vso.set_olay_all_fstat(ilab, idx)
            vso.set_thr_all_fstat(ilab, idx)
            good_labs.append(ilab)
            mini_other_Fstat.append([idx, vso])

        else:
            print("+* WARNING: found no home for: {}"
                  "".format(ilab))
            bad_labs.append(ilab)

        idx+= 1

    if mini_Full_Fstat :
        if verb > 1 :
            print("++ Num Full_Fstat: {}".format(len(mini_Full_Fstat)))
        list_vso_init.extend(mini_Full_Fstat)
    if mini_GLT :
        if verb > 1 :
            print("++ Num GLT: {}".format(len(mini_GLT)))
        list_vso_init.extend(mini_GLT)
    if mini_nonGLT :
        if verb > 1 :
            print("++ Num nonGLT: {}".format(len(mini_nonGLT)))
        list_vso_init.extend(mini_nonGLT)
    if mini_other_Fstat :
        if verb > 1 :
            print("++ Num other_Fstat: {}".format(len(mini_other_Fstat)))
        list_vso_init.extend(mini_other_Fstat)

    # cut down number of stats, perhaps
    nvso_init = len(list_vso_init)
    if nvso_init > nmax_selfchosen:
        list_vso_init = list_vso_init[:nmax_selfchosen]
        if verb :
            print("++ Trimming number of stats dsets to view from {} to {}"
                  "".format(nvso_init, nmax_selfchosen))

    # preserve the relative order of stats within the stats dset
    list_vso_init.sort()
    for ll in list_vso_init :
        list_vso_final.append(ll[1])

    nvso_final = len(list_vso_final)

    # NB: len(good_labs) may not be eq to nvso_final
    return nvso_final, list_vso_final, good_labs, bad_labs

# ----------------------------------------------------------------------

# This is kind of an annoyingly long function, but kind of has the
# equivalent of 'case ..' in C.  With a lot of subcases.  
def check_plabs_in_label_list(list_plab, list_label, fname='', verb=0):
    '''Label prefixes (=prelabs) are the start of labels attached with
datasets.  This function finds which full labels are associated with
pre-labels (e.g., stimulus names).


    Parameters
    ----------
    list_plab    : (list) list of prelabs to be found homes within the  
                   list of labels
    list_label   : (list) list of full labels from stats dset header
    fname        : (str) filename from which info is coming---just to store
                   in any vso objects, for info
    verb         : (int) verbosity level

    Return
    ------
    nbad         : (int) number of bad plabs (i.e., unfound ones), so that
                   having a zero output means all matched within list_label
    bad_plabs    : (list) list of prelabs that do NOT appear within list_label
    good_plabs   : (list) list of prelabs that DO appear within list_label
    list_vso     : (list) list of vstat objects to use (one per 'good' prelab)
                   -> these become the main output to use
    nvso         : (int) len of list_vso, the number of obj to use

    '''
    
    ### below note:
    #    plab = the prefix part of a label (not: #, _GLT, _Coef,
    #           _Tstat, _Fstat)
    #    flab = full label, like the [i] entry in a stats dset header label
    #           list
    #    glab = full label, like flab, but of a different index
    #    vso  = the 'VStat Object' we want to listify for later APQC

    nplab  = len(list_plab)
    nlabel = len(list_label)
    nbad   = 0

    # check they are all present
    bad_plabs  = []
    good_plabs = []
    list_vso   = []

    # loop over each pre-label
    for plab in list_plab :
        # start obj; just is discarded if no match is found
        vso = vstat_obj()
        vso.set_label(plab)

        idx = 0
        while idx < nlabel :
            flab = list_label[idx]            # full label

            if flab == plab:
                # this should probably only happen for Full_Fstat?
                TF, is_lone = is_label_Fstat_and_lone(idx, list_label)
                if TF :
                    # is *some* Fstat
                    vso.set_olay_all_fstat(flab, idx)
                    vso.set_thr_all_fstat(flab, idx)
                    good_plabs.append(plab)
                    list_vso.append(vso)
                elif flab.endswith('_Coef') and \
                     is_label_some_Coef_with_Tstat(idx, list_label) :
                    # is *some* Coef/Tstat pair, perhaps even TENT, oddly
                    # but if user asked for it *exactly*, provide it
                    vso.set_olay_all_coef(flab, idx)
                    vso.set_thr_all_tstat(list_label[idx+1], idx+1)
                    good_plabs.append(plab)
                    list_vso.append(vso)
                else:
                    print("** ERROR: confused at full label == prelab check.\n"
                          "   Don't know what to do with prelabel: {}"
                          "".format(plab))
                    bad_plabs.append(plab)
                break

            elif flab.startswith(plab) and flab.endswith('_Coef') :
                # Now check if it is a good Coef/Tstat pairing
                if not(is_label_some_Coef_with_Tstat(idx, list_label)) :
                    print("** ERROR: confused at coef check.\n"
                          "   Don't know what to do with prelabel: {}"
                          "".format(plab))
                    bad_plabs.append(plab)
                    break

                # check if it is SERIES of Coef/Tstat
                TF, fidx = is_label_SERIES_Coef_with_Tstat(idx, list_label)
                if TF :
                    # since it is a SERIES, jump to the Fstat for this plab
                    vso.set_olay_all_fstat(list_label[fidx], fidx)
                    vso.set_thr_all_fstat(list_label[fidx], fidx)
                    good_plabs.append(plab)
                    list_vso.append(vso)
                    break
                else:
                    # is 'regular' or GLT Coef+Tstat pair: both fine
                    vso.set_olay_all_coef(flab, idx)
                    vso.set_thr_all_tstat(list_label[idx+1], idx+1)
                    good_plabs.append(plab)
                    list_vso.append(vso)
                    break

            elif flab.startswith(plab + '_GLT#') and \
                 flab.endswith('_Fstat') :
                # should not reach here: should be caught earlier
                print("+* WARNING: should have caught GLT FStat earlier?\n"
                      "   Observed for: {}"
                          "".format(plab))
                vso.set_olay_all_fstat(flab, idx)
                vso.set_thr_all_fstat(flab, idx)
                good_plabs.append(plab)
                list_vso.append(vso)
                break

            elif flab == plab + '_Fstat' :
                # should be a lone Fstat here, since no plab/'_Coef'
                # combo was found above
                vso.set_olay_all_fstat(flab, idx)
                vso.set_thr_all_fstat(flab, idx)
                good_plabs.append(plab)
                list_vso.append(vso)
                break
            idx+=1

        # if we don't find anything, it must be a bad label
        if idx >=nlabel:
            bad_plabs.append(plab)

    if bad_plabs :
        nbad = len(bad_plabs)
        print("+* WARNING: {} unlocatable user prefix for vstat label(s):\n"
              "      {}\n".format(nbad, ', '.join(bad_plabs)))
        if verb :
            print("   ... when searching through these actual labels:\n:"
                  "      {}\n"
                  "".format(', '.join(list_label)))

    # check for duplicates, in case user entered different forms
    # of same thing
    if 1 :
        list_olay = [vso.olay_label for vso in list_vso]
        # get list of any items whose overlay label is a repeat
        list_dup_idx = [ i for i in range(len(list_olay)) \
                         if list_olay.index(list_olay[i]) != i ]
        ndup = len(list_dup_idx)
        if ndup :
            if 1 : #verb :
                list_dup = [ list_olay[i] for i in range(len(list_olay)) \
                             if list_olay.index(list_olay[i]) != i ]
                print("+* WARNING: {} dups in user vstat list:".format(ndup))
                print("   {}".format(", ".join(list_dup)))

            # to remove, put list in order of decreasing index
            list_dup_idx.sort(reverse=True)
            for nn in list_dup_idx:
                tmp = list_vso.pop(nn)
                tmp = good_plabs.pop(nn)

    nvso_final = len(list_vso)

    return nbad, bad_plabs, good_plabs, list_vso, nvso_final

# ----------------------------------------------------------------------

def parse_stats_dset_labels( fname, 
                             use_Full_Fstat=True, 
                             nmax_selfchosen=5,
                             user_plabs=[],
                             verb=0):
    '''For a given stats_dset 'fname', make a list of subbricks that will
be visualized in the vstat QC block.  Users can specify a list of
label prefixes 'user_plabs' (to which Full will be prepended, by
default, if not included).  NB: These label prefixes don't include the
'_GLT', "#N", 'Tstat', 'Fstat', 'Coef', etc. parts of the full labels.  The
order of specified labels will be reflected in the output (see above
about Full).

If the user does not provide a list, this program has internal logic
to decide what to put into a list (up to nmax_selfchosen items).  The
relative order of chosen items will still follow the stats_dset
ordering.  Priority order:
  + Full_Fstat
  + *_GLT*{Coef,Tstat} pairs
  + remaining (stim) {Coef,Tstat} pairs
... all while avoiding TENT-like cases (pref#0_Coef, pref#1_Coef,
pref#2_Coef, etc.)
    
    Parameters
    ----------
    fname           : (str) name of a stats_dset
    use_Full_Fstat  : (bool) include Full (for Full_Fstat) in list, even if  
                      not specified by user (probably always want this True)
    nmax_selfchosen : (int) if the user has not specified a list of labels,
                      how many items should we choose to output
    user_plabs      : (list) a list of user-specified stim or GLT
                      labels to find within the stats_dset labels.  If 
                      empty, use internal 'default logic' to decide what 
                      to include in the QC block
    verb            : (int) verbosity level

    Return
    ------
    list_objs       : (list) a list of vstat_obj objects that contain all
                      necessary information for the vstat-processing function 
                      here

    '''

    # info from the dset
    dset_labels = get_dset_label_list(fname)
    nlabels     = len(dset_labels)

    list_vso    = []

    if user_plabs :
        if use_Full_Fstat :
            if not('Full_Fstat' in user_plabs) :
                user_plabs.insert(0, 'Full_Fstat')

        nbad, bad_plabs, good_plabs, list_vso, nvso = \
            check_plabs_in_label_list( user_plabs, dset_labels, fname )

    else:
        # 'default' case of using internal logit to decide.
        # also using default number of items to return
        nvso, list_vso, good_labs, bad_labs = \
            get_vstat_stats_default( dset_labels,
                                     nmax_selfchosen=nmax_selfchosen, 
                                     verb=verb )

    print("++ Found {} stats items to use for vstat QC block".format(nvso))

    if verb :
        for i in range(nvso): 
            print("{:4d} : olay = {:<20s} | thr = {}"
                  "".format(i, list_vso[i].olay_label, list_vso[i].thr_label) )

    return list_vso


# ---------------------------------------------------------------------

### ***This is not used anymore; this was only used in special cases
### ***before, previously, too, if someone put in a user_stats uvar.
### ***See the same function name *without* the "_OLD" in it.
def parse_stats_dset_labels_OLD( fname, lsearch = [] ) :
    '''fname is a stats_dset.

    lsearch is a list of user-specified stim or GLT labels (as well as
    the default 'Full_Fstat' label) to find within the stats_dset labels.

    OUTPUT: a list of vstat_obj objects that contain all necessary
    information for the vstat-processing function here.

    '''

    cmd_get_labelstr = '''3dinfo -label {}'''.format(fname)

    sint, so, se = ab.simple_shell_exec(cmd_get_labelstr, capture=1)
    sos          = so.split('|')
    llabels      = [x.strip() for x in sos]
    Nlabels      = len(llabels)

    cmd_get_nv   = '''3dinfo -nv {}'''.format(fname)

    sint, so, se = ab.simple_shell_exec(cmd_get_nv, capture=1)
    Nvols        = int(so.strip())

    if Nlabels != Nvols :
        print("** ERROR: Nlabels ({}) does not match Nvols ({})\n"
              "   in stats file {}.\n"
              "   Possible bad char (e.g., |) in str label?\n"
              "".format( Nlabels, Nvols, fname))
        sys.exit(5)

    list_pre_found = []
    list_objs = []

    for pre in lsearch:
        i = 0
        while i < Nlabels :
            x = llabels[i]
            if x.find(pre)==0 :
                # first one found will be for the olay
                list_pre_found.append(pre)
                vso = vstat_obj()
                vso.set_label(pre)
                vso.set_stats_dset(fname)
                addtoi = 1

                post = x[-5:]
                if post == '_Coef' :
                    vso.set_olay_all_coef(x, i)
                    # and check for accompanying stat
                    if i+1 < Nlabels :
                        j = i+1
                        y = llabels[j]
                        if y.find(pre)==0 :
                            addtoi+=1
                            post = y[-5:]
                            if post == 'Tstat' :
                                vso.set_thr_all_tstat(y, j)
                                if j+1 < Nlabels :
                                    k = j+1
                                    z = llabels[k]
                                    if z.find(pre)==0 :
                                        post = z[-5:]
                                        if post == 'Fstat' :
                                            # all we do is add to
                                            # index-- ignore other steps
                                            addtoi+=1
                            elif post == 'Fstat' :
                                vso.set_thr_all_fstat(y, j)
                            else:
                                print("ERROR: no stat brick to accompany"
                                      "[{}]th volume {} in {}?"
                                      "".format(i, x, fname))
                                sys.exit(7)
                elif post == 'Tstat' :
                    vso.set_thr_all_tstat(x, i)
                    if i+1 < Nlabels :
                        j = i+1
                        y = llabels[j]
                        if y.find(pre)==0 :
                            post = y[-5:]
                            if post == 'Fstat' :
                                # all we do is add to index-- ignore
                                # other steps
                                addtoi+=1
                elif post == 'Fstat' :
                    vso.set_olay_all_fstat(x, i)
                    vso.set_thr_all_fstat(x, i)
                else:
                    print("ERROR: what unexpected volume is this, of unknown"
                          " type?  [{}]th volume {} in {}?"
                          "".format(i, x, fname))
                    sys.exit(7)

                #print("++ Found: {} in: {} and {}  ... addtoi= {}"
                #      "".format(pre, vso.olay_label, vso.thr_label, addtoi))
                list_objs.append(vso) 
                
                i+= addtoi # we accumulate how many to add
            else :
                i+= 1

    for pre in lsearch:
        if not(list_pre_found.__contains__(pre)) :
            print("*+ WARNING: user-asked-for stim or GLT label '{}'\n"
                  "            has not been found in the stats dset: {}"
                  "".format(pre, fname))

    return list_objs







# ----------------------------------------------------------------------
# ----------------------------------------------------------------------
# ----------------------------------------------------------------------
# ----------------------------------------------------------------------

if __name__ == "__main__" :

    # demo examples
    homedir  = os.path.expanduser('~')
    # standard Bootcamp stats dset from s05*
    bootstat = homedir + '/AFNI_data6/FT_analysis/FT.results/'
    bootstat+= 'stats.FT+tlrc.HEAD'

    # from tweaked s05* to run with TENTs
    bootstat2 = homedir + '/AFNI_data6/FT_analysis/FT_TENT.results/'
    bootstat2+= 'stats.FT_TENT+tlrc.HEAD'

    list_stats = [ bootstat, bootstat2 ]

    for stats_dset in list_stats:
        if os.path.exists(stats_dset) :
            print("\n"*2)
            print("++ Test stats stuff for dset:")
            print("   {}".format(stats_dset))

            labs = get_dset_label_list(stats_dset)

            print(80*'=')
            print("++ Check for Full_Fstat")
            print("{:>4s}    {:2s}    {:<20s}"
                  "".format('IDX', 'TF', 'STATS LABEL'))
            print("{:>4s}    {:2s}    {:<20s}"
                  "".format(4*'-', 2*'-', 20*'-'))
            for i in range(len(labs)): 
                yesno = is_label_Full_Fstat(i, labs)
                print("{:4d} -> {:2d} :  {:<20s}"
                      "".format(i, yesno, labs[i]))
            print(80*'=')


            print(80*'=')
            print("++ Check for regular_Coef_with_Tstat")
            print("{:>4s}    {:2s}  {:4s}    {:<20s}"
                  "".format('IDX', 'TF', 'FIDX', 'STATS LABEL'))
            print("{:>4s}    {:2s}  {:4s}    {:<20s}"
                  "".format(4*'-', 2*'-', 4*'-', 20*'-'))
            for i in range(len(labs)): 
                yesno, fidx = is_label_REG_Coef_with_Tstat(i, labs)
                print("{:4d} -> {:2d}, {:4d} :  {:<20s}"
                      "".format(i, yesno, fidx, labs[i]))
            print(80*'=')


            print(80*'=')
            print("++ Check for TENT_Coef_with_Tstat")
            print("{:>4s}    {:2s}  {:4s}    {:<20s}"
                  "".format('IDX', 'TF', 'FIDX', 'STATS LABEL'))
            print("{:>4s}    {:2s}  {:4s}    {:<20s}"
                  "".format(4*'-', 2*'-', 4*'-', 20*'-'))
            for i in range(len(labs)): 
                yesno, fidx = is_label_SERIES_Coef_with_Tstat(i, labs)
                print("{:4d} -> {:2d}, {:4d} :  {:<20s}"
                      "".format(i, yesno, fidx, labs[i]))
            print(80*'=')
        

            print(80*'=')
            print("++ Check for Fstat_and_lone")
            print("{:>4s}    {:2s}  {:4s}    {:<20s}"
                  "".format('IDX', 'TF', 'LONE', 'STATS LABEL'))
            print("{:>4s}    {:2s}  {:4s}    {:<20s}"
                  "".format(4*'-', 2*'-', 4*'-', 20*'-'))
            for i in range(len(labs)): 
                yesno, fidx = is_label_Fstat_and_lone(i, labs)
                print("{:4d} -> {:2d}, {:4d} :  {:<20s}"
                      "".format(i, yesno, fidx, labs[i]))
            print(80*'=')
        
        # test user plabs funcs
        if 1 :
            list_plab = ['vis', 'aud', 'V-A', 'Full_Fstat', 'blah']
            nbad, list_bad, list_good, list_vso = \
                check_plabs_in_label_list(list_plab, labs, stats_dset)

            print(80*'=')
            print("++ user list labels:\n   {}\n".format(list_plab))
            print("++ full label list:\n   {}\n".format(labs))
            print("-> bad label list:\n   {}\n".format(list_bad))
            print("-> good label list (detailed, with olay and thr labels):")
            for i in range(len(list_good)):
                print("{:<15s} : {:<20s} &  {:<20s}"
                      "".format(list_good[i], 
                                list_vso[i].olay_label,
                                list_vso[i].thr_label))

