author    = "PA Taylor (NIMH, NIH)"
#version   = "1.0"; date  = "Oct 24, 2018"
# + birth
#
#version   = "1.2345"; date  = "Nov 1, 2018"
# + [PT] now a working beta version, including boxplots
#
#version   = "1.3"; date  = "Nov 28, 2018"
# + [PT] fixed censor_hline stuff- now is list of floats, except
#        at single plot level, when is just a float
#
#version   = "1.31"; date  = "Dec 5, 2018"
# + [PT] at the moment, numpy isn't actually used here, so removing
#        that import-- I would expect it to return *someday*, though.
#
#version   = "1.4"; date  = "Jan 14, 2019"
# + [PT] calc 1D-plot LW from Npts
#
#ver = '1.5' ; date = 'Feb 16, 2020' 
# + [PT] add in ability for -censor_hline to contain a 'NONE' argument 
#        ... bc that is useful as a placeholder when some images have
#        censor vals and others don't
#
#ver = '1.9' ; date = 'June 17, 2020' 
# [PT] add in legend, legend_label and legend_loc functionality
#
#ver = '2.0' ; date = 'April 22, 2020'
# [PT] can now wrap y-axis labels, because they might get long
#    + couple ways of trying: purely by length, or with some 'logic'
#
ver = '2.1' ; date = 'May 11, 2020'
# [PT] replace str.isnumeric() with str.isdigit(), for backward
#      compatibility with Python v2.7.  Grrr.
#
# =================================================================

import sys
import os
import matplotlib.pyplot  as plt
from   matplotlib         import rcParams, gridspec
from   matplotlib.ticker  import AutoMinorLocator
import matplotlib.patches as matpat
from afnipy import lib_afni1D         as LAD
from afnipy import lib_apqc_io        as laio
from afnipy import afni_util          as au

MAXLEN = 10**7     # can adjust if that is ever necessary!
EPS    = 10**(-14)

MULTIRUN_SHADE = '0.95'

# =====================================================================
# =====================================================================

def long_string_wrap_by_char(x, maxlen = 7):
    '''Sometimes labels are long, and you might want to wrap them to be at
    most a certain length per line (e.g., long labels for regressors
    of interest).  If 'x' has a newline char in that, that is OK; we
    will split with that char, and build based on the rest of the
    pieces.

    This is the more complicated version of wrapping: pay attention to
    contents, chopping into possibly ragged lines.

    This is a helper function for: long_string_wrap().

    Params
    ------

    x      : input str
    maxlen : max number of chars per line (inclusively)

    Return
    ------

    z      : str, version of x with '\n' inserted to obey wrapping len

    '''

    xlen   = len(x)

    # simple cases
    if type(x) != str : return '', 0
    if xlen <= maxlen : return x, 1

    xlist  = x.split('\n')
    zsplit = []
    
    for y in xlist:
    
        ybag = []
        the_str = y
        count = 0
        while len(the_str) :
            full_len = len(the_str)-1
            for i in range(full_len):
                # things that partition at [i]
                if ( the_str[i] == '_' ) :
                    ybag.append(the_str[:i])
                    ybag.append(the_str[i])
                    the_str = the_str[i+1:]
                    break
                # things that split from [i]
                elif ( i>0 and the_str[i].isupper() and \
                       the_str[i+1].islower())              or \
                    ( i>0 and the_str[i] == '#' ) :
                    ybag.append(the_str[:i])
                    the_str = the_str[i:]
                    break
                # things that split from [i+1]
                elif ( the_str[i].isdigit() and \
                       not(the_str[i+1].isdigit()))        or \
                     ( not(the_str[i].isdigit()) and \
                       not(the_str[i] == '#') and      \
                       the_str[i+1].isdigit())  :
                    ybag.append(the_str[:i+1])
                    the_str = the_str[i+1:]
                    break
            if i == full_len-1 :
                ybag.append(the_str)
                the_str = ''
                break
                    
            count+= 1
            if count == 1000:
                print("+* Warn: went into infinite (well, 1000) loop?\n"
                      "   Stopping, and using simple split output")
                return long_string_wrap_by_len(x, maxlen = maxlen)

        # forward pass to glue
        ny   = len(ybag)
        idx  = 0

        while idx < ny :
            word = ''
            lw   = len(word)
            i    = idx
            while i < ny:
                if lw < maxlen:
                    lnew = len(ybag[i])
                    if lw + lnew <= maxlen :
                        word+= ybag[i]
                        i+= 1
                    elif lnew > maxlen :
                        word+= ybag[i][:maxlen-lw]
                        ybag[i] = ybag[i][maxlen-lw:]
                        break
                    else:
                        break
                    lw = len(word)
                else:
                    break
            idx = i

            zsplit.append(word)

    #print("DONE the bag:", ybag)
    #print("DONE the str:", the_str)
    #print("DONE zsplit:", zsplit)

    nlines = len(zsplit)
    z      = '\n'.join(zsplit)

    return z, nlines

