#!/home/conda/feedstock_root/build_artifacts/bld/rattler-build_pycbc_1768936173/host_env_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehol/bin/python
#
# Copyright (C) 2019 Francesco Pannarale
#
# This program is free software; you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by the
# Free Software Foundation; either version 3 of the License, or (at your
# option) any later version.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General
# Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.


"""
Workflow generator to run PyGRB offline post-processing.
"""

# =============================================================================
# Preamble
# =============================================================================
import sys
import socket
import logging
import argparse
import os

import pycbc
import pycbc.version
import pycbc.workflow as _workflow
from pycbc.results import layout
from pycbc.results.pygrb_postprocessing_utils import extract_ifos
from pycbc.results import save_fig_with_metadata

__author__ = "Francesco Pannarale  <francesco.pannarale@ligo.org>"
__version__ = pycbc.version.git_verbose_msg
__date__ = pycbc.version.date
__program__ = "pycbc_pygrb_results_workflow"


# Function that converts a list of file paths to a FileList in which each File
# is provided with a label
def labels_in_files_metadata(labels, rundir, file_paths):
    """Return a FileList based on the list of file paths.  Each File in it
    is provided with its tag from labels: the correctess of the tag is
    based on the file name.
    """
    if len(labels) != len(file_paths):
        logging.error("The two arguments must have the same length.")

    # Sort the file paths according to labels
    up_labels = [l.upper() for l in labels]
    sorted_file_paths = [
        list(filter(lambda x: l in x.upper(), file_paths))[0]
        for l in up_labels
    ]

    # Convert sorted_file_paths to a FileList where each File has its label
    # in the tags
    labels_paths = zip(up_labels, sorted_file_paths)
    out = _workflow.FileList(
        [
            _workflow.resolve_url_to_file(
                os.path.join(rundir, p), attrs={'tags': [l]}
            )
            for (l, p) in labels_paths
        ]
    )

    return out


# Function to retrieve the segments plot (produced in the preprocessing stage)
# that ensures its copy is saved in the output directory before returning to
# the original working directory. Appropriate meta data is added to the plot.
def display_seg_plot(output_dir, segment_dir):
    """Return the File of the segments plot (which was already produced during
    the pre-processing) after adding the appropriate metadata to it.
    """
    from PIL import Image, PngImagePlugin

    curr_dir = os.getcwd()
    os.chdir(output_dir)
    segments_plot_name = (
        'GRB' + wflow.cp.get('workflow', 'trigger-name') + '_segments.png'
    )
    segments_plot = _workflow.resolve_url_to_file(
        os.path.join(segment_dir, segments_plot_name)
    )
    segments_plot_path = segments_plot.storage_path
    im = Image.open(segments_plot_path)
    meta = PngImagePlugin.PngInfo()
    meta.add_text('title', str('Segments and analysis time'))
    meta.add_text(
        'caption',
        str(
            'Segments (horizontal bands) available around the trigger time (orange vertical line) and analysis time used (orange box).'
        ),
    )
    meta.add_text(
        'cmd',
        str(
            'This plot is generated by the make_grb_segments_plot function during the pre-processing stage of the analysis.'
        ),
    )
    im.save(segments_plot_path, "png", pnginfo=meta)
    os.chdir(curr_dir)

    return segments_plot


# =============================================================================
# Main script
# =============================================================================
# Use the standard workflow command-line parsing routines.
parser = argparse.ArgumentParser(description=__doc__[1:])
pycbc.add_common_pycbc_options(parser)
parser.add_argument(
    "-t",
    "--trig-files",
    action="store",
    required=True,
    nargs="+",
    help="The locations of the trigger files "
    "with assumed order: [ALL_TIMES, ONSOURCE,"
    " OFFSOURCE, OFFTRIAL_1, ..., OFFTRIAL_N]",
)
parser.add_argument(
    "--sky-grid",
    required=True,
    action="store",
    help="The location of the sky grid file"
)
parser.add_argument(
    "-i",
    "--inj-files",
    action="store",
    nargs="+",
    help="Location(s) of input injection results file(s)",
)
parser.add_argument(
    "-b",
    "--bank-file",
    action="store",
    help="The location of the full template bank file",
)
parser.add_argument(
    "--segment-dir",
    action="store",
    help="The location of the segment files",
)
parser.add_argument(
    "--veto-file",
    help="File containing segments used to veto injections",
)

