#!/usr/bin/env python

from __future__ import division
import logging
import os
import sys
import re
import pandas as pd
import zika_methods
import zika_utils
from argparse import ArgumentParser
from genologics.lims import Lims
from genologics.config import BASEURI, USERNAME, PASSWORD
from scilifelab_epps.epp import attach_file
from genologics.entities import Process
from numpy import minimum
from datetime import datetime as dt


DESC = """EPP used to create csv files for the bravo robot"""

MAX_WARNING_VOLUME = 150.0
MIN_WARNING_VOLUME = 2.0

# Three values are minimum required conc for setup workset, maximum conc for dilution and minimum volume for dilution
Dilution_preset = {
    "Smarter pico": [1.25, 375.0, 10.0]
}

# Pre-compile regexes in global scope:
IDX_PAT = re.compile("([ATCG]{4,})-?([ATCG]*)")
TENX_PAT = re.compile("SI-GA-[A-H][1-9][0-2]?")

def obtain_previous_volumes(currentStep, lims):
    samples_volumes = {}
    re_well = re.compile("([A-H]):?0?([0-9]{1,2})")
    previous_steps = set()
    for input_artifact in currentStep.all_inputs():
        previous_steps.add(input_artifact.parent_process)
    for pp in previous_steps:
        for output in pp.all_outputs():
            if output.name == "EPP Generated Bravo CSV File for Normalization":
                try:
                    fid = output.files[0].id
                except:
                    raise RuntimeError("Cannot access the normalisation CSV file to read the volumes.")
                else:
                    file_contents = lims.get_file_contents(id=fid)
                    if isinstance(file_contents, bytes):
                        file_contents = file_contents.decode('utf-8')
                    genologics_format = False
                    well_idx = 4
                    plate_idx = 3
                    source_vol_idx = 2
                    buffer_vol_idx = 5
                    for line in file_contents.split('\n'):
                        # Skip some lines:
                        if not line.rstrip():
                            continue
                        elif "Date of file generation:" in line:
                            continue
                        elif "Generated by:" in line:
                            continue
                        elif "Sample Name" in line:
                            # This is Genologics format and the header line
                            # so change column indices:
                            genologics_format = True
                            elements = line.split(',')
                            for idx, el in enumerate(elements):
                                if el == "Source Volume (uL)":
                                    source_vol_idx = idx
                                elif el == "Volume of Dilution Buffer (uL)":
                                    buffer_vol_idx = idx
                                elif el == "Destination Well":
                                    well_idx = idx
                                elif el == "Destination Plate":
                                    plate_idx = idx
                                elif el == "Sample Name":
                                    name_idx = idx
                        else:
                            elements = line.split(',')
                            well = elements[well_idx]
                            matches = re_well.search(well)
                            if matches:
                                well = ":".join(x for x in matches.groups())
                            plate = elements[plate_idx]
                            srcvol = elements[source_vol_idx]
                            bufvol = elements[buffer_vol_idx]
                            # Remove any quotes:
                            (plate, srcvol, bufvol) = [s.replace('"', '') for s in (plate, srcvol, bufvol)]
                            srcvol = float(srcvol)
                            bufvol = float(bufvol)
                            totvol = bufvol
                            # For Genologics format compability:
                            if genologics_format:
                                totvol += srcvol
                                name = elements[name_idx].replace('"', '')
                                samples_volumes[name] = totvol
                            else:
                                samples_volumes.setdefault(plate, {})[well] = totvol
    return samples_volumes


def make_datastructure(currentStep, lims, log):
    data = []
    samples_volumes = {}
    try:
        samples_volumes = obtain_previous_volumes(currentStep, lims)
    except:
        log.append("Unable to find previous volumes")
        sys.stderr.write("Samples volumes cannot be found. Check the file from the previous step")
        sys.exit(2)

    for inp, out in currentStep.input_output_maps:
        if out['output-type'] == 'Analyte':
            obj = {}
            obj['name'] = inp['uri'].samples[0].name
            obj['id'] = inp['uri'].id
            if "Normalized conc. (nM)" in inp['uri'].udf:
                obj['conc'] = inp['uri'].udf['Normalized conc. (nM)']
            else:
                obj['conc'] = inp['uri'].udf['Concentration']
            obj['pool_id'] = out['uri'].id
            # obj['pool_conc']=out['uri'].udf['Normalized conc. (nM)']
            obj['src_fc'] = inp['uri'].location[0].name
            obj['src_fc_id'] = inp['uri'].location[0].id
            obj['src_well'] = inp['uri'].location[1]
            obj['dst_fc'] = out['uri'].location[0].id
            obj['dst_well'] = out['uri'].location[1]
            # For Genologics format compability:
            if obj['name'] in samples_volumes:
                obj['vol'] = samples_volumes[obj['name']]
            # Try to match container ID first then name:
            elif obj['src_fc_id'] in samples_volumes:
                obj['vol'] = samples_volumes[obj['src_fc_id']][obj['src_well']]
            elif "Volume (ul)" in inp['uri'].udf:
                obj['vol']=inp['uri'].udf["Volume (ul)"]
            else:
                try:
                    obj['vol'] = samples_volumes[obj['src_fc']][obj['src_well']]
                except KeyError:
                    obj['vol'] = None
                    log.append("Unable to find previous volume for {}".format(obj["name"]))

            data.append(obj)

    return data

""" LAZY WAY
 Just divide the total with the number of samples, it is implied that the final
 conc and the conc of every input is the same:
"""


def lazy_volumes(samples, final_vol):
    return [final_vol / len(samples) for s in samples]

""" OTHER WAY
 Iteratively reduce the smallest input volume until we are close to the desired
 total volume. This works when the inputs have different concentrations, the
 final pool concentration will then end up somewhere between the conc of the
 highest and the lowest input concentration:
"""