def long_string_wrap_by_len(x, maxlen = 7):
    '''Sometimes labels are long, and you might want to wrap them to be at
    most a certain length per line (e.g., long labels for regressors
    of interest).  If 'x' has a newline char in that, that is OK; we
    will split with that char, and build based on the rest of the
    pieces.

    This is the simple version of wrapping: ignore any contents, just
    chop into constant length lines.

    This is a helper function for: long_string_wrap().

    Params
    ------

    x      : input str
    maxlen : max number of chars per line (inclusively)

    Return
    ------

    z      : str, version of x with '\n' inserted to obey wrapping len

    '''

    xlen   = len(x)

    # simple cases
    if type(x) != str : return '', 0
    if xlen <= maxlen : return x, 1

    xlist  = x.split('\n')
    zsplit = []
    
    for y in xlist:
        # basic, split every 'maxlen' chars
        ylen   = len(y)
        npiece = ylen // maxlen

        for nn in range(npiece):
            zsplit.append( y[nn*maxlen:(nn+1)*maxlen] )
        if ylen % maxlen :
            zsplit.append( y[npiece*maxlen:] )
        
    nlines = len(zsplit)
    z      = '\n'.join(zsplit)

    return z, nlines

def long_string_wrap(x, maxlen = 7, verb=0):
    '''Main function wrapping long strings into multiple shorter ones,
    inserting newline chars.  This calls a couple ways of trying to
    shorten, and then picks the "best".
    '''

    x_bylen,  n_bylen  = long_string_wrap_by_len( x, maxlen=maxlen )
    x_bychar, n_bychar = long_string_wrap_by_char( x, maxlen=maxlen )

    if verb > 1 :
        bord = "-"*maxlen
        print("++ String, wrapped by char ({} lines):\n{}\n"
              "{}\n{}".format(n_bychar, bord, x_bychar, bord))
        print("++ String, wrapped by len ({} lines):\n{}\n"
              "{}\n{}".format(n_bylen, bord, x_bylen, bord))

    if n_bychar <= n_bylen :
        if verb:
            print("++ Output: wrapped by char")
        return x_bychar
    else:
        if verb:
            print("++ Output: wrapped by len")
        return x_bylen

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

# Note: current size limit of plots is 10**6 values in a column, if
# using create_xarrs via "-xvals ..."