_workflow.add_workflow_command_line_group(parser)
_workflow.add_workflow_settings_cli(parser, include_subdax_opts=True)
args = parser.parse_args()

pycbc.init_logging(
    args.verbose, format="%(asctime)s: %(levelname)s: %(message)s"
)

# Store starting run directory
start_rundir = os.getcwd()

# Create the workflow object
logging.info("Generating %s workflow", args.workflow_name)
wflow = _workflow.Workflow(args, name=args.workflow_name)

logging.info("Post-processing output will be generated in %s", args.output_dir)
if not os.path.exists(args.output_dir):
    _workflow.makedir(args.output_dir)
os.chdir(args.output_dir)

# Setup results directory
rdir = layout.SectionNumber(
    'webpage',
    [
        'offsource_triggers_vs_time',
        'signal_consistency',
        'injections',
        'loudest_offsource_events',
        'exclusion_distances',
        'open_box',
        'workflow',
    ],
)
_workflow.makedir(rdir.base)
_workflow.makedir(rdir['workflow'])

# File instances of all input trigger files
# Expected structure of args.trig_files:
# [ALL_TIMES, ONSOURCE, OFFSOURCE, OFFTRIAL_1, ..., OFFTRIAL_N]
trig_files = [
    _workflow.resolve_url_to_file(os.path.join(start_rundir, trig_file))
    for trig_file in args.trig_files
]
[all_times_file, onsource_file, offsource_file] = trig_files[0:3]
offtrial_files = trig_files[3:]
logging.info("Using the following trigger files:")
logging.info("All times: %s", all_times_file.storage_path)
logging.info("Onsource: %s", onsource_file.storage_path)
logging.info("Offsource: %s", offsource_file.storage_path)
logging.info("Offtrials: %s", [f.storage_path for f in offtrial_files])

sky_grid_file = os.path.join(start_rundir, args.sky_grid)
sky_grid_file = _workflow.resolve_url_to_file(sky_grid_file)
logging.info("Using the following sky grid : %s", sky_grid_file)
# File instance of the template bank file
bank_file = os.path.join(start_rundir, args.bank_file)
bank_file = _workflow.resolve_url_to_file(bank_file)

# Sanity check and File instances of the input injection files
inj_sets = wflow.cp.get_subsections('injections')
eff_secs = wflow.cp.get_subsections('pygrb_efficiency')
if not set(inj_sets).issubset(eff_secs):
    err_msg = "Each [injections] subsection requires at "
    err_msg += "least one dedicated [pygrb_efficiency] subsection. "
    err_msg += "[injections] subsections found: %s. "
    err_msg += "[pygrb_efficiency] subsections found: %s. "
    logging.error(err_msg, inj_sets, eff_secs)
inj_sets = [i.upper() for i in inj_sets]
inj_files = labels_in_files_metadata(inj_sets, start_rundir, args.inj_files)

# File instance of the veto file
veto_file = args.veto_file
if veto_file: 
    veto_file = os.path.join(start_rundir, args.veto_file)
    veto_file = _workflow.resolve_url_to_file(veto_file)

# IFOs actually used: determined by data availability
ifos = extract_ifos(offsource_file.storage_path)
wflow.ifos = ifos

plotting_nodes = []
html_nodes = []
mfu_nodes = []

# Convert the segments files to a FileList
seg_files = _workflow.build_segment_filelist(args.segment_dir)