def optimize_volumes(samples, final_vol, limit_vol=2):
    # Create a list we can sort to get the min/max values:
    l = [(s["conc"] * s["vol"], s["conc"], s["vol"]) for s in samples]
    # Find the min/max values by sorting on the different values:
    min_conc = sorted(l, key=lambda x: x[1])[0][1]  # unused
    max_conc = sorted(l, key=lambda x: x[1])[-1][1]
    min_vol = sorted(l, key=lambda x: x[2])[0][2]
    # The volume of the input with lowest amount:
    min_amount = sorted(l)[0][2]

    def _minimize_vol(vol, final_vol=final_vol, limit_vol=limit_vol, reduce=0.9):
        try_vol = reduce * vol
        # The lowest volume to take would then be (sample(s) w highest conc):
        low_vol = min(try_vol * max_conc / s["conc"] for s in samples)
        # Total pool volume if we were to take this amount of all samples:
        tot_vol = sum(try_vol * max_conc / s["conc"] for s in samples)
        # We don't want to pipette less than limit_vol
        # while keeping total volume above final_vol:
        if low_vol >= limit_vol and tot_vol >= final_vol and try_vol >= limit_vol:
            return _minimize_vol(try_vol)
        else:
            # We can't improve anymore within the given limits...
            return vol

    # Start from whichever is the smallest volume:
    use_vol = _minimize_vol(min(min_amount, min_vol))
    # Calculate the volume to take of each input:
    return [(use_vol * max_conc / s["conc"]) for s in samples]


def compute_transfer_volume(currentStep, lims, log):
    data = make_datastructure(currentStep, lims, log)
    returndata = []
    for pool in currentStep.all_outputs():
        if pool.type == 'Analyte':
            valid_inputs = [x for x in data if x['pool_id'] == pool.id]
            # Set the output conc of the pool and also get the "desired" pool
            # volume, which is which?
            final_vol = float(pool.udf["Final Volume (uL)"])
            conc = valid_inputs[0]["conc"]
            # If all inputs are of the same conc use the trivial algorithm,
            # else try to optimize:
            if all(s["conc"] == conc for s in valid_inputs):
                vols = lazy_volumes(valid_inputs, final_vol)
                if sum(vols) > MAX_WARNING_VOLUME:
                    log.append("ERROR: Total volume of pool {} is too high: {}. Redo the calculations manually!".format(pool.name, sum(vols)))
                pool.udf['Normalized conc. (nM)'] = conc
            else:
                vols = optimize_volumes(valid_inputs, final_vol, MIN_WARNING_VOLUME)
                # Calculate and add the theoretical pool conc:
                if sum(vols) > MAX_WARNING_VOLUME:
                    log.append("ERROR: Total volume of pool {} is too high: {}. Redo the calculations manually!".format(pool.name, sum(vols)))
                z = list(zip([s["conc"] for s in valid_inputs], vols))
                v = (sum(x[0] * x[1] for x in z) / sum(vols))
                pool.udf['Normalized conc. (nM)'] = v
            pool.put()
            for s, vol in zip(valid_inputs, vols):
                s['vol_to_take'] = vol
                returndata.append(s)

    return returndata


def aliquot_fixed_volume(currentStep, lims, volume, log):
    data = []
    for inp, out in currentStep.input_output_maps:
        if out['output-type'] == 'ResultFile':
            obj = {}
            obj['src_fc'] = inp['uri'].location[0].name.replace(',','_').replace(' ','_')
            obj['src_fc_id'] = inp['uri'].location[0].id
            obj['src_well'] = inp['uri'].location[1]
            obj['dst_fc'] = out['uri'].location[0].name.replace(',','_').replace(' ','_')
            obj['dst_fc_id'] = out['uri'].location[0].id
            obj['dst_well'] = out['uri'].location[1]
            obj['vol'] = volume
            data.append(obj)
    data = sorted(data, key=lambda k: (k['src_fc_id'], k['src_well']))
    return data


def prepooling(currentStep, lims):
    log = []

    if zika_utils.verify_step(
        currentStep, 
        targets = [('', 'Library Pooling (RAD-seq) v1.0')]
    ):
        zika_methods.pool(
            currentStep=currentStep, 
            lims=lims,
            udfs = {
                "target_amt": "Amount taken (ng)",
                "target_vol": "Final Volume (uL)",
                "target_conc": None,
                "final_amt": "Amount taken (ng)",
                "final_vol": "Final Volume (uL)",
                "final_conc": None
            }
        )

    elif currentStep.instrument.name == "Zika":
        zika_methods.pool(
            currentStep=currentStep, 
            lims=lims,
            udfs = {
                "target_amt": None, 
                "target_vol": "Final Volume (uL)",
                "target_conc": "Pool Conc. (nM)",
                "final_amt": None, 
                "final_vol": "Final Volume (uL)",
                "final_conc": "Pool Conc. (nM)"
            }
        )

    else:
        # First thing to do is to grab the volumes of the input artifacts. The method is ... rather unique.
        data = compute_transfer_volume(currentStep, lims, log)
        with open("bravo.csv", "w") as csvContext:
            for s in data:
                if s['vol_to_take'] > MAX_WARNING_VOLUME:
                    log.append("Volume for sample {} is above {}, redo the calculations manually".format(MAX_WARNING_VOLUME, s['name']))
                if s['vol_to_take'] < MIN_WARNING_VOLUME:
                    log.append("Volume for sample {} is below {}, redo the calculations manually".format(MIN_WARNING_VOLUME, s['name']))
                csvContext.write("{0},{1},{2},{3},{4}\n".format(s['src_fc_id'], s['src_well'], s['vol_to_take'], s['dst_fc'], s['dst_well']))
        if log:
            with open("bravo.log", "w") as logContext:
                logContext.write("\n".join(log))

        df = pd.read_csv("bravo.csv", header=None)
        df['dest_row'] = df.apply(lambda row: row[4].split(':')[0], axis=1)
        df['dest_col'] = df.apply(lambda row: int(row[4].split(':')[1]), axis=1)
        df = df.sort_values(['dest_col', 'dest_row']).drop(['dest_row', 'dest_col'], axis=1)
        df.to_csv('bravo.csv', header=False, index=False)

        for out in currentStep.all_outputs():
            # attach the csv file and the log file
            if out.name == "EPP Generated Bravo CSV File":
                attach_file(os.path.join(os.getcwd(), "bravo.csv"), out)
            if log and out.name == "Bravo Log":
                attach_file(os.path.join(os.getcwd(), "bravo.log"), out)
        if log:
            # to get an error display in the lims, you need a non-zero exit code AND a message in STDERR
            sys.stderr.write("Errors were met, please check the Log file\n")
            sys.exit(2)
        else:
            logging.info("Work done")


