#! /usr/bin/env python3

"""\
%(prog)s [options] <yodafile1> [<yodafile2> <yodafile3>...] [PLOT:Key1=Val1:...]

Make web pages from histogram files written out by Rivet.  You can specify
multiple Monte Carlo YODA files to be compared in the same syntax as for
rivet-cmphistos, i.e. including plotting options.

Reference data, analysis metadata, and plot style information should be found
automatically (if not, set the RIVET_ANALYSIS_PATH or similar variables
appropriately).

Any existing output directory will be overwritten.

ENVIRONMENT:
 * RIVET_ANALYSIS_PATH: list of paths to be searched for analysis plugin libraries
 * RIVET_DATA_PATH: list of paths to be searched for data files
"""

import sys
if sys.version_info[:1] < (3,):
    print("rivet-mkhtml requires Python3 ... exiting")
    sys.exit(1)

import rivet, yoda
import os, re, subprocess
from rivet.plotting.make_plots import assemble_plotting_data
from yoda.plotting import script_generator
import multiprocessing
import importlib

#rivet.util.check_python_version()
rivet.util.set_process_name(os.path.basename(__file__))
COMMAND = " ".join([os.path.basename(sys.argv[0])] + sys.argv[1:])

import shutil
import argparse
try:
    nproc = min(1,multiprocessing.cpu_count()-1)
except:
    nproc = 1

parser = argparse.ArgumentParser(usage=__doc__)
parser.add_argument("YODAFILES", nargs="+", help="data files to compare")
parser.add_argument("-o", "--outputdir", dest="OUTPUTDIR",
                    default="./rivet-plots", help="directory for Web page output")
parser.add_argument("-c", "--config", dest="CONFIGFILES", action="append", default=['~/.make-plots'],
                    help="plot config file(s) to be used with rivet-cmphistos")
parser.add_argument("-j", "--jobs", metavar="JOBS", dest="JOBS", type=int,
                    default=None, help="number of parallelized processes to run [%s]" % nproc)
parser.add_argument("--ignore-missing", dest="IGNORE_MISSING", action="store_true",
                    default=False, help="ignore missing YODA files")
parser.add_argument("-i", "--ignore-unvalidated", dest="IGNORE_UNVALIDATED", action="store_true",
                    default=False, help="ignore unvalidated analyses")
# parser.add_argument("--ref", "--refid", dest="REF_ID",
#                   default=None, help="ID of reference data set (file path for non-REF data)")
parser.add_argument("--no-rivet-refs", dest="RIVETREFS", action="store_false",
                        default=True, help="don't use Rivet reference data files")
parser.add_argument("--dry-run", help="don't actually do any plotting or HTML building", dest="DRY_RUN",
                    action="store_true", default=False)
parser.add_argument("--pwd", dest="PATH_PWD", action="store_true", default=False,
                    help="append the current directory (pwd) to the analysis/data search paths (cf. $RIVET_ANALYSIS_PATH)")
parser.add_argument("--style", dest="STYLE", help="Choose the plotting style", default='default')

stygroup = parser.add_argument_group("Style options")
stygroup.add_argument("-t", "--title", dest="TITLE",
                      default="Plots from Rivet analyses", help="title to be displayed on the main web page")
stygroup.add_argument("--reflabel", dest="REFLABEL",
                      default="Data", help="legend entry for reference data")
stygroup.add_argument("--ratiolabel", dest="RATIOPLOTLABEL",
                      default=None, help="label on ratio panel")
stygroup.add_argument("--deviation", dest="DEVIATION", action="store_true",
                      default=False, help="rescale ratio panel to standard deviation (bin by bin)")
#stygroup.add_argument("--no-plottitle", dest="NOPLOTTITLE", action="store_true",
#                      default=False, help="don't show the plot title on the plot "
#                      "(useful when the plot description should only be given in a caption)")
stygroup.add_argument("-s", "--single", dest="SINGLE", action="store_true",
                      default=False, help="display plots on single webpage.")