# 'bf' stands for 'bigfig' (figplobj); 'ss' for single subject
# (subplobj).
def make_1dplot_figure(bf):
    '''Input 'bf' is an instance of the figplobj object.  It is made up of
lots of individual subject 'ss' instances of the subplobj object.

    '''

    fff = plt.figure(bf.title, figsize=bf.figsize)

    rcParams['font.family'] = bf.fontfamily
    rcParams['axes.linewidth'] = 0.5         # de-emphasize
    #plt.xkcd() # ha!

    # we can add in one summary boxplot per subj 
    # at the moment, can't have one_graph with boxplot
    if bf.boxplot_on :
        ncol = 2
        a, subpl = plt.subplots( bf.nsub, ncol, 
                                 gridspec_kw = {'width_ratios':[12, 1],
                                 'wspace':0.05},
                                 sharey='row',
                                 squeeze=True,
                                 figsize=bf.figsize )
    else:
        ncol = 1
        a, subpl = plt.subplots( bf.ngraph, ncol, 
                                 figsize=bf.figsize )

    for i in range(bf.nsub):
        ss = bf.all_subs[i]
        ii = i+1

        # just relabel, need to account for different cases of what
        # subpl is and what its shape is (if it even has a shape!
        if bf.boxplot_on :
            if bf.nsub > 1:
                pp = subpl[i,0]
            else:
                pp = subpl[0]
        else:
            if bf.ngraph > 1:
                pp = subpl[i]
            else:
                pp = subpl

        # ----------------- Main plot: time series ---------------------

        if bf.npatch :
            rsum = ss.x[0]
            for vv in range(0, bf.npatch-1, 2):
                rsum+= bf.patch_arr[vv]
                pp.add_patch(
                    matpat.Rectangle( ( rsum, 
                                        ss.ylim[0] ),
                                      width=bf.patch_arr[vv+1],
                                      height=(ss.ylim[1] - ss.ylim[0]),
                                      facecolor=laio.DEF_patch_alt_RGB,
                                      lw=0, edgecolor=None, alpha=None) ) 
                rsum+= bf.patch_arr[vv+1]

        if bf.ncensor : 
            xoffset = 0.5 * bf.censor_width
            for cc in range(bf.ncensor):
                pp.add_patch(
                    matpat.Rectangle( (bf.censor_arr[cc] - xoffset, 
                                       ss.ylim[0]),
                                      width=bf.censor_width,
                                      height=(ss.ylim[1] - ss.ylim[0]),
                                      facecolor=bf.censor_RGB,
                                      lw=0, edgecolor=None, alpha=None) )

        if ss.censor_hline : 
            if ss.censor_hline != 'NONE' :
                pp.axhline( y=ss.censor_hline, 
                            c=laio.DEF_censor_hline_RGB, 
                            ls=':', lw=1)

        if bf.see_xax : 
            pp.axhline(y=0, c='0.6', ls='-', lw=0.5)

        if not(bf.margin_on) :
            a.subplots_adjust(left=0.0, bottom=0.0, right=1, top=1)

        ## the actual plot. 
        # [PT: Jan 14, 2019] now get line width as a function of the
        # number of points.
        #if not(bf.margin_on) :
        #    pp.axis('off')
        sp = pp.plot( ss.x, ss.y, 
                      color = ss.color,
                      lw=calc_lw_from_npts(ss.npts),
                      label=ss.leglabel )

        # if using yrange: can have a semitransparent filled area
        if len(ss.yrantop) :
            spran = pp.fill_between( ss.x, ss.yranbot, ss.yrantop, 
                                     color = ss.color,
                                     alpha=0.2,
                                     lw=0.0 )

        pp.set_xlim(ss.xlim)
        pp.set_ylim(ss.ylim)

        pp.set_xlabel(ss.xlabel, fontsize=bf.fontsize)

        if bf.ylabels_maxlen :
            ss.ylabel = long_string_wrap(ss.ylabel, maxlen=bf.ylabels_maxlen)

        pp.set_ylabel(ss.ylabel, fontsize=bf.fontsize)

        # get ylabels aligned horizontally
        pp.get_yaxis().set_label_coords(-0.1, 0.5)
        print("++ Plotting: {}".format(ss.ylabel))

        pp.xaxis.set_minor_locator(AutoMinorLocator(5))
        pp.yaxis.set_minor_locator(AutoMinorLocator(2))
        pp.tick_params( axis='both', which='minor', direction='in', color='0.5',
                        bottom=True, left=True, right=True ) #, top=True )
        pp.tick_params( axis='both', which='major', direction='in', color='0.5',
                        bottom=True, left=True, right=True ) #, top=True )
        if bf.margin_on :
            pp.spines['bottom'].set_color('0.5')
            pp.spines['top'   ].set_color('0.5')
            pp.spines['left'  ].set_color('0.5')
            pp.spines['right' ].set_color('0.5')

        # only show tick labels at very bottom
        if i < bf.ngraph-1 :
            # [PT] pp.set_xticks() here just to suppress (unnec) warning:
            # "FixedFormatter should only be used together with FixedLocator"
            # ... but it also makes the xaxes vary across a plot, so remove 
            # for now. Maybe come back to this...
            #pp.set_xticks(pp.get_xticks()) 
            nlabs = len(pp.get_xticklabels())
            pp.set_xticklabels(['']*nlabs)

        if bf.title and not(i):
            # cheating with title because tight layout doesn't know about
            # suptitle
            pp.set_title(bf.title, fontsize=bf.fontsize)

        if bf.legend_on :
            pp.legend(loc=ss.legloc)


        # ----------------- Optional plot: boxplot ---------------------

        if bf.boxplot_on :
            if bf.nsub > 1:
                qq = subpl[i,1]
            else:
                qq = subpl[1]

            if ss.censor_hline : 
                if ss.censor_hline != 'NONE' :
                    qq.axhline( y=ss.censor_hline, 
                                c=laio.DEF_censor_hline_RGB, 
                                ls=':', lw=1)

            if bf.see_xax : 
                qq.axhline(y=0, c='0.6', ls='-', lw=0.5)

            # ------------ actual boxplot ------------------

            ## [PT: Jan 15, 2019] Add functionality to see boxplot of
            ## censored data, too, if not turned off *when* censoring
            ## and asking for a boxplot
            if bf.bplot_view == "BC_AC" :
                W = 0.15
                sq = qq.boxplot( [ss.y, ss.ycen],
                                 widths=W,
                                 positions=[0.25, 0.75],
                                 sym='.',
                                 notch=0, 
                                 patch_artist=True )
                ''' # unfortunately, this may not be workable to distinguish
                W3 = 3*W
                qq.add_patch(
                    matpat.Rectangle( ( 0.25 - W3/2.0, 
                                        ss.ylim[0] ),
                                      width=W3,
                                      height=(ss.ylim[1] - ss.ylim[0]),
                                      facecolor=bf.censor_RGB,
                                      lw=0, edgecolor=None, alpha=None) )
                '''
                if not(i):
                    # cheating with title because tight layout doesn't
                    # know about suptitle: BC='before censoring', and
                    # AC='after censoring'
                    qq.set_title( "BC AC", fontsize=bf.fontsize )

            elif bf.bplot_view == "BC_ONLY" :
                sq = qq.boxplot( ss.y,
                                 widths=0.1,
                                 sym='.',
                                 notch=0, 
                                 patch_artist=True )
            elif bf.bplot_view == "AC_ONLY" :
                sq = qq.boxplot( ss.ycen,
                                 widths=0.1,
                                 sym='.',
                                 notch=0, 
                                 patch_artist=True )
                if not(i):
                    qq.set_title( "AC", fontsize=bf.fontsize )
                


            # fun parameter-setting for boxplot
            SETLW = 1.
            MARKSIZE1 = 8
            MARKSIZE2 = 5
            flilines = sq['fliers']
            for line in flilines:
                line.set_color(ss.color)
                line.set_markersize(MARKSIZE1)
            medlines = sq['medians']
            for line in medlines:
                line.set_color('0.7')
                line.set_linewidth(SETLW*1.25 )
            boxlines = sq['boxes']
            for line in boxlines:
                line.set_color(ss.color)
            plt.setp(sq['fliers'], marker='.', mew=0.3, mec='k', mfc=ss.color, 
                     color=ss.color, ms=MARKSIZE2)
            plt.setp(sq['whiskers'], color=ss.color, linestyle='-', lw=SETLW )
            plt.setp(sq['caps'],     color=ss.color, linestyle='-', lw=SETLW )

            # no xticks/labels
            if 1:
                qq.set_xticks([])

            # stuff for y-axis ticks (on) and labels (off)
            qq.yaxis.set_minor_locator(AutoMinorLocator(2))
            qq.tick_params( axis='y', which='minor', direction='in', 
                            color='0.5',
                            bottom=True, left=True, right=True ) #, top=True )
            qq.tick_params( axis='y', which='major', direction='in', 
                            color='0.5',
                            bottom=True, left=True, right=True,
                            labelleft=False) #, top=True )
            qq.spines['bottom'].set_color('0.5')
            qq.spines['top'   ].set_color('0.5')
            qq.spines['left'  ].set_color('0.5')
            qq.spines['right' ].set_color('0.5')



    # finishing touches

    if bf.layout == 'nospace' :
        fff.subplots_adjust( wspace=0.1, hspace=0.1 )
    #elif bf.layout == 'tight':
    #    plt.tight_layout()

    if not(bf.margin_on) :
        plt.savefig( bf.fname, dpi=bf.dpi, facecolor=bf.bkgd_color)
    else :
        plt.savefig( bf.fname, dpi=bf.dpi, facecolor=bf.bkgd_color,
                     bbox_inches='tight')
    print("++ Done! Figure created:\n\t {}".format(bf.fname))

    return 0

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