def setup_qpcr(currentStep, lims):
    log = []
    data = aliquot_fixed_volume(currentStep, lims, MIN_WARNING_VOLUME, log)
    with open("bravo.csv", "w") as csvContext:
        for s in data:
            csvContext.write("{0},{1},{2},{3},{4}\n".format(s['src_fc_id'], s['src_well'], s['vol'], s['dst_fc'], s['dst_well']))
    if log:
        with open("bravo.log", "w") as logContext:
            logContext.write("\n".join(log))

    df = pd.read_csv("bravo.csv", header=None)
    df['dest_row'] = df.apply(lambda row: row[4].split(':')[0], axis=1)
    df['dest_col'] = df.apply(lambda row: int(row[4].split(':')[1]), axis=1)
    df = df.sort_values(['dest_col', 'dest_row']).drop(['dest_row', 'dest_col'], axis=1)
    df.to_csv('bravo.csv', header=False, index=False)

    for out in currentStep.all_outputs():
        # attach the csv file and the log file
        if out.name == "EPP Generated Bravo CSV File":
            attach_file(os.path.join(os.getcwd(), "bravo.csv"), out)
        if log and out.name == "Bravo Log":
            attach_file(os.path.join(os.getcwd(), "bravo.log"), out)
    if log:
        # to get an error display in the lims, you need a non-zero exit code AND a message in STDERR
        sys.stderr.write("Errors were met, please check the Log file\n")
        sys.exit(2)
    else:
        logging.info("Work done")


def default_bravo(lims, currentStep, with_total_vol=True):

    # Re-route to Zika
    if zika_utils.verify_step(
        currentStep,
        targets = [
            ('SMARTer Pico RNA', "Setup Workset/Plate"),
            ("QIAseq miRNA",     "Setup Workset/Plate"),
        ]
        ):
        zika_methods.norm(
            currentStep=currentStep, 
            lims=lims,
            udfs = {
                "target_amt": "Target Amount (ng)",
                "target_vol": "Target Total Volume (uL)",
                "target_conc": None,
                "final_amt": "Amount taken (ng)",
                "final_vol": "Total Volume (uL)",
                "final_conc": None 
            }
        )
    elif zika_utils.verify_step(
        currentStep,
        targets = [("Amplicon", "Setup Workset/Plate")]
        ):
        zika_methods.norm(
            currentStep=currentStep, 
            lims=lims, 
            # Use lower minimum pipetting volume and customer metrics
            zika_min_vol=0.1,
            use_customer_metrics=True,
            udfs = {
                "target_amt": "Target Amount (ng)",
                "target_vol": "Target Total Volume (uL)",
                "target_conc": None,
                "final_amt": "Amount taken (ng)",
                "final_vol": "Total Volume (uL)",
                "final_conc": None
            }
        )
    
    elif currentStep.instrument.name == "Zika":
        sys.stderr.write("Zika is not enabled for the current workflow/step")
        sys.exit(2)

    else:
        wfs_with_vol_adj = ['SMARTer Pico RNA', 'QIAseq miRNA', 'Amplicon']
        checkTheLog = [False]
        dest_plate = []
        with open("bravo.csv", "w") as csvContext:
            with open("bravo.log", "w") as logContext:
                # working directly with the map allows easier input/output handling
                for art_tuple in currentStep.input_output_maps:
                # filter out result files
                    if art_tuple[0]['uri'].type == 'Analyte' and art_tuple[1]['uri'].type == 'Analyte':
                        source_fc = art_tuple[0]['uri'].location[0].name
                        source_well = art_tuple[0]['uri'].location[1]
                        dest_fc = art_tuple[1]['uri'].location[0].id
                        dest_well = art_tuple[1]['uri'].location[1]
                        dest_fc_name = art_tuple[1]['uri'].location[0].name
                        dest_plate.append(dest_fc_name)
                        if with_total_vol:
                            if art_tuple[1]['uri'].udf.get("Total Volume (uL)") or art_tuple[1]['uri'].udf.get("Target Total Volume (uL)"):
                                art_workflows, volume, final_volume, amount_taken, total_volume, target_amount = calc_vol(art_tuple, logContext, checkTheLog, wfs_with_vol_adj)
                                # Update Amount taken (ng) and Total Volume (uL) in LIMS
                                if not (set(wfs_with_vol_adj) & set(art_workflows)):
                                    if not any(x == '#ERROR#' for x in [volume, final_volume, amount_taken]):
                                        art_tuple[1]['uri'].udf['Amount taken (ng)'] = float(amount_taken)
                                        art_tuple[1]['uri'].udf['Total Volume (uL)'] = float(final_volume)
                                        art_tuple[1]["uri"].put()
                                else:
                                    if not any(x == '#ERROR#' for x in [volume, final_volume, amount_taken, total_volume, target_amount]):
                                        art_tuple[1]['uri'].udf['Amount taken (ng)'] = float(amount_taken)
                                        art_tuple[1]['uri'].udf['Total Volume (uL)'] = float(final_volume)
                                        art_tuple[1]['uri'].udf['Target Amount (ng)'] = float(target_amount)
                                        art_tuple[1]['uri'].udf['Target Total Volume (uL)'] = float(total_volume)
                                        art_tuple[1]["uri"].put()
                                csvContext.write("{0},{1},{2},{3},{4},{5}\n".format(source_fc, source_well, volume, dest_fc, dest_well, final_volume))
                            else:
                                logContext.write("No Total Volume found for sample {0}\n".format(art_tuple[0]['uri'].samples[0].name))
                                checkTheLog[0] = True
                        else:
                            art_workflows, volume, final_volume, amount_taken, total_volume, target_amount = calc_vol(art_tuple, logContext, checkTheLog, wfs_with_vol_adj)
                            csvContext.write("{0},{1},{2},{3},{4}\n".format(source_fc, source_well, volume, dest_fc, dest_well))

        df = pd.read_csv("bravo.csv", header=None)
        df['dest_row'] = df.apply(lambda row: row[4].split(':')[0], axis=1)
        df['dest_col'] = df.apply(lambda row: int(row[4].split(':')[1]), axis=1)
        df = df.sort_values(['dest_col', 'dest_row']).drop(['dest_row', 'dest_col'], axis=1)
        df.to_csv('bravo.csv', header=False, index=False)

        # For now only one output plate is supported:
        if len(list(set(dest_plate))) == 1:
            dest_plate_name = list(set(dest_plate))[0]
            os.rename("bravo.csv", "{}_bravo.csv".format(dest_plate_name))
            os.rename("bravo.log", "{}_bravo.log".format(dest_plate_name))
        else:
            sys.stderr.write("ERROR: Multiple output plates!\n")
            sys.exit(2)

        for out in currentStep.all_outputs():
            # attach the csv file and the log file
            if out.name == "EPP Generated Bravo CSV File":
                for f in out.files:
                    lims.request_session.delete(f.uri)
                lims.upload_new_file(out, "{}_bravo.csv".format(dest_plate_name))
            if out.name == "Bravo Log":
                for f in out.files:
                    lims.request_session.delete(f.uri)
                lims.upload_new_file(out, "{}_bravo.log".format(dest_plate_name))
        if checkTheLog[0]:
            # to get an error display in the lims, you need a non-zero exit code AND a message in STDERR
            sys.stderr.write("Errors were met, please check the Log file\n")
            sys.exit(2)
        else:
            logging.info("Work done")