# Logfile of this workflow
wf_log_file = _workflow.File(
    wflow.ifos,
    'workflow-log',
    wflow.analysis_time,
    extension='.txt',
    directory=rdir['workflow'],
)
logfile = logging.FileHandler(filename=wf_log_file.storage_path, mode='w')
logfile.setLevel(logging.INFO)
formatter = logging.Formatter('%(asctime)s: %(levelname)s: %(message)s')
logfile.setFormatter(formatter)
logging.getLogger('').addHandler(logfile)
logging.info("Created log file %s", wf_log_file.storage_path)

# TODO: Pick up inifile, segments plot, GRB time and location, and
#       report IFO responses
# Read the configuration file
# typecast str from command line to File instances
# cp = configuration.WorkflowConfigParser(opts.pp_config_file)

out_dir = rdir.base
_workflow.makedir(out_dir)

#
# Opening page
#
# Create the information table about the GRB trigger
info_table_node, grb_info_table = _workflow.make_pygrb_info_table(
    wflow, 'pygrb_grb_info_table', out_dir
)
html_nodes.append(info_table_node)
# Plot the search grid
plot_node, skygrid_plot = _workflow.make_pygrb_plot(
    wflow, 'pygrb_plot_skygrid', out_dir,
    sky_grid_file=sky_grid_file
)
plotting_nodes.append(plot_node)
# Retrieve the segments plot (produced in the preprocessing stage)
seg_plot = display_seg_plot(out_dir, args.segment_dir)
summary_layout = [(grb_info_table[0],), (seg_plot, skygrid_plot[0])]
layout.two_column_layout(out_dir, summary_layout)

#
# Plot SNR timeseries
#
out_dir = rdir['offsource_triggers_vs_time']
_workflow.makedir(out_dir)

# Retrieve the name of the preferred injection set for these plots
tuning_inj_set = wflow.cp.get('workflow-pygrb_results_workflow', 'tuning-inj-set')
tuning_inj_set = tuning_inj_set.upper()
logging.info("The tuning injections set is %s", tuning_inj_set)

# Retrieve the injections result File corresponding to the tuning set
tuning_inj_file = inj_files.find_output_with_tag(
    tuning_inj_set, fail_if_not_single_file=True
)
logging.info(
    "The tuning injections results file is %s", tuning_inj_file.storage_path
)

# Loop over timeseries request by the user
timeseries = wflow.cp.get_subsections('pygrb_plot_snr_timeseries')
out_dirs_dict = {
    'coherent': 'offsource_triggers_vs_time/coh_snr_timeseries',
    'reweighted': 'offsource_triggers_vs_time/reweighted_snr_timeseries',
    'single': 'offsource_triggers_vs_time/single_ifo_snr_timeseries',
    'null': 'offsource_triggers_vs_time/null_snr_timeseries',
}
for snr_type in timeseries:
    out_dir = rdir[out_dirs_dict[snr_type]]
    _workflow.makedir(out_dir)
    files = _workflow.FileList([])
    # Only single SNR timeseries requires looping over IFOs
    ifos_to_loop = ifos if snr_type == 'single' else [None]
    for ifo in ifos_to_loop:
        timeseries_plots = _workflow.FileList([])
        # Plots without and with injections
        for inj_file in set([None, tuning_inj_file]):
            # If the tuning injection file is used for the plot, include its
            # tags in assembling the plot name
            inj_tags = inj_file.tags if inj_file else []
            tags = [snr_type] + inj_tags
            plot_node, output_files = _workflow.make_pygrb_plot(
                wflow,
                'pygrb_plot_snr_timeseries',
                out_dir,
                trig_file=offsource_file,
                inj_file=inj_file,
                ifo=ifo,
                seg_files=seg_files,
                veto_file=veto_file,
                tags=tags,
            )
            plotting_nodes.append(plot_node)
            # We want a File, not a 1-element list with a File
            # pycbc_pygrb_plot_snr_timeseries produces only one plot: take [0]
            timeseries_plots.append(output_files[0])
        files.append(timeseries_plots)
    layout.two_column_layout(out_dir, files)