def calc_lw_from_npts(N):
    if N < 450:
        return 1.5
    elif N < 1200:
        return 1.0
    elif N < 2550:
        return 0.75
    else:
        return 0.5

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

# make the 'bigfig' object (type: figplobj) from lots of individual
# 'ss' subjects (type: subplobj).
def populate_1dplot_fig(iopts):

    # The order of setting things here matters in some cases, so don't
    # rearrange without mega-testing.
    bigfig = laio.figplobj()
    bigfig.set_dpi( iopts.dpi )
    bigfig.set_layout( iopts.layout )
    bigfig.set_title( iopts.title )
    bigfig.set_fname( iopts.prefix )
    bigfig.set_fontsize( iopts.fontsize )
    bigfig.set_fontfamily( iopts.fontfamily )
    bigfig.set_fontstyles( iopts.fontstyles )
    bigfig.set_censor_arr( iopts.censor_arr )
    bigfig.set_censor_width( iopts.censor_width )
    bigfig.set_censor_RGB( iopts.censor_RGB )
    bigfig.set_censor_on( iopts.censor_on )
    bigfig.set_censor_hline( iopts.censor_hline )
    bigfig.set_patch_arr( iopts.patch_arr )
    bigfig.set_boxplot( iopts.boxplot_on )
    bigfig.set_margin( iopts.margin_on )
    bigfig.set_bplot_view( iopts.bplot_view )
    bigfig.set_legend_on( iopts.legend_on )
    bigfig.set_ylabels_maxlen( iopts.ylabels_maxlen )

    for i in range(iopts.ndsets):

        # can reverse the order of plotting, relative to input
        if iopts.reverse_order : 
            ii = iopts.ndsets - i - 1
        else:
            ii = i

        ss = laio.subplobj()

        ss.set_color(iopts.get_color(i))

        ss.set_x( iopts.all_x )
        ss.set_xlim()
        # only put xlabel on bottom panel
        if i < iopts.ndsets - 1:
            ss.set_xlabel("")
        else:
            ss.set_xlabel( iopts.xlabel )

        ss.set_y( iopts.all_y[ii] )
        # include plus/min y-range values, if they were input
        if len(iopts.all_yrantop) :
            ss.set_ylim_use_pm( iopts.ylim_use_pm )
            ss.set_yran( iopts.all_yranbot[ii], iopts.all_yrantop[ii] )

        # if not a null set, then there is one for each subj; for
        # plotting in "after censoring" boxplot, if selected at the
        # moment, only a single censor list gets applied to whole
        # plot.  This gets calculated, even if not applied.
        if bigfig.boxplot_ycen : 
            ycen = make_censored_sublist( ss.y, bigfig.censor_arr )
            ss.set_ycen( ycen )

        # if not a null set, then there is one for each subj
        if iopts.censor_hline : 
            ss.set_censor_hline(iopts.censor_hline[ii] )

        if iopts.onescl :
            ss.set_ylim( laio.rangeize_plots( iopts.all_ymin, 
                                              iopts.all_ymax,
                                              firm_lim = iopts.yaxran[ii]) )
        else:
            ss.set_ylim( ext_ylim=iopts.yaxran[ii] )
        ss.set_ylabel( iopts.ylabels[ii] )

        if bigfig.legend_on :
            ss.set_leglabel( iopts.leglabels[ii] )
            ss.set_legloc( iopts.leglocs[ii] )

        bigfig.add_sub(ss)

    # depends on final number of subjects
    bigfig.set_figsize( iopts.figsize )
    bigfig.set_ngraph( iopts.one_graph )

    return bigfig

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