def dilution(currentStep):
    checkTheLog = [False]
    # Read in the presets of minimum required conc for setup workset, maximum conc for dilution and minimum volume for dilution
    if "SMARTer Pico RNA" in currentStep.input_output_maps[0][0]['uri'].workflow_stages[0].workflow.name:
        preset = Dilution_preset['Smarter pico']
    # Use the values set in the step UDFs; if not set, use the default values
    min_required_conc = currentStep.udf['Minimum required conc for workset (ng/ul)'] if currentStep.udf['Minimum required conc for workset (ng/ul)'] else preset[0]
    max_conc_for_dilution = currentStep.udf['Maximum conc for dilution (ng/ul)'] if currentStep.udf['Maximum conc for dilution (ng/ul)'] else preset[1]
    min_vol_for_dilution = currentStep.udf['Minimum volume for dilution (ul)'] if currentStep.udf['Minimum volume for dilution (ul)'] else preset[2]
    with open("bravo.csv", "w") as csvContext:
        with open("bravo.log", "w") as logContext:
            # working directly with the map allows easier input/output handling
            for art_tuple in currentStep.input_output_maps:
            # filter out result files
                if art_tuple[0]['uri'].type == 'Analyte' and art_tuple[1]['uri'].type == 'Analyte':
                    source_fc = art_tuple[0]['uri'].location[0].name
                    source_well = art_tuple[0]['uri'].location[1]
                    dest_fc = art_tuple[1]['uri'].location[0].id
                    dest_well = art_tuple[1]['uri'].location[1]
                    try:
                        # Only ng/ul or ng/uL are supported
                        assert art_tuple[0]['uri'].udf['Conc. Units'] in ["ng/ul", "ng/uL"]
                        # Fill in all necessary UDFs
                        art_tuple[1]['uri'].udf['Concentration'] = art_tuple[0]['uri'].udf['Concentration']
                        art_tuple[1]['uri'].udf['Conc. Units'] = art_tuple[0]['uri'].udf['Conc. Units']
                        # Case that sample concentration lower than the minimum required conc for setup workset
                        if art_tuple[1]['uri'].udf['Concentration'] < min_required_conc:
                            if art_tuple[0]['uri'].udf['Volume (ul)'] >= min_vol_for_dilution:
                                art_tuple[1]['uri'].udf['Volume to take (uL)'] = min_vol_for_dilution
                                art_tuple[1]['uri'].udf['Final Concentration'] = art_tuple[1]['uri'].udf['Concentration']
                                art_tuple[1]['uri'].udf['Final Volume (uL)'] = min_vol_for_dilution
                                logContext.write("WARN : Sample {0} located {1} {2} has a LOWER conc than {3}. Take {4} ul directly into the dilution plate.\n".format(art_tuple[1]['uri'].samples[0].name,art_tuple[0]['uri'].location[0].name, art_tuple[0]['uri'].location[1],min_required_conc,min_vol_for_dilution))
                            else:
                                art_tuple[1]['uri'].udf['Volume to take (uL)'] = 0
                                art_tuple[1]['uri'].udf['Final Concentration'] = 0
                                art_tuple[1]['uri'].udf['Final Volume (uL)'] = 0
                                logContext.write("ERROR : Sample {0} located {1} {2} has a LOWER conc than {3} and total volume less than {4} ul. It is skipped in dilution.\n".format(art_tuple[1]['uri'].samples[0].name,art_tuple[0]['uri'].location[0].name, art_tuple[0]['uri'].location[1],min_required_conc,min_vol_for_dilution))
                        # Case that sample concentration higher than the maximum conc for dilution
                        elif art_tuple[1]['uri'].udf['Concentration'] > max_conc_for_dilution:
                            art_tuple[1]['uri'].udf['Volume to take (uL)'] = 0
                            art_tuple[1]['uri'].udf['Final Concentration'] = 0
                            art_tuple[1]['uri'].udf['Final Volume (uL)'] = 0
                            logContext.write("ERROR : Sample {0} located {1} {2} has a HIGHER conc than {3}. It is skipped in dilution.\n".format(art_tuple[1]['uri'].samples[0].name,art_tuple[0]['uri'].location[0].name, art_tuple[0]['uri'].location[1],max_conc_for_dilution))
                        # Case that dilution will be done with 2uL sample
                        elif art_tuple[1]['uri'].udf['Concentration'] <= max_conc_for_dilution and art_tuple[1]['uri'].udf['Concentration'] > float(min_required_conc*min_vol_for_dilution)/MIN_WARNING_VOLUME:
                            if art_tuple[0]['uri'].udf['Volume (ul)'] >= MIN_WARNING_VOLUME:
                                final_conc = min_required_conc
                                step = 0.25
                                while final_conc <= max_conc_for_dilution*MIN_WARNING_VOLUME/MAX_WARNING_VOLUME:
                                    if float(art_tuple[1]['uri'].udf['Concentration']*MIN_WARNING_VOLUME/final_conc) <= MAX_WARNING_VOLUME:
                                        art_tuple[1]['uri'].udf['Volume to take (uL)'] = MIN_WARNING_VOLUME
                                        art_tuple[1]['uri'].udf['Final Concentration'] = final_conc
                                        art_tuple[1]['uri'].udf['Final Volume (uL)'] = float(art_tuple[1]['uri'].udf['Concentration']*MIN_WARNING_VOLUME/final_conc)
                                        logContext.write("INFO : Sample {0} looks okay.\n".format(art_tuple[1]['uri'].samples[0].name))
                                        break
                                    else:
                                        final_conc = final_conc+0.25
                            else:
                                art_tuple[1]['uri'].udf['Volume to take (uL)'] = 0
                                art_tuple[1]['uri'].udf['Final Concentration'] = 0
                                art_tuple[1]['uri'].udf['Final Volume (uL)'] = 0
                                logContext.write("ERROR : Sample {0} located {1} {2} has a LOWER volume than {3} ul. It is skipped in dilution.\n".format(art_tuple[1]['uri'].samples[0].name,art_tuple[0]['uri'].location[0].name, art_tuple[0]['uri'].location[1],MIN_WARNING_VOLUME))
                        # Case that more than 2uL sample is needed for dilution
                        elif art_tuple[1]['uri'].udf['Concentration'] <= float(min_required_conc*min_vol_for_dilution)/MIN_WARNING_VOLUME and art_tuple[1]['uri'].udf['Concentration'] >= min_required_conc:
                            if art_tuple[0]['uri'].udf['Volume (ul)'] >= float(min_required_conc*min_vol_for_dilution/art_tuple[1]['uri'].udf['Concentration']):
                                art_tuple[1]['uri'].udf['Volume to take (uL)'] = float(min_required_conc*min_vol_for_dilution/art_tuple[1]['uri'].udf['Concentration'])
                                art_tuple[1]['uri'].udf['Final Concentration'] = min_required_conc
                                art_tuple[1]['uri'].udf['Final Volume (uL)'] = min_vol_for_dilution
                                logContext.write("INFO : Sample {0} looks okay.\n".format(art_tuple[1]['uri'].samples[0].name))
                            else:
                                art_tuple[1]['uri'].udf['Volume to take (uL)'] = 0
                                art_tuple[1]['uri'].udf['Final Concentration'] = 0
                                art_tuple[1]['uri'].udf['Final Volume (uL)'] = 0
                                logContext.write("ERROR : Sample {0} located {1} {2} has a LOWER volume than {3} ul. It is skipped in dilution.\n".format(art_tuple[1]['uri'].samples[0].name,art_tuple[0]['uri'].location[0].name, art_tuple[0]['uri'].location[1],float(min_required_conc*min_vol_for_dilution/art_tuple[1]['uri'].udf['Concentration'])))
                    except KeyError as e:
                        logContext.write("ERROR : The input artifact is lacking a field : {0}\n".format(e))
                        checkTheLog[0] = True
                    except AssertionError:
                        logContext.write("ERROR : This script expects the concentration to be in ng/ul or ng/uL, this does not seem to be the case.\n")
                        checkTheLog[0] = True
                    except ZeroDivisionError:
                        logContext.write("ERROR: Sample {0} has a concentration of 0\n".format(art_tuple[1]['uri'].samples[0].name))
                        checkTheLog[0] = True

                    art_tuple[1]['uri'].put()
                    csvContext.write("{0},{1},{2},{3},{4},{5}\n".format(source_fc, source_well, art_tuple[1]['uri'].udf['Volume to take (uL)'], dest_fc, dest_well, art_tuple[1]['uri'].udf['Final Volume (uL)']))

    df = pd.read_csv("bravo.csv", header=None)
    df['dest_row'] = df.apply(lambda row: row[4].split(':')[0], axis=1)
    df['dest_col'] = df.apply(lambda row: int(row[4].split(':')[1]), axis=1)
    df = df.sort_values(['dest_col', 'dest_row']).drop(['dest_row', 'dest_col'], axis=1)
    df.to_csv('bravo.csv', header=False, index=False)

    for out in currentStep.all_outputs():
        # attach the csv file and the log file
        if out.name == "EPP Generated Bravo CSV File":
            attach_file(os.path.join(os.getcwd(), "bravo.csv"), out)
        if out.name == "Bravo Log":
            attach_file(os.path.join(os.getcwd(), "bravo.log"), out)
    if checkTheLog[0]:
        # to get an eror display in the lims, you need a non-zero exit code AND a message in STDERR
        sys.stderr.write("Errors were met, please check the Log file\n")
        sys.exit(2)
    else:
        logging.info("Work done")