#
# Signal consistency plots
#
out_dir = rdir['signal_consistency']
_workflow.makedir(out_dir)
# Chisq veto vs SNR plots
out_dir = rdir['signal_consistency/chi_squared_tests']
_workflow.makedir(out_dir)
files = _workflow.FileList([])
# Loop over vetoes request by the user
vetoes = wflow.cp.get_subsections('pygrb_plot_chisq_veto')
for veto in vetoes:
    # Loop over IFOs only in the case of single detector chi-squares
    ifo_loop = [ifos[0]] if veto == 'network' else ifos
    for ifo in ifo_loop:
        # Plot with and without injections
        for inj_file in set([tuning_inj_file, None]):
            # If the tuning injection file is used for the plot, include its
            # tags in assembling the plot name
            inj_tags = inj_file.tags if inj_file else []
            tags = [veto] + inj_tags
            ifo_arg = None if veto == 'network' else ifo
            plot_node, output_files = _workflow.make_pygrb_plot(
                wflow,
                'pygrb_plot_chisq_veto',
                out_dir,
                trig_file=offsource_file,
                inj_file=inj_file,
                ifo=ifo_arg,
                seg_files=seg_files,
                veto_file=veto_file,
                tags=tags,
            )
            plotting_nodes.append(plot_node)
            # We want a File, not a 1-element list with a File
            # pycbc_pygrb_plot_chisq_veto produces only one plot: take [0]
            files.append(output_files[0])
chisq_layout = list(layout.grouper(files, 2))
layout.two_column_layout(out_dir, chisq_layout)

# Single detector chi-square plots: zoomed in and zoomed out
out_dir = rdir['signal_consistency/individual_detector_snrs']
_workflow.makedir(out_dir)
files = _workflow.FileList([])
# Single IFO SNR vs Coherent SNR plots: zoomed in and zoomed out
# Requires looping over IFOs
if wflow.cp.has_section('pygrb_plot_coh_ifosnr'):
    for ifo in ifos:
        # Plots with and without injections
        for inj_file in set([tuning_inj_file, None]):
            sngl_snr_plots = _workflow.FileList([])
            for zoom_tag in [['zoomin'], []]:
                # If the tuning injection file is used for the plot, include
                # its tags in assembling the plot name
                inj_tags = inj_file.tags if inj_file else []
                tags = zoom_tag + inj_tags
                # Single IFO SNR vs Coherent SNR
                plot_node, output_files = _workflow.make_pygrb_plot(
                    wflow,
                    'pygrb_plot_coh_ifosnr',
                    out_dir,
                    trig_file=offsource_file,
                    inj_file=inj_file,
                    ifo=ifo,
                    seg_files=seg_files,
                    veto_file=veto_file,
                    tags=tags,
                )
                plotting_nodes.append(plot_node)
                # We want a File, not a 1-element list with a File
                # pycbc_pygrb_plot_cohifo_snr produces only one plot: take [0]
                sngl_snr_plots.append(output_files[0])
            files.append(sngl_snr_plots)
    layout.two_column_layout(out_dir, files)
else:
    msg = 'No pygrb_plot_coh_ifosnr section found in the configuration file. '
    msg += 'No coherent vs single detector SNR plots will be generated.'
    logging.info(msg)