# Apply censoring to a time series, to generate a list of censored
# values (for boxplotting, mainly)
def make_censored_sublist( y, cen_arr ):

    Ny    = len(y)
    olist = []

    for i in range(Ny):
        if not(cen_arr.__contains__(i)) :
            olist.append( y[i] )

    return olist

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

def populate_1dplot_arrays(iopts):

    iopts.create_all_y()
    ok = iopts.check_dims_all_y() # check with number ylabels

    iopts.create_all_x()
    ok2 = iopts.check_dims_all_x_ys()

    iopts.create_all_yran()  # if plus/min ranges entered; len checked here.

    # once arrays are read in, so the total number is known, sort out
    # color table: will either be what user entered or default palette
    # if ndsets>1; otherwise, just a black line.
    iopts.check_color_table()

    # once arrays are read in (particularly x values), make the censor list
    iopts.set_censor_arr()

    # once arrays are read in, check if the number censor hlines is
    # good: it must be either 0 (no lines added), 1 (same line added
    # to all) and/or match the number of input yfiles
    iopts.check_censor_hlines()
    # ... and check this in the same way: scales for y-axis stretch.
    #     must be done *after* censor hlines are set, and *before* yrange
    #     is assigned; censor hlines will be set to 1 here
    iopts.check_scales()
    if iopts.count_scale() :
        iopts.apply_scales_all_y()
        iopts.apply_scales_censor_hlines()
    # ... and check this in the same way: optional user entry for
    # ylims; this is *not* scaled-- user's value is used directly
    iopts.check_yaxran()

    return 0

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