stygroup.add_argument("--no-ratio", dest="SHOW_RATIO", action="store_false",
                     default=None, help="don't draw a ratio plot under each main plot.")
stygroup.add_argument("--no-errs", "--no-mcerrs", "--no-mc-errs", dest="MC_ERRS", action="store_false",
                      default=True, help="plot error bars.")
stygroup.add_argument("--canvastext", dest="CANVASTEXT", default=None,
                      help="Additional text to draw on the canvas")
stygroup.add_argument("--nRatioTicks", dest="NRATIOTICKS", default=1,
                      help="Modify number of minor ticks between major ticks on ratio plot.")
stygroup.add_argument("--offline", dest="OFFLINE", action="store_true",
                      default=False, help="generate HTML that does not use external URLs.")
stygroup.add_argument("-f", "--format", action="append", dest="FORMATS", default=["PDF", "PNG"],
                    help="output format string consisting of desired output formats separated by commas [default=PDF,PNG]")
# stygroup.add_argument("--booklet", dest="BOOKLET", action="store_true",
#                       default=False, help="create booklet (currently only available for PDF with pdftk or pdfmerge).")
stygroup.add_argument("--rmopts", "--remove-options", dest="REMOVE_OPTIONS", action="store_true", default=False,
                      help="remove options label from legend")

selgroup = parser.add_argument_group("Selective plotting")
selgroup.add_argument("-m", "--match", action="append", dest="PATHPATTERNS", default=[],
                      help="only write out histograms whose $path/$name string matches any of these regexes")
selgroup.add_argument("-M", "--unmatch", action="append", dest="PATHUNPATTERNS", default=[],
                      help="exclude histograms whose $path/$name string matches any of these regexes")
selgroup.add_argument("--ana-match", action="append", dest="ANAPATTERNS", default=[],
                      help="only write out histograms from analyses whose name matches any of these regexes (same as --match)")
selgroup.add_argument("--ana-unmatch", action="append", dest="ANAUNPATTERNS", default=[],
                      help="exclude histograms from analyses whose name matches any of these regexes (same as --unmatch)")
selgroup.add_argument("--with-variations", "--show-variations", help="also plot multiweight variations", dest="SHOW_WEIGHTS",
                    action="store_true", default=False)

vrbgroup = parser.add_argument_group("Verbosity control")
vrbgroup.add_argument("-v", "--verbose", help="add extra debug messages", dest="VERBOSE",
                      action="store_true", default=False)


def anasort(name):
    """\
    Sort analyses: group ascending by analysis name (could specialise grouping by collider), then
    descending by year, and finally descending by bibliographic archive ID code (INSPIRE first).
    """
    rtn = (1, name)
    if name.startswith("MC"):
        rtn = (99999999, name)
    else:
        stdparts = name.split("_")
        try:
            year = int(stdparts[1])
            rtn = (0, stdparts[0], -year, 0)
            idcode = (0 if stdparts[2][0] == "I" else 1e10) - int(stdparts[2][1:])
            rtn = (0, stdparts[0], -year, idcode)
            if len(stdparts) > 3:
                rtn += stdparts[3:]
        except:
            pass
    return rtn


from textwrap import indent, dedent
def redent(depth, code, pre="  "):
    "Re-indent with the in-source indent removed and replaced with the nesting-depth * tabbing"
    return indent(dedent(code), depth*pre)

def which(program):
    """\
    Find the path to an executable
    http://stackoverflow.com/questions/377017/test-if-executable-exists-in-python
    """
    import os
    def is_exe(fpath):
        return os.path.isfile(fpath) and os.access(fpath, os.X_OK)

    fpath, fname = os.path.split(program)
    if fpath:
        if is_exe(program):
            return program
    else:
        for path in os.environ["PATH"].split(os.pathsep):
            path = path.strip('"')
            exe_file = os.path.join(path, program)
            if is_exe(exe_file):
                return exe_file
    return None