# Null SNR/Overwhitened null stat vs Coherent SNR plots
null_snr_out_dir = rdir['signal_consistency/null_snrs']
_workflow.makedir(null_snr_out_dir)
null_snr_files = _workflow.FileList([])
# Coincident SNR vs Coherent SNR plots
coinc_out_dir = rdir['signal_consistency/coincident_snr']
_workflow.makedir(coinc_out_dir)
coinc_files = _workflow.FileList([])
# Loop over null statistics requested by the user (including coincident SNR)
nstats = wflow.cp.get_subsections('pygrb_plot_null_stats')
for nstat in nstats:
    # Plots with and without injections, zoomed in and zoomed out
    for inj_file in set([tuning_inj_file, None]):
        if nstat == 'coincident':
            out_dir = coinc_out_dir
            files = coinc_files
        else:
            out_dir = null_snr_out_dir
            files = null_snr_files
        null_stats_plots = _workflow.FileList([])
        for zoom_tag in [['zoomin'], []]:
            # If the tuning injection file is used for the plot, include its
            # tags in assembling the plot name
            inj_tags = inj_file.tags if inj_file else []
            tags = [nstat] + zoom_tag + inj_tags
            plot_node, output_files = _workflow.make_pygrb_plot(
                wflow,
                'pygrb_plot_null_stats',
                out_dir,
                trig_file=offsource_file,
                inj_file=inj_file,
                seg_files=seg_files,
                veto_file=veto_file,
                tags=tags,
            )
            plotting_nodes.append(plot_node)
            # We want a File, not a 1-element list with a File
            # pycbc_pygrb_plot_null_stats produces only one plot: take [0]
            null_stats_plots.append(output_files[0])
        files.append(null_stats_plots)
layout.two_column_layout(null_snr_out_dir, null_snr_files)
layout.two_column_layout(coinc_out_dir, coinc_files)

# layout.group_layout(rdir['coincident_triggers'],
#                     closed_box_ifars + all_snrifar + [bank_plot[0][0]])

#
# Found/missed injections plots and tables
#
out_dir = rdir['injections']
_workflow.makedir(out_dir)
# Loop over injection plots requested by the user
inj_plots = wflow.cp.get_subsections('pygrb_plot_injs_results')
# The command above also picks up the injection set names so we remove them
# from the set of requested injection plot types
inj_plots = [inj_plot for inj_plot in inj_plots if inj_plot not in inj_sets]
for inj_set in inj_sets:
    # Retrieve the injections result File corresponding to the inj_set label
    inj_file = inj_files.find_output_with_tag(
        inj_set, fail_if_not_single_file=True
    )
    out_dir = rdir['injections/' + inj_set]
    _workflow.makedir(out_dir)
    files = _workflow.FileList([])
    # Generate plots: loop over inj_plots and found-missed/missed-found
    for inj_plot in inj_plots:
        y_qty, x_qty = inj_plot.split('_')
        ifos_to_loop = [None]
        if y_qty == 'effsitedist':
            ifos_to_loop = ifos
        for ifo in ifos_to_loop:
            for fm_or_mf in ['missed-on-top', 'found-on-top']:
                tags = [y_qty, x_qty, fm_or_mf, inj_set]
                plot_node, output_files = _workflow.make_pygrb_plot(
                    wflow,
                    'pygrb_plot_injs_results',
                    out_dir,
                    trig_file=offsource_file,
                    ifo=ifo,
                    inj_file=inj_file,
                    seg_files=seg_files,
                    veto_file=veto_file,
                    tags=tags,
                )
                plotting_nodes.append(plot_node)
                # A File is needed, not a 1-element list with a File
                # pycbc_pygrb_plot_injs_results produces only one plot, so
                # take [0]
                files.append(output_files[0])
    # Generate quiet-found and missed-found html tables
    html_node, [mf_table, qf_table, qf_h5] = _workflow.make_pygrb_injs_tables(
        wflow,
        out_dir,
        bank_file,
        offsource_file,
        seg_files,
        inj_file=inj_file,
        veto_file=veto_file,
        tags=inj_file.tags,
    )
    html_nodes.append(html_node)

    inj_layout = list(layout.grouper(files, 2)) + [(mf_table,), (qf_table,)]
    layout.two_column_layout(out_dir, inj_layout)

    # Follow up of loudest N quiet/missed-found injections:
    # qf_h5 includes quiet and missed found injections
    out_dir = rdir['injections/' + inj_set + 'loudest_quiet_found_followups']
    if not wflow.cp.has_section('workflow-minifollowups'):
        msg = 'No [workflow-minifollowups] section found in '
        msg += 'configuration file'
        logging.info(msg)
    else:
        logging.info('Entering minifollowups module for injections')
        mfu_node = _workflow.setup_pygrb_minifollowups(
            wflow,
            qf_h5,
            offsource_file,
            'daxes',
            out_dir,
            seg_files=seg_files,
            veto_file=veto_file,
            tags=inj_file.tags + ['loudest_quiet_found_injs'],
        )
        mfu_nodes.append(mfu_node)
        logging.info('Leaving minifollowups')