def normalization(current_step):
    log = []
    with open("bravo.csv", "w") as csv:
        for art in current_step.input_output_maps:
            src = art[0]["uri"]
            dest = art[1]["uri"]
            if src.type == dest.type == "Analyte":
                # Source sample:
                src_plate = src.location[0].id
                src_well = src.location[1]
                try:
                    src_tot_volume = float(src.udf["Volume (ul)"])
                except:
                    src_tot_volume = 999999
                    log.append("WARNING: No volume found for input sample {0}".format(src.samples[0].name))
                try:
                    src_volume = float(dest.udf["Volume to take (uL)"])
                except:
                    sys.stderr.write("Field 'Volume to take (uL)' is empty for artifact {0}\n".format(dest.name))
                    sys.exit(2)
                if "Concentration" in src.udf:
                    src_conc = src.udf["Concentration"]
                    if src.udf["Conc. Units"] != "nM":
                        log.append("ERROR: No valid concentration found for sample {0}".format(src.samples[0].name))
                elif "Normalized conc. (nM)" in src.udf:
                    src_conc = src.udf["Normalized conc. (nM)"]
                else:
                    sys.stderr.write("Non input concentration found for sample {0}\n".format(dest.name))
                    sys.exit(2)


                # Diluted sample:
                dest_plate = dest.location[0].id
                dest_well = dest.location[1]
                try:
                    dest_conc = dest.udf["Normalized conc. (nM)"]
                except:
                    sys.stderr.write("Field 'Normalized conc. (nM)' is empty for artifact {0}\n".format(dest.name))
                    sys.exit(2)
                if src_conc < dest_conc:
                    log.append("ERROR: Too low concentration for sample {0}".format(src.samples[0].name))
                else:
                    # Warn if volume to take > volume available or max volume is
                    # exceeded but still do the calculation:
                    if src_volume > src_tot_volume:
                        log.append("WARNING: Not enough available volume of sample {0}".format(src.samples[0].name))
                    final_volume = src_conc * src_volume / dest_conc
                    if final_volume > MAX_WARNING_VOLUME:
                        log.append("WARNING: Maximum volume exceeded for sample {0}".format(src.samples[0].name))
                    csv.write("{0},{1},{2},{3},{4},{5}\n".format(src_plate, src_well, src_volume, dest_plate, dest_well, final_volume))
    if log:
        with open("bravo.log", "w") as log_context:
            log_context.write("\n".join(log))

    df = pd.read_csv("bravo.csv", header=None)
    df['dest_row'] = df.apply(lambda row: row[4].split(':')[0], axis=1)
    df['dest_col'] = df.apply(lambda row: int(row[4].split(':')[1]), axis=1)
    df = df.sort_values(['dest_col', 'dest_row']).drop(['dest_row', 'dest_col'], axis=1)
    df.to_csv('bravo.csv', header=False, index=False)

    for out in current_step.all_outputs():
        # attach the csv file and the log file
        if out.name == "EPP Generated Bravo CSV File for Normalization":
            attach_file(os.path.join(os.getcwd(), "bravo.csv"), out)
        elif out.name == "Bravo Log" and log:
            attach_file(os.path.join(os.getcwd(), "bravo.log"), out)
    if log:
        # to get an eror display in the lims, you need a non-zero exit code AND a message in STDERR
        sys.stderr.write("Errors were met, please check the log file\n")
        sys.exit(2)
    else:
        logging.info("Work done")