def executeScript(script, shared_modules_list=[]):
    """
    Execute a single Python script, with argument string arg
    """

    if not os.path.isfile(script):
        raise FileNotFoundError(f"Python script {script} not found!")

    try:
        # load shared imports
        mpl = importlib.import_module(shared_modules_list[0])
        np  = importlib.import_module(shared_modules_list[1])
        sys_lib = importlib.import_module(shared_modules_list[2])
        os_lib  = importlib.import_module(shared_modules_list[3])
        plt = importlib.import_module(shared_modules_list[4])

        # pass imports to globals, including plot directory
        script_globals = {'mpl': mpl, 'np': np, 'sys' : sys_lib,
                          'os' : os_lib, 'plt' : plt, '__file__' : script}

        # execute script
        exec(open(script).read(), script_globals)

    except Exception as ex:
        print("Unexpected error when executing ", script)
        print(ex)


def generate_plots(pyScripts,nproc=1):

    # manage matplotlib and numpy processes centrally,
    # so that they are not imported for each individual script
    manager = multiprocessing.Manager()
    manager_module = manager.Namespace()
    manager_module.shared_modules = ["matplotlib", "numpy", "sys", "os", "matplotlib.pyplot"]

    # Call multiprocessing pool to generate plots
    p = multiprocessing.Pool(processes=nproc)

    try:
        # show progress bar
        import tqdm
        jobs = [p.apply_async(func=executeScript,
                                args=(pyScript, manager_module.shared_modules,))
                                for pyScript in pyScripts]
        p.close()
        p.join()

    except ImportError:
        for i, _ in enumerate(p.starmap(func=executeScript,
                    iterable=[(pyScript, manager_module.shared_modules)
                            for pyScript in pyScripts])):
            sys.stderr.write(f'\rdone {100*i/len(pyScripts):.1f}%')