#
# FAP distributions
#
out_dir = rdir['loudest_offsource_events']
_workflow.makedir(out_dir)
files = []
# Loop over statistics requested by the user
stats = wflow.cp.get_subsections('pygrb_plot_stats_distribution')
for stat in stats:
    plot_node, output_file = _workflow.make_pygrb_plot(
        wflow,
        'pygrb_plot_stats_distribution',
        out_dir,
        trig_file=offsource_file,
        inj_file=inj_file,
        seg_files=seg_files,
        veto_file=veto_file,
        tags=[stat],
    )
    plotting_nodes.append(plot_node)
    # We want a File, not a 1-element list with a File
    # pycbc_pygrb_plot_stats_distribution produces only one plot: take [0]
    files.append(output_file[0])

# Generate loudest off-source triggers table
html_node, [lofft_table, lofft_h5] = _workflow.make_pygrb_injs_tables(
    wflow, out_dir, bank_file, offsource_file, seg_files
)
html_nodes.append(html_node)

lofft_layout = list(layout.grouper(files, 2)) + [(lofft_table,)]
layout.two_column_layout(out_dir, lofft_layout)

# Follow up N loudest offsource triggers (parent-child)
out_dir = rdir['loudest_offsource_events/followups']
if not wflow.cp.has_section('workflow-minifollowups'):
    msg = 'No [workflow-minifollowups] section found in '
    msg += 'configuration file'
    logging.info(msg)
else:
    logging.info('Entering minifollowups module for offsource')
    mfu_node = _workflow.setup_pygrb_minifollowups(
        wflow,
        lofft_h5,
        offsource_file,
        'daxes',
        out_dir,
        seg_files=seg_files,
        veto_file=veto_file,
        tags=['loudest_offsource_events'],
    )
    mfu_nodes.append(mfu_node)
    logging.info('Leaving minifollowups')


#
# Exclusion distance and efficiency plots based on offtrials
#
out_dir = rdir['exclusion_distances']
_workflow.makedir(out_dir)

# Offtrials and injection sets requested by the user
num_trials = int(wflow.cp.get('trig_combiner', 'num-trials'))
offtrials = [f"offtrial_{i+1}" for i in range(num_trials)]

# Sensitivity plots of each injection set
out_dir = rdir['exclusion_distances']
_workflow.makedir(out_dir)
bkgd_plots = []
for inj_set in inj_sets:
    # Retrieve the injections result File for the inj_set label
    inj_file = inj_files.find_output_with_tag(
        inj_set, fail_if_not_single_file=True
    )
    tags = [offtrials[0]] + inj_file.tags
    plot_node, output_files = _workflow.make_pygrb_plot(
        wflow,
        'pygrb_efficiency',
        out_dir,
        trig_file=offsource_file,
        inj_file=inj_file,
        bank_file=bank_file,
        seg_files=seg_files,
        veto_file=veto_file,
        tags=tags,
        plot_bkgd=True,
    )
    plotting_nodes.append(plot_node)
    bkgd_plots += [output_files[0]]
bkgd_plots = list(layout.grouper(bkgd_plots, 2))
layout.two_column_layout(out_dir, bkgd_plots)