class apqc_1dplot_plopts:

    Nplots  = 0
    ylabels = []
    yscales = []
    xlabel  = ""
    title   = ""
    prefix  = "OUTPUT_1D.jpg"
    dpi     = 100

    def add_ylabel(self, ylabel):
        self.ylabels.append(ylabel)
        self.Nplots+= 1

    def add_yscale(self, bot, top):
        self.yscales.append([bot, top])

    def set_xlabel(self, xlabel):
        self.xlabel = xlabel

    def set_title(self, title):
        self.title = title

    def set_prefix(self, prefix):
        self.prefix = prefix

    def set_dpi(self, dpi):
        self.dpi = dpi

    # check lots of properties for consistency
    def check_all(self):
        MISS = 0
        if Nplots != len(self.ylabels):
            print("mismatched Nplots-len(ylabels)")
            MISS+=1
        if Nplots != len(self.yscales):
            print("mismatched Nplots-len(yscales)")
            MISS+=1
        if not(self.ylabels) :
            print("missing: ylabels-- things to plot!")
            MISS+=1
        return MISS

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


########################### not used (and broken) #############################
####################### maybe will revisit this, if necessary #################
#
#import numpy as np
#
#def make_locations_ticks(N, lims):
#    '''N is number of (major) ticks to have, and lims is an array of upper
#and lower limits.  There will be 4 minor ticks placed throughout.'''
#
#    nice_deltas = [ 1, 2, 2.5, 3, 4, 5, 7.5, 10 ] 
#    Nnice       = len(nice_deltas)
#    
#
#    if N <= 0 :
#        sys.exit("** ERROR: need > 0 tick to set locations!")
#    if lims[0] > lims[1] :
#        sys.exit("** ERROR: lower limit greater than top one?!")
#    elif lims[0] == lims[1] :
#        out = [ -0.1, 0.1 ] # just pick some interval
#
#    fullrange = lims[1] - lims[0]
#    delta0 = float(fullrange)/N
#
#    INCLUDES_ZERO = (np.sign(lims[0])*np.sign(lims[1]) <= 0 )
#
#    if not(INCLUDES_ZERO) :
#        pp = np.round(np.log10(np.abs(delta0)))
#        nd_scal = (10**pp) * np.array(nice_deltas)
#        print("PP and ND_SCALE:  {} and {}".format(pp, nd_scal))
#        mindiff = 10**(pp+5)
#        indmin  = Nnice
#        for i in range(Nnice):
#            diff = nd_scal[i] - delta0
#            print("[{}] {} ---> DIFF: {}".format(i, nd_scal[i], diff))
#            if ( diff > 0 ) and ( diff < mindiff ) :
#                mindiff = diff
#                indmin = i
#        print("delta0 = {} and chose mindiff = {}, delta: {}".format(delta0, mindiff, nd_scal[indmin]))
#
#def adapt_num_to_sigfigs(N, nsigfig=2, rtype='RDOWN'):
#    '''Take a number 'N' (can be int or float) and return it rounded to
#the nearest 'nsigfig' significant figures. The default methodology at
#the moment of rounding is rtype='RDOWN', but this can be adjusted to
#either 'RUP' or 'ROUND', where
#
#    RDOWN : round magnitude down
#    RUP   : round magnitude up
#    ROUND : round in a rounding manner
#
#The output type will float if there are nonzero values to the right of
#the decimal point (to 14 decimals precision); otherwise, int.
#
#    '''
#
#    # when pushing around numbers, how many decimals to keep in the
#    # sci not
#    ndec = nsigfig - 1 
#
#    a     = '{:E}'.format(abs(N))
#    nsign = np.sign(N)
#    b     = a.split('E')
#    npow  = int(b[1]) # will be an integer
#
#    c = float(b[0]) * 10**ndec
#    if rtype == 'RDOWN' :
#        nbase = int(np.floor(c))
#    elif rtype == 'RUP' :
#        nbase = int(np.ceil(c))
#    elif rtype == 'ROUND' :
#        nbase = int(np.round(c, decimals=0))
#
#    nmove = npow - ndec
#    print("NBASE: {}, npow {}, ndec {}, nmove {}".format(nbase, npow, ndec, nmove))
#    sbase = str(nbase)  # string form of integer, need to put decimal
#                        # back in
#    lbase = list(sbase) # list of chars
#    if nmove < 0 :
#        nchar = len(lbase)
#        if abs(nmove) >= nchar:
#            npad = abs(nmove) - nchar
#            out = ''.join(['0.'] + ['0']*npad + lbase)
#        else:
#            out = ''.join(lbase.insert(nmove, '.'))
#
#    elif nmove == 0 :
#        out = sbase
#    elif nmove > 0 :
#        nrhs = len(sbase)
#        stmp = sbase + ( '0'*(nmove - nrhs) )
#        ltmp = [stmp[0]]
#        for i in range(nmove):
#            ltmp.append(stmp[i+1])
#        ltmp.append(stmp[nmove:])
#        out = ''.join(ltmp)
#
#    out = float(out)
#    # check whether it is really an integer or not
#    remainder = out % 1
#    if np.abs(remainder) < EPS:
#        out = int(out)
#        if out:
#            return nsign*out
#        else:
#            return out
#    else:
#        return nsign * out
#
#
#
#
#
#
#
#
#