def generateScripts(args):

    style = args.STYLE

    ## Add pwd to search paths
    if args.PATH_PWD:
        rivet.addAnalysisLibPath(os.path.abspath("."))
        rivet.addAnalysisDataPath(os.path.abspath("."))


    ## Check that there are some arguments!
    if not args.YODAFILES:
        print("Error: You need to specify some YODA files to be plotted!")
        sys.exit(1)


    ## Make output directory
    if os.path.exists(args.OUTPUTDIR) and not os.path.realpath(args.OUTPUTDIR)==os.getcwd():
        import shutil
        shutil.rmtree(args.OUTPUTDIR)
    try:
        os.makedirs(args.OUTPUTDIR)
    except:
        print("Error: failed to make new directory '%s'" % args.OUTPUTDIR)
        sys.exit(1)

    ## Check that YODA files are accessible
    yodafiles = [ ]
    for yodafile in args.YODAFILES:
        yodafilepath = os.path.abspath(yodafile.split(":")[0])
        if not yodafile.startswith("PLOT:") and not re.fullmatch(r"^REF(\d+)?:.*", yodafile):
            if not os.access(yodafilepath, os.R_OK):
                print("Error: cannot read from %s" % yodafilepath)
                if args.IGNORE_MISSING:
                    continue
                else:
                    sys.exit(2)
        yodafiles.append(yodafile)

    ## Make sure user-supplied config files are accessible
    configfiles = [ os.path.abspath(os.path.expanduser(cf)) for cf in args.CONFIGFILES if os.access(cf, os.R_OK) ]

    pat_match = list(set(args.PATHPATTERNS + args.ANAPATTERNS))
    pat_unmatch = list(set(args.PATHUNPATTERNS + args.ANAUNPATTERNS))
    plotContents, hasVars = assemble_plotting_data(yodafiles, args.PATH_PWD,
                                                   args.RIVETREFS, pat_match, pat_unmatch, [os.path.abspath("../")],
                                                   style, configfiles, True, args.OUTPUTDIR, args.MC_ERRS,
                                                   True, [], args.VERBOSE,
                                                   nRatioTicks=args.NRATIOTICKS, showWeights = args.SHOW_WEIGHTS,
                                                   removeOptions = args.REMOVE_OPTIONS, deviation=args.DEVIATION,
                                                   canvasText=args.CANVASTEXT, refLabel=args.REFLABEL,
                                                   ratioPlotLabel=args.RATIOPLOTLABEL, showRatio=args.SHOW_RATIO)

    # Unique list of analyses
    analyses = list(set([p.split("/")[1] for p in plotContents.keys()]))

    if hasVars and not args.SHOW_WEIGHTS:
        print ('Found multiweight variations in one of the input YODA files.')
        print ('You can plot them using --with-variations.')

    ## Write web page containing all (matched) plots
    ## Make web pages first so that we can load it locally in
    ## a browser to view the output before all plots are made
    analyses = sorted(analyses, key=anasort)
    if args.DRY_RUN:
        for analysis in analyses:
            anapath = os.path.join(args.OUTPUTDIR, analysis)
            if os.path.exists(anapath): continue
            os.makedirs(anapath)
    else:

        allPlotNames = plotContents.keys()

        style = redent(2, '''\
        <style>
          html { font-family: arial black, sans-serif; color: #333333; }
          img { border: 0; }
          a { text-decoration: none; font-weight: bold; }
          footer { clear:both; margin-top:2em; margin-bottom:2em; padding-top:2em; color: #999999; }

          .anasumm, .indexanasumm { margin-bottom: 2.5em; line-height: 1.4; font-size: 120%; font-family: georgia, palatino, serif; }
          .back, .back a { font-weight: bold; color: gray; font-variant: small-caps; text-transform: uppercase; margin-bottom: 2em; }

          .filterform { position: fixed; top: 1em; right: 1em; background: white; opacity: 0.2; }
          .filterform { padding: 1em; padding-bottom: 0.1em; border: 2px solid gray; border-radius: 0.7em; }
          .filterform:hover, .filterform:focus-within { opacity: 1.0; -webkit-transition: opacity 1s; transition: opacity 1s; drop-shadow(gray 0.5em 0.5em 10px); }
          .filterpatt { padding: 0.3em; font-size: 100%; border: 1px solid #85C1E9; border-radius: 0.1em; }
          .filterbutton { padding: 0.3em; font-size: 100%; border: 2px solid #85C1E9; background:#D6EAF8; border-radius: 0.3em; }

          .plot { float: left; font-weight: bold; }
          .plotlinks {  }
          .plotname { font-size: smaller; }

          .firstana h2 { break-before: avoid;  }
          .ana h2 { break-before: page; page-break-before: always; }
          .plot { page-break-inside: avoid; break-inside: avoid; }

          @media print {
            .noprint { display: none; }
            .back { display: none; }
            .filterform { display: none; }
            .plotlinks { display: none; }
            .plot { width: 40%; padding: 5mm; }
            .plots { text-align: center; }
            .plotthumb { max-width: 95%; padding: 0mm; margin: 0mm; }
          }
        </style>
        ''')

        ## Include MathJax configuration
        script = ''
        if not args.OFFLINE:
            script = redent(2, '''\
            <script type="text/javascript">
              window.MathJax = { tex: { inlineMath: [['$','$']] },
                                 options: { menuOptions: { settings: { inTabOrder: false } } } };
            </script>
            <script type="text/javascript" id="MathJax-script" async
                src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/3.2.2/es5/tex-chtml-full.min.js">
            </script>
            ''')
        ## Add local JavaScript
        script += "\n\n" + redent(2, '''\
            <script type="text/javascript">
              function filterPlots(ana, patt) {
                var regex = new RegExp(patt);
                var sec = document.getElementById('ana_'+ana);
                var plots = sec.getElementsByClassName(\'plot\');
                var i; for (i = 0; i < plots.length; ++i) {
                    // var viz = (regex.test(plots[i].id)) ? 'visible' : 'hidden';
                    // plots[i].style.visibility = viz;
                    var dis = (regex.test(plots[i].id)) ? 'block' : 'none';
                    plots[i].style.display = dis;
                }
                return false;
              }
            </script>
            ''')

        ## A helper function for metadata LaTeX -> HTML conversion
        from rivet.util import htmlify

        ## Timestamp and command HTML fragments to be used on each page:
        import datetime
        timestamp = '<p id="timestamp">Generated at %s</p>' % datetime.datetime.now().strftime("%A, %d. %B %Y %I:%M%p")
        command = '<p id="command">Created with command: <pre>%s</pre></p>' % COMMAND

        index = open(os.path.join(args.OUTPUTDIR, "index.html"), "w")
        index.write('<!DOCTYPE html>\n<html>\n  <head>\n')
        index.write('    <title>%s</title>\n    %s</head>\n\n<body>' % (args.TITLE, style+script))

        # if args.BOOKLET and "PDF" in args.FORMATS:
        #     index.write('<h1><a href="booklet.pdf">%s</a></h1>\n\n' % args.TITLE)
        # else:
        index.write('<h1>%s</h1>\n\n' % args.TITLE)

        if args.SINGLE and len(analyses) > 1:
            ## Write table of contents
            index.write('<ul>\n')
            for analysis in analyses:
                summary = analysis
                ana = rivet.AnalysisLoader.getAnalysis(analysis)
                if ana:
                    summary = "%s (%s)" % (ana.summary(), analysis)
                    if args.IGNORE_UNVALIDATED and ana.status() != "VALIDATED":
                        continue
                index.write('<li><a href="#%s">%s</a>\n' % (analysis, htmlify(summary)) )
            index.write('</ul>\n')

        official_routines = rivet.stdAnalysisNames()
        for iana, analysis in enumerate(analyses):
            references = []
            summary = htmlify(analysis)
            description, inspireid, spiresid = None, None, None

            if analysis.find("_I") > 0:
                inspireid = analysis[analysis.rfind('_I')+2:len(analysis)]
            elif analysis.find("_S") > 0:
                spiresid = analysis[analysis.rfind('_S')+2:len(analysis)]

            ana = rivet.AnalysisLoader.getAnalysis(analysis)
            if ana:
                if ana.summary():
                    summary = htmlify("%s (%s)" % (ana.summary(), analysis))
                references = ana.references()
                description = htmlify(ana.description())
                spiresid = ana.spiresId()

                if args.IGNORE_UNVALIDATED and ana.status().upper() != "VALIDATED":
                    continue

            try:
                if args.SINGLE:
                    anacls = "ana" if iana != 0 else "firstana" #< page-break in print, *between* analyses
                    index.write('<section class="{}" id="ana_{}">\n'.format(anacls, analysis))
                    index.write('\n<h2 style="clear:left; padding-top:2em;"><a name="%s">%s</a></h2>\n' % (analysis, summary))
                else:
                    index.write('\n<h2><a href="%s/index.html" style="text-decoration:none;">%s</a></h2>\n' % (analysis, summary))
            except UnicodeEncodeError as ue:
                print("Unicode error in analysis description for " + analysis + ": " + str(ue))

            reflist = []
            if inspireid:
                reflist.append('<a href="https://inspirehep.net/literature/%s">Inspire</a>' % inspireid)
                reflist.append('<a href="http://hepdata.net/record/ins%s">HepData</a>' % inspireid)
            elif spiresid:
            # elif ana.spiresId():
                reflist.append('<a href="https://inspirehep.net/literature?q=%s">Inspire</a>' % spiresid)
                reflist.append('<a href="http://hepdata.cedar.ac.uk/view/irn%s">HepData</a>' % spiresid)
            if analysis in official_routines:
                reflist.append('<a class="noprint" href="https://rivet.hepforge.org/analyses/%s.html">Analysis reference</a>' % analysis)
            for i, ref in enumerate(references):

                try:
                    if ref.startswith("arXiv:"):
                        arxivID = ref.split(' ')[0].split(':')[-1]
                        if arxivID:
                            references[i] = '<a href="https://arxiv.org/abs/%s">arXiv:%s</a>' % (arxivID, arxivID)
                    elif ref.startswith("doi:"):
                        doi = ref.split(' ')[0].split(':')[-1]
                        if doi:
                            references[i] = '<a href="https://dx.doi.org/%s">doi:%s</a>' % (doi, doi)
                    elif ref.startswith("ALICE-"):
                        code = ref.replace("ALICE-", "")
                        url = "https://alice-publications.web.cern.ch/node/" + code
                        references[i] = '<a href="%s">%s</a>' % (url, ref)
                    elif ref.startswith("ATLAS-"):
                        code = ref.replace("ATLAS-", "")
                        url = "https://atlas.web.cern.ch/Atlas/GROUPS/PHYSICS/PAPERS/" + code
                        references[i] = '<a href="%s">%s</a>' % (url, ref)
                    elif ref.startswith("CMS-"):
                        code = ref.replace("CMS-", "")
                        url = "https://cms-results.web.cern.ch/cms-results/public-results/publications/" + code
                        references[i] = '<a href="%s">%s</a>' % (url, ref)
                    elif ref.startswith("LHCb-", ""):
                        code = ref.replace("PAPER-", "")
                        url = "https://lhcbproject.web.cern.ch/Publications/LHCbProjectPublic/%s.html" % ref
                        references[i] = '<a href="%s">%s</a>' % (url, code)
                except:
                    pass
            reflist += references
            index.write('<p>%s</p>\n' % " &#124; ".join(reflist))

            if description:
                try:
                    index.write('<p class="indexanasumm">%s</p>\n' % description)
                except UnicodeEncodeError as ue:
                    print("Unicode error in analysis description for " + analysis + ": " + str(ue))

            anapath = os.path.join(args.OUTPUTDIR, analysis)
            if not os.path.exists(anapath):
                os.makedirs(anapath)
            if not args.SINGLE:
                anaindex = open(os.path.join(anapath, "index.html"), 'w')
                anaindex.write('<!DOCTYPE html>\n<html>\n<head>\n<title>%s &ndash; %s</title>\n%s</head>\n\n<body>\n' %
                               (htmlify(args.TITLE), analysis, style + script))
                anaindex.write('<section id="ana_{}">\n'.format(analysis))
                anaindex.write('<p class="back"><a href="../index.html" title="Return to index page">&larrhk; Index</a></p>\n')
                anaindex.write('<h2>%s</h2>\n' % htmlify(analysis))
                if description:
                    try:
                        anaindex.write('<p class="anasumm">\n  %s\n</p>\n' % description)
                    except UnicodeEncodeError as ue:
                        print("Unicode error in analysis description for " + analysis + ": " + str(ue))
            else:
                anaindex = index

            ## JS filtering
            if not args.SINGLE:
                anaindex.write('\n')
                anaindex.write('<div class="filterform">\n')
                anaindex.write('  <form onsubmit="var patt = document.getElementById(\'patt_{ana}\').value; filterPlots(\'{ana}\', patt); return false;">\n'.format(ana=analysis))
                anaindex.write('  <span><b>Filter plots:</b>&emsp;</span>\n')
                anaindex.write('  <input class="filterpatt" id="patt_{ana}" type="search" accesskey="s" title="Filter: Alt+s">\n'.format(ana=analysis))
                anaindex.write('  <input class="filterbutton" type="submit" value="Filter">\n')
                # anaindex.write('  <span style="background-color:00cc00; border:2px solid #009933; padding:2px; border-radius:2px;"\n onclick="var patt = document.getElementById(\'patt_{ana}\').value; filterPlots(\'{ana}\', patt);">Filter</span>\n'.format(ana=analysis))
                anaindex.write('  </form>\n')
                anaindex.write('</div>\n')
                anaindex.write('\n')

            anaindex.write('\n\n<div class="plots">\n\n')

            srcfiles = [ f.split('/')[-1] for f in allPlotNames if analysis in f ]
            # anaindex.write('<div style="float:none; overflow:auto; width:100%">\n')
            for srcfile in sorted(srcfiles):
                obsname = os.path.basename(srcfile).replace(".py", "")
                pngfile = obsname+".png"
                pdffile = obsname+".pdf"
                pyfile  = obsname+".py"
                datfile = pyfile.replace(".py", "__data.py")
                if args.SINGLE:
                    pngfile = os.path.join(analysis, pngfile)
                    pdffile = os.path.join(analysis, pdffile)
                    pyfile  = os.path.join(analysis, pyfile)
                    datfile = os.path.join(analysis, datfile)

                linkname = analysis + "-" + obsname
                anaindex.write('<div class="plot" id="plot_{o}">\n'.format(o=obsname))
                anaindex.write('  <div class="plothead">\n')
                anaindex.write('    <span class="plotlinks">\n')
                anaindex.write('      <a href="#%s">&#9875;</a>\n' % linkname)
                anaindex.write('      <a href="%s">&#8984;</a>\n' % pyfile)
                anaindex.write('      <a href="%s">&#128202;</a>\n' % datfile)
                anaindex.write('    </span>\n')
                anaindex.write('    <span class="plotname">%s:</span>\n' % os.path.splitext(pdffile)[0])
                anaindex.write('  </div>\n')
                anaindex.write('  <a id="%s" href="%s"><img class="plotthumb" src="%s"></a>\n' % (linkname, pdffile, pngfile))
                anaindex.write('</div>\n\n')
            # anaindex.write('</div>\n')

            anaindex.write('\n\n</div>\n\n') #< plots div

            if args.SINGLE:
                index.write('</section>\n\n')
            else:
                anaindex.write('</section>\n\n')
                anaindex.write('<footer>\n%s\n</footer>\n\n' % timestamp)
                anaindex.write('</body>\n</html>')
                anaindex.close()

        index.write('<footer>\n%s\n%s\n</footer>\n\n' % (timestamp, command))
        index.write('</body>\n</html>')
        index.close()


    ## *Now* make the plots, etc.
    num_plots = len(plotContents)
    pyScripts = []
    print("Making {} plots".format(num_plots))
    for i, (plotName, singlePlotContent) in enumerate(plotContents.items()):
        print("Generating", args.OUTPUTDIR+plotName+".py") # "({}/{} remaining)".format(num_plots-i, num_plots))

        ## writePyScript returns path of the .py script
        try:
            pyScript = script_generator.process(singlePlotContent, plotName, args.OUTPUTDIR, args.FORMATS)
            pyScripts += [os.path.abspath(pyScript)]
        except Exception as e:
            if args.VERBOSE:
                print(e)
            print('Unexpected error encountered for', plotName)
    return pyScripts