# Exclusion distances of each injection set
for i, offtrial in enumerate(offtrials):
    out_dir = rdir[f'exclusion_distances/{offtrial}']
    _workflow.makedir(out_dir)
    eff_layout = []
    for inj_set in inj_sets:
        # Retrieve the injections result File for the inj_set label
        inj_file = inj_files.find_output_with_tag(
            inj_set, fail_if_not_single_file=True
        )
        tags = [offtrial] + inj_file.tags
        plot_node, output_files = _workflow.make_pygrb_plot(
            wflow,
            'pygrb_efficiency',
            out_dir,
            trig_file=offsource_file,
            onsource_file=offtrial_files[i],
            inj_file=inj_file,
            bank_file=bank_file,
            seg_files=seg_files,
            veto_file=veto_file,
            tags=tags,
            plot_bkgd=False,
        )
        plotting_nodes.append(plot_node)
        eff_plot = output_files[0]
        json_input = output_files[1]
        # Create information table about exclusion distances
        excl_dist_table_node, excl_dist_table = (
            _workflow.make_pygrb_info_table(
                wflow,
                'pygrb_exclusion_dist_table',
                out_dir,
                in_files=json_input,
                tags=tags,
            )
        )
        html_nodes.append(excl_dist_table_node)
        eff_layout += [(eff_plot, excl_dist_table[0])]
    layout.two_column_layout(out_dir, eff_layout, offtrial)

# Make room for throughput histograms (TODO)
base = rdir['workflow/throughput']
_workflow.makedir(base)

# Save global config file
base = rdir['workflow/configuration']
_workflow.makedir(base)
ini_file_path = os.path.join(base, args.workflow_name + '.ini')
with open(ini_file_path, 'w') as ini_fh:
    wflow.cp.write(ini_fh)
ini_file = _workflow.FileList(
    [
        _workflow.File(
            wflow.ifos,
            '',
            wflow.analysis_time,
            file_url='file://' + ini_file_path,
        )
    ]
)
layout.single_layout(base, ini_file)

# Create versioning information
_workflow.make_versioning_page(
    wflow,
    wflow.cp,
    rdir['workflow/version'],
)

# Create the final log file
log_file_html = _workflow.File(
    wflow.ifos,
    'WORKFLOW-LOG',
    wflow.analysis_time,
    extension='.html',
    directory=rdir['workflow'],
)

# Create a page to contain a dashboard link
dashboard_file = _workflow.File(
    wflow.ifos,
    'DASHBOARD',
    wflow.analysis_time,
    extension='.html',
    directory=rdir['workflow'],
)
dashboard_str = """<center><p style="font-size:20px"><b><a href="PEGASUS_DASHBOARD_URL" target="_blank">Pegasus Dashboard Page</a></b></p></center>"""
kwds = {
    'title': 'Pegasus Dashboard',
    'caption': "Link to Pegasus Dashboard",
    'cmd': "PYCBC_SUBMIT_DAX_ARGV",
}
save_fig_with_metadata(dashboard_str, dashboard_file.storage_path, **kwds)

# Create pages for the submission script to write data
_workflow.makedir(rdir['workflow/dax'])
_workflow.makedir(rdir['workflow/input_map'])
_workflow.makedir(rdir['workflow/output_map'])
_workflow.makedir(rdir['workflow/planning'])

# Protect the open box results folder
out_dir = rdir['open_box']
_workflow.makedir(out_dir)
os.chmod(out_dir, 0o0700)

# Generate loudest on-source trigger table
_workflow.makedir(out_dir)
html_node, [lont_table, lont_h5] = _workflow.make_pygrb_injs_tables(
    wflow, out_dir, bank_file, offsource_file, seg_files, on_file=onsource_file
)
html_nodes.append(html_node)