def sample_dilution_before_QC(currentStep):
    checkTheLog = [False]
    mode = currentStep.udf['Mode']
    with open("bravo.csv", "w") as csvContext:
        with open("bravo.log", "w") as logContext:
            for art_tuple in currentStep.input_output_maps:
                if art_tuple[1]['output-generation-type'] == 'PerInput':
                    source_fc = art_tuple[0]['uri'].location[0].name
                    source_well = art_tuple[0]['uri'].location[1]
                    dest_fc = art_tuple[1]['uri'].location[0].id
                    dest_well = art_tuple[1]['uri'].location[1]
                    submitted_sam = art_tuple[0]['uri'].samples[0]
                    sample_name = submitted_sam.name
                    # Retrieve the previous aggregate values for concentration and volume. Note that aggregated values have a higher priority than customer values
                    try:
                        aggregate_conc = art_tuple[0]['uri'].udf['Concentration']
                        aggregate_vol = art_tuple[0]['uri'].udf['Volume (ul)']
                        input_conc = aggregate_conc
                        input_vol = aggregate_vol
                    except KeyError:
                        logContext.write("WARNING : Sample {0} does not have aggregated values for concentration or volume. Trying with customer values instead.\n".format(sample_name))
                        # Retrieve customer values for concentration and volume
                        try:
                            customer_conc = submitted_sam.udf['Customer Conc']
                            customer_vol = submitted_sam.udf['Customer Volume']
                            input_conc = customer_conc
                            input_vol = customer_vol
                        except KeyError:
                            logContext.write("ERROR : Sample {0} does not have customer values for concentration or volume. It will be skipped.\n".format(sample_name))
                            checkTheLog[0] = True
                            continue

                    # Volume for the dilution mode
                    if mode == 'Dilution to a new plate':
                        # Error when the input volume is lower than the minimum pipetting volume
                        if input_vol < MIN_WARNING_VOLUME:
                            logContext.write("ERROR : Sample {0} has too little volume for dilution.\n".format(sample_name))
                            checkTheLog[0] = True
                            continue
                        # Fetch the set value of volume to take. Otherwise take as little as possible
                        else:
                            try:
                                vol_taken = art_tuple[1]['uri'].udf['Volume to take (uL)']
                                if vol_taken > input_vol:
                                    logContext.write("ERROR : Sample {0} has a volume {1} uL which is not enough for taking {2} uL.\n".format(sample_name, customer_vol, vol_taken))
                                    checkTheLog[0] = True
                                    continue
                            except KeyError:
                                vol_taken = MIN_WARNING_VOLUME
                    # Volume for the aliquotation mode
                    elif mode == 'Add EB to original plate':
                        vol_taken = input_vol

                    # 1st priority: Aim concentration
                    try:
                        final_conc = art_tuple[1]['uri'].udf['Final Concentration']
                        final_vol = input_conc*vol_taken/final_conc
                        dilution_fold = final_vol/vol_taken
                        EB_vol = final_vol-vol_taken
                    except KeyError:
                        # 2nd priority: Final volume
                        try:
                            final_vol = art_tuple[1]['uri'].udf['Final Volume (uL)']
                            final_conc = input_conc*vol_taken/final_vol
                            dilution_fold = final_vol/vol_taken
                            EB_vol = final_vol-vol_taken
                        except KeyError:
                            # 3rd priority: Dilution fold
                            try:
                                dilution_fold = art_tuple[1]['uri'].udf['Dilution Fold']
                                final_vol = vol_taken*dilution_fold
                                final_conc = input_conc/dilution_fold
                                EB_vol = final_vol-vol_taken
                            except KeyError:
                            # Error when no value is set
                                logContext.write("ERROR : Sample {0} does not have a preset value.\n".format(sample_name))
                                checkTheLog[0] = True
                                continue
                    # Whether final volume is higher than the capacity of plate
                    if final_vol <= MAX_WARNING_VOLUME and final_conc <= input_conc:
                        art_tuple[1]['uri'].udf['Final Concentration'] = final_conc
                        art_tuple[1]['uri'].udf['Final Volume (uL)'] = final_vol
                        art_tuple[1]['uri'].udf['Dilution Fold'] = dilution_fold
                        art_tuple[1]['uri'].udf['Volume to take (uL)'] = vol_taken
                        art_tuple[1]['uri'].put()
                        if mode == 'Dilution to a new plate':
                            csvContext.write("{0},{1},{2},{3},{4},{5}\n".format(source_fc, source_well, vol_taken, dest_fc, dest_well, final_vol))
                        elif mode == 'Add EB to original plate':
                            csvContext.write("{0},{1},{2},{3},{4}\n".format('EB_plate', 'A1', EB_vol, source_fc, source_well))
                    elif final_vol > MAX_WARNING_VOLUME:
                        logContext.write("ERROR : Sample {0} will have a dilution higher than max allowed volume {1}.\n".format(sample_name, MAX_WARNING_VOLUME))
                        checkTheLog[0] = True
                        continue
                    elif final_conc > input_conc:
                        logContext.write("ERROR : Sample {0} will have a final concentration higher than the input concentration {1}.\n".format(sample_name, input_conc))
                        checkTheLog[0] = True
                        continue

    df = pd.read_csv("bravo.csv", header=None)
    df['dest_row'] = df.apply(lambda row: row[4].split(':')[0], axis=1)
    df['dest_col'] = df.apply(lambda row: int(row[4].split(':')[1]), axis=1)
    df = df.sort_values(['dest_col', 'dest_row']).drop(['dest_row', 'dest_col'], axis=1)
    df.to_csv('bravo.csv', header=False, index=False)

    for out in currentStep.all_outputs():
        # attach the csv file and the log file
        if out.name == "EPP Generated Bravo CSV File":
            attach_file(os.path.join(os.getcwd(), "bravo.csv"), out)
        if out.name == "Bravo Log":
            attach_file(os.path.join(os.getcwd(), "bravo.log"), out)
    if checkTheLog[0]:
        # to get an eror display in the lims, you need a non-zero exit code AND a message in STDERR
        sys.stderr.write("Errors were met, please check the Log file\n")
        sys.exit(2)
    else:
        logging.info("Work done")