if __name__ == '__main__':

    ## Parse arguments
    args = parser.parse_args()

    ## Generate executable python scripts
    pyScripts = generateScripts(args)

    ## Execute generated Python scripts with a multiprocessing Pool
    if not args.DRY_RUN:
        sys.stdout.write('Plotting...')
        generate_plots(pyScripts, nproc=args.JOBS)

        sys.stdout.write('\r')
        sys.stdout.write('Plotting... done!\n')

        # if args.BOOKLET and 'pdf' in args.FORMATS:
        #     if which("pdftk") is not None:
        #         bookletcmd = ["pdftk"]
        #         for analysis in analyses:
        #             anapath = os.path.join(args.OUTPUTDIR, analysis)
        #             bookletcmd += sorted(glob.glob("%s/*.pdf" % anapath))
        #         bookletcmd += ["cat", "output", "%s/booklet.pdf" % args.OUTPUTDIR]
        #         if args.VERBOSE:
        #             print(bookletcmd)
        #         subprocess.Popen(bookletcmd).wait()
        #     elif which("pdfmerge") is not None:
        #         bookletcmd = ["pdfmerge"]
        #         for analysis in analyses:
        #             anapath = os.path.join(args.OUTPUTDIR, analysis)
        #             bookletcmd += sorted(glob.glob("%s/*.pdf" % anapath))
        #         bookletcmd += ["%s/booklet.pdf" % args.OUTPUTDIR]
        #         if args.VERBOSE:
        #             print(bookletcmd)
        #         subprocess.Popen(bookletcmd).wait()
        #     else:
        #         print("Neither pdftk nor pdfmerge available --- no booklet output possible")