# Reweighted and coherent SNR timeseries for the full data stretch
timeseries_plots = _workflow.FileList([])
for snr_type in ['reweighted', 'coherent']:
    plot_node, output_files = _workflow.make_pygrb_plot(
        wflow,
        'pygrb_plot_snr_timeseries',
        out_dir,
        trig_file=all_times_file,
        seg_files=seg_files,
        veto_file=veto_file,
        tags=[snr_type, 'alltimes'],
    )
    plotting_nodes.append(plot_node)
    timeseries_plots.extend(output_files)
openbox_layout = [(lont_table,)] + list(layout.grouper(timeseries_plots, 2))
layout.two_column_layout(out_dir, openbox_layout)

# Loudest on-source trigger follow up
out_dir = rdir['open_box/loudest_event_followup']
if not wflow.cp.has_section('workflow-minifollowups'):
    msg = 'No [workflow-minifollowups] section found in '
    msg += 'configuration file'
    logging.info(msg)
else:
    logging.info("Entering minifollowups module for loudest onsource")
    mfu_node = _workflow.setup_pygrb_minifollowups(
        wflow,
        lont_h5,
        onsource_file,
        'daxes',
        out_dir,
        seg_files=seg_files,
        veto_file=veto_file,
        tags=['loudest_onsource_event'],
    )
    mfu_nodes.append(mfu_node)
    logging.info("Leaving onsource minifollowups")

# Exclusion distances and efficiency plots based on the on-source
out_dir = rdir['open_box/exclusion_distances']
eff_layout = []
for inj_set in inj_sets:
    # Retrieve the injections result File corresponding to the inj_set label
    inj_file = inj_files.find_output_with_tag(
        inj_set, fail_if_not_single_file=True
    )
    tags = ['onsource'] + inj_file.tags
    plot_node, output_files = _workflow.make_pygrb_plot(
        wflow,
        'pygrb_efficiency',
        out_dir,
        trig_file=offsource_file,
        onsource_file=onsource_file,
        inj_file=inj_file,
        bank_file=bank_file,
        seg_files=seg_files,
        veto_file=veto_file,
        tags=tags,
        plot_bkgd=False,
    )
    plotting_nodes.append(plot_node)
    eff_plot = output_files[0]
    json_input = output_files[1]
    # Create information table about exclusion distances
    excl_dist_table_node, excl_dist_table = _workflow.make_pygrb_info_table(
        wflow,
        'pygrb_exclusion_dist_table',
        out_dir,
        in_files=json_input,
        tags=tags,
    )
    html_nodes.append(excl_dist_table_node)
    eff_layout += [(eff_plot, excl_dist_table[0])]
layout.two_column_layout(out_dir, eff_layout)

# Job to gather all html material in the webpage
logging.info(
    "Path for make_results_web_page: %s", os.path.join(os.getcwd(), rdir.base)
)
_workflow.make_results_web_page(
    wflow,
    os.path.join(os.getcwd(), rdir.base),
    template='red',
    explicit_dependencies=plotting_nodes + html_nodes + mfu_nodes,
)

# Close the log and flush to the html file
logging.shutdown()
with open(wf_log_file.storage_path, "r") as logfile:
    logdata = logfile.read()
log_str = """
<p>Workflow generation script created workflow in output directory: %s</p>
<p>Workflow name is: %s</p>
<p>Workflow generation script run on host: %s</p>
<pre>%s</pre>
""" % (
    os.getcwd(),
    args.workflow_name,
    socket.gethostname(),
    logdata,
)
kwds = {
    'title': 'Workflow Generation Log',
    'caption': f"Log of the workflow script {sys.argv[0]}",
    'cmd': ' '.join(sys.argv),
}
save_fig_with_metadata(log_str, log_file_html.storage_path, **kwds)
layout.single_layout(rdir['workflow'], ([dashboard_file, log_file_html]))

# Go back to the run directory and save the workflow
os.chdir(start_rundir)
wflow.save()
logging.info("Dax written.")