def main(lims, args):
    currentStep = Process(lims, id=args.pid)

    if currentStep.type.name in ['Library Pooling (HiSeq X) 1.0']:
        check_barcode_collision(currentStep)
        prepooling(currentStep, lims)
    elif currentStep.type.name in [
        "Pre-Pooling (MiSeq) 4.0",
        "Pre-Pooling (Illumina SBS) 4.0",
        "Library Pooling (RAD-seq) v1.0",
        "Library Pooling (TruSeq Small RNA) 1.0",
        "Pre-Pooling (NovaSeq) v2.0",
        "Pre-Pooling (NextSeq) v1.0",
        "Pre-Pooling",
        "Pre-Pooling (NovaSeqXPlus) v1.0",
    ]:
        prepooling(currentStep, lims)
    elif currentStep.type.name in [
        "Library Normalization (HiSeq X) 1.0",
        "Library Normalization (Illumina SBS) 4.0",
        "Library Normalization (MiSeq) 4.0",
        "Library Normalization (NovaSeq) v2.0",
        "Library Normalization (NextSeq) v1.0",
        "Library Normalization,",
        "Library Normalization (NovaSeqXPlus) v1.0",
    ]:
        normalization(currentStep)
    elif currentStep.type.name == 'Library Pooling (RAD-seq) 1.0':
        default_bravo(lims, currentStep, False)
    elif currentStep.type.name == 'Diluting Samples':
        dilution(currentStep)
    elif currentStep.type.name == 'Sample Dilution Before QC':
        sample_dilution_before_QC(currentStep)
    elif currentStep.type.name in [
        "qPCR QC (Library Validation) 4.0",
        "qPCR QC (Dilution Validation) 4.0",
    ]:
        setup_qpcr(currentStep, lims)
    else:
        default_bravo(lims, currentStep)


def calc_vol(art_tuple, logContext, checkTheLog, wfs_with_vol_adj):
    art_workflows = []
    for stage in art_tuple[0]['uri'].workflow_stages_and_statuses:
        if stage[1] == 'IN_PROGRESS':
            art_workflows.append(stage[0].workflow.name)
    no_depletion_flag = False
    project = art_tuple[0]['uri'].samples[0].project
    if project and ('no depletion' in project.udf.get('Library construction method', '') or 'No depletion' in project.udf.get('Library prep option', '')):
        no_depletion_flag = True
    try:
        # not handling different units yet. Might be needed at some point.
        assert art_tuple[0]['uri'].udf['Conc. Units'] in ["ng/ul", "ng/uL"]

        if not (set(wfs_with_vol_adj) & set(art_workflows)):
            amount_ng = target_amount = art_tuple[1]['uri'].udf.get('Amount taken (ng)', 0)
            final_volume = total_volume = art_tuple[1]['uri'].udf.get('Total Volume (uL)', 0)
        else:
            amount_ng = target_amount = art_tuple[1]['uri'].udf.get('Target Amount (ng)', 0)
            final_volume = total_volume = art_tuple[1]['uri'].udf.get('Target Total Volume (uL)', 0)

        max_volume_warning = ''
        if final_volume > MAX_WARNING_VOLUME:
            max_volume_warning = 'NOTE! Total dilution volume higher than {}!'.format(MAX_WARNING_VOLUME)

        try:
            if art_tuple[0]['uri'].parent_process.type.name == "Diluting Samples":
                conc = art_tuple[0]['uri'].udf['Final Concentration']
                org_vol = art_tuple[0]['uri'].udf['Final Volume (uL)']
            else:
                conc = art_tuple[0]['uri'].udf['Concentration']
                org_vol = art_tuple[0]['uri'].udf['Volume (ul)']
        except AttributeError:
            conc = art_tuple[0]['uri'].udf['Concentration']
            org_vol = art_tuple[0]['uri'].udf['Volume (ul)']
        volume = float(amount_ng) / float(conc)

        # Case with very low sample volume: take everything or what is needed. Reset amount values and keep the target dilution volume
        if org_vol < MIN_WARNING_VOLUME:
            volume = min(org_vol, volume)
            amount_taken = target_amount = volume * conc
            logContext.write("WARN : Sample {0} located {1} {2} has a LOW original volume : {3}. Take {4}uL sample which is {5}ng and dilute in a total volume {6}uL. {7}\n".format(art_tuple[1]['uri'].samples[0].name,
                                                                                                                                                                                 art_tuple[0]['uri'].location[0].name,
                                                                                                                                                                                 art_tuple[0]['uri'].location[1],
                                                                                                                                                                                 "{0:.2f}".format(org_vol),
                                                                                                                                                                                 "{0:.2f}".format(volume),
                                                                                                                                                                                 "{0:.2f}".format(amount_taken),
                                                                                                                                                                                 "{0:.2f}".format(final_volume),
                                                                                                                                                                                 max_volume_warning))
            checkTheLog[0] = True
        # Case with very low pipetting volume due to high sample conc:
        elif volume < MIN_WARNING_VOLUME:
            # When the volume is lower than the MIN_WARNING_VOLUME, set volume to MIN_WARNING_VOLUME, and expand the final dilution volume
            # But not apply to the no-depletion RNA protocol
            if not no_depletion_flag:
                final_volume = MIN_WARNING_VOLUME*float(conc)/(float(amount_ng)/float(final_volume))
                amount_taken = MIN_WARNING_VOLUME*conc

                if final_volume > MAX_WARNING_VOLUME:
                    max_volume_warning = 'NOTE! Total dilution volume higher than {}!'.format(MAX_WARNING_VOLUME)

                logContext.write("WARN : Sample {0} located {1} {2}  has a LOW pippetting volume: {3}. CSV adjusted by taking {4}uL sample which is {5}ng and diluting in a total volume {6}uL. {7}\n".format(art_tuple[1]['uri'].samples[0].name,
                                                                                                                                                                                                        art_tuple[0]['uri'].location[0].name,
                                                                                                                                                                                                        art_tuple[0]['uri'].location[1],
                                                                                                                                                                                                        "{0:.2f}".format(volume),
                                                                                                                                                                                                        MIN_WARNING_VOLUME,
                                                                                                                                                                                                        "{0:.2f}".format(amount_taken),
                                                                                                                                                                                                        "{0:.2f}".format(final_volume),
                                                                                                                                                                                                        max_volume_warning))
                volume = MIN_WARNING_VOLUME
                target_amount = amount_taken/final_volume*total_volume
            else:
                amount_taken = target_amount = volume * conc
                logContext.write("WARN : Sample {0} located {1} {2} has a LOW pippetting volume: {3}. Take {4}uL sample which is {5}ng and dilute in a total volume {6}uL. {7}\n".format(art_tuple[1]['uri'].samples[0].name,
                                                                                                                                                                                      art_tuple[0]['uri'].location[0].name,
                                                                                                                                                                                      art_tuple[0]['uri'].location[1],
                                                                                                                                                                                      "{0:.2f}".format(volume),
                                                                                                                                                                                      "{0:.2f}".format(volume),
                                                                                                                                                                                      "{0:.2f}".format(amount_taken),
                                                                                                                                                                                      "{0:.2f}".format(final_volume),
                                                                                                                                                                                      max_volume_warning))
            checkTheLog[0] = True
        elif volume > org_vol or volume > final_volume:
            # check against the "original sample volume" and the "total dilution volume"
            new_volume = min(org_vol, final_volume)
            amount_taken = target_amount = new_volume * conc
            if org_vol <= final_volume:
                logContext.write("WARN : Sample {0} located {1} {2} has a HIGHER volume than the original: {3}uL over {4}uL. Take original volume: {4}uL which is {5}ng and dilute in a total volume {6}uL. {7}\n".format(art_tuple[1]['uri'].samples[0].name,
                                                                                                                                                                                                                             art_tuple[0]['uri'].location[0].name,
                                                                                                                                                                                                                             art_tuple[0]['uri'].location[1],
                                                                                                                                                                                                                             "{0:.2f}".format(volume),
                                                                                                                                                                                                                             "{0:.2f}".format(org_vol),
                                                                                                                                                                                                                             "{0:.2f}".format(amount_taken),
                                                                                                                                                                                                                             "{0:.2f}".format(final_volume),
                                                                                                                                                                                                                             max_volume_warning))
            else:
                logContext.write("WARN : Sample {0} located {1} {2} has a HIGHER volume than the total: {3}uL over {4}uL. Take total volume: {4}uL which is {5}ng. {6}\n".format(art_tuple[1]['uri'].samples[0].name,
                                                                                                                                                                                  art_tuple[0]['uri'].location[0].name,
                                                                                                                                                                                  art_tuple[0]['uri'].location[1],
                                                                                                                                                                                  "{0:.2f}".format(volume),
                                                                                                                                                                                  "{0:.2f}".format(final_volume),
                                                                                                                                                                                  "{0:.2f}".format(amount_taken),
                                                                                                                                                                                  max_volume_warning))
            volume = new_volume
            checkTheLog[0] = False
        elif max_volume_warning:
            amount_taken = target_amount = volume * conc
            logContext.write("WARN : Sample {0} located {1} {2}: {3}\n".format(art_tuple[1]['uri'].samples[0].name,
                                                                               art_tuple[0]['uri'].location[0].name,
                                                                               art_tuple[0]['uri'].location[1],
                                                                               max_volume_warning))
        else:
            amount_taken = target_amount = volume * conc
            logContext.write("INFO : Sample {0} located {1} {2} looks okay.\n".format(art_tuple[1]['uri'].samples[0].name,
                                                                                      art_tuple[0]['uri'].location[0].name,
                                                                                      art_tuple[0]['uri'].location[1]))

        return (art_workflows, "{0:.2f}".format(volume), "{0:.2f}".format(final_volume), "{0:.2f}".format(amount_taken), "{0:.2f}".format(total_volume), "{0:.2f}".format(target_amount))
    except KeyError as e:
        logContext.write("ERROR : The input artifact is lacking a field : {0}\n".format(e))
        checkTheLog[0] = True
    except AssertionError:
        logContext.write("ERROR : This script expects the concentration to be in ng/ul or ng/uL, this does not seem to be the case.\n")
        checkTheLog[0] = True
    except ZeroDivisionError:
        logContext.write("ERROR: Sample {0} has a concentration of 0\n".format(art_tuple[1]['uri'].samples[0].name))
        checkTheLog[0] = True
    # this allows to still write the file. Won't be readable though
    return (art_workflows, "#ERROR#", "#ERROR#", "#ERROR#", "#ERROR#", "#ERROR#")

def check_barcode_collision(step):
    for output in step.all_outputs():
        barcodes=[]
        if output.type == "Analyte":
            for io in step.input_output_maps:
                if io[1]['limsid'] == output.id:
                    barcode=find_barcode(io[0]['uri'])
                    if barcode not in barcodes:
                        barcodes.append(find_barcode(io[0]['uri']))
                    else:
                        raise Exception("Similar barcodes {0} in pool {}".format(barcode, output.id))

def find_barcode(artifact):
        if len(artifact.samples) == 1 and artifact.reagent_labels:
            reagent_label_name=artifact.reagent_labels[0].upper()
            idxs = TENX_PAT.findall(reagent_label_name)
            if idxs:
                # Put in tuple with empty string as second index to
                # match expected type:
                idxs = (idxs[0], "")
            else:
                try:
                    idxs = IDX_PAT.findall(reagent_label_name)[0]
                except IndexError:
                    try:
                        # we only have the reagent label name.
                        rt = lims.get_reagent_types(name=reagent_label_name)[0]
                        idxs = IDX_PAT.findall(rt.sequence)[0]
                    except:
                        return ("NoIndex","")

            return idxs
        else:
            if artifact == artifact.samples[0].artifact:
                return None
            else:
                next_artifact=None
                for iomap in artifact.parent_process.input_output_maps:
                    if iomap[1]['uri'].id == artifact.id:
                        next_artifact=iomap[0]['uri']
                return find_barcode(next_artifact)


if __name__ == "__main__":
    parser = ArgumentParser(description=DESC)
    parser.add_argument('--pid',
                        help='Lims id for current Process')
    args = parser.parse_args()

    lims = Lims(BASEURI, USERNAME, PASSWORD)
    lims.check_version()
    main(lims, args)
