def get_values(*names):
    import json
    _all_values = json.loads("""{"num_samp":4,"reset_tipracks":true, "mix_reps2":5, "settling_1":4, "settling_3":3, "settling_drying":3, "wash1_vol":250,"wash2_vol":200, "wash3_vol":100, "elution_vol":60, "binding_buffer_vol":200,"final_vol":80,"heating_module_temp":60,"mag_height_1":3.1,"waste_water_mode":false,"asp_height":1,"length_from_side":2,"p300_mount":"left"}""")
    return [_all_values[n] for n in names]


"""Protocol."""
import os
import csv
from opentrons.types import Point

metadata = {
    'protocolName': 'RNA Extraction with Magnetic Life',
    'author': 'Rami Farawi <rami.farawi@opentrons.com>',
    'source': 'Custom Protocol Request',
    'apiLevel': '2.11'
}


def run(ctx):
    """Protocol."""
    [num_samp, reset_tipracks,  mix_reps2,
     settling_1, settling_3, settling_drying, 
     wash1_vol, wash2_vol, wash3_vol, elution_vol, 
     binding_buffer_vol, final_vol, heating_module_temp, mag_height_1,
     waste_water_mode, asp_height, length_from_side,
     p300_mount] = get_values(  # noqa: F821
        "num_samp", "reset_tipracks", "mix_reps2",
         "settling_1", "settling_3","settling_drying", 
         "wash1_vol", "wash2_vol", "wash3_vol", "elution_vol",
         "binding_buffer_vol", "final_vol",
        "heating_module_temp", "mag_height_1", "waste_water_mode",
        "asp_height", "length_from_side", "p300_mount")

    if not 0 <= num_samp <= 4:
        raise Exception('Please enter a sample number between 1-4')

    # load labware
    mag_mod = ctx.load_module('magnetic module gen2', '7')
    mag_plate = mag_mod.load_labware(
        'innovationlaboratoryproducts_96_wellplate_1000ul', label='Mag Plate')
    reagent_tuberack = ctx.load_labware(
                'opentrons_10_tuberack_falcon_4x50ml_6x15ml_conical', '4',
                label='Reagent Tuberack')
    waste_tuberack = ctx.load_labware(
                            'opentrons_6_tuberack_falcon_50ml_conical', '5',
                            label='Waste Tuberack')
    tiprack300 = [ctx.load_labware('opentrons_96_tiprack_300ul', slot)
                  for slot in ['8', '9']]
    temp_mod = ctx.load_module('temperature module gen2', '10')
    temp_rack = temp_mod.load_labware(
        'opentrons_96_aluminumblock_biorad_wellplate_200ul',
        label='Temperature tuberack')
    tiprack200 = ctx.load_labware('opentrons_96_filtertiprack_200ul', '11')
    tuberack_lysis = ctx.load_labware('opentrons_24_tuberack_eppendorf_1.5ml_safelock_snapcap', '3')

    # load instrument
    p300 = ctx.load_instrument('p300_single_gen2',
                               p300_mount,
                               tip_racks=tiprack300)

    # Tip tracking between runs
    if not ctx.is_simulating():
        file_path = '/data/csv/tiptracking.csv'
        file_dir = os.path.dirname(file_path)
        # check for file directory
        if not os.path.exists(file_dir):
            os.makedirs(file_dir)
        # check for file; if not there, create initial tip count tracking
        if not os.path.isfile(file_path):
            with open(file_path, 'w') as outfile:
                outfile.write("0, 0\n")

    tip_count_list = []
    if ctx.is_simulating():
        tip_count_list = [0, 0]
    elif reset_tipracks:
        tip_count_list = [0, 0]
    else:
        with open(file_path) as csv_file:
            csv_reader = csv.reader(csv_file, delimiter=',')
            tip_count_list = next(csv_reader)

    num_one = int(tip_count_list[0])
    num_two = int(tip_count_list[1])
    starting_tip_regular = num_one
    starting_tip_filter = num_two

    tips_300 = [tip for tiprack in tiprack300 for tip in tiprack.wells()]
    regular_tip_count = num_one
    regular_parked_tip_nums = {0: starting_tip_regular,
                               1: starting_tip_regular+1,
                               2: starting_tip_regular+2,
                               3: starting_tip_regular+3}

    def pick_up300(sample_tip=num_one, use_park=False):
        nonlocal regular_tip_count
        if regular_tip_count == 95:
            ctx.pause("Replace all 300ul non-filter tip racks")
            regular_tip_count = 0
            p300.pick_up_tip(tips_300[regular_tip_count+num_samp])
            regular_tip_count += 1
        elif use_park:
            p300.pick_up_tip(tips_300[regular_parked_tip_nums[sample_tip]])
        else:
            p300.pick_up_tip(tips_300[regular_tip_count+num_samp])
            regular_tip_count += 1

    filter_tip_count = num_two
    filter_parked_tip_nums = {0: starting_tip_filter,
                              1: starting_tip_filter+1,
                              2: starting_tip_filter+2,
                              3: starting_tip_filter+3}

    def pick_up_filter(sample_tip=num_two, use_park=False):
        nonlocal filter_tip_count
        if filter_tip_count == 95:
            ctx.pause("Replace all 200ul filter tip racks")
            filter_tip_count = 0
            p300.pick_up_tip(tiprack200.wells()[filter_tip_count+num_samp])
            filter_tip_count += 1
        else:
            p300.pick_up_tip(tiprack200.wells()[filter_tip_count+num_samp])
            filter_tip_count += 1

    # load reagents
    lysis_buffer = reagent_tuberack.wells_by_name()['A2'].bottom(z=30)
    elution_buffer = reagent_tuberack.wells_by_name()['C2'].bottom(z=30)
    binding_buffer = reagent_tuberack.wells_by_name()['B2'].bottom(z=20)
    rps_wash_buffer = reagent_tuberack.wells_by_name()['A3'].bottom(z=20)
    wash_buffer = reagent_tuberack.wells_by_name()['A4'].bottom(z=20)
    calciumchloride_buffer = reagent_tuberack.wells_by_name()['B1'].bottom(z=20)
    samples_lysis = tuberack_lysis.columns()[0][:6:1][:num_samp]
    samples = mag_plate.columns()[0][:8:1][:num_samp]
    samples_second_well = mag_plate.columns()[1][:8:1][:num_samp]
    samples_third_well = mag_plate.columns()[2][:8:1][:num_samp]
    waste = waste_tuberack.wells()[0].bottom(z=80)

    
    def mixing(vol, index, loc, loc_length_from_side):
        ctx.comment('Mixing')
        side = 0
        mix_loc3 = loc.bottom(z=5).move(
                Point(x=(loc.diameter/2-loc_length_from_side)*side))
        p300.mix(10, vol, mix_loc3, rate = 10)

    def mixing_RPS(vol, index, loc, loc_length_from_side):
        ctx.comment('Mixing RPS')
        side = 1
        mix_loc = loc.bottom(z=2).move(
                Point(x=(loc.diameter/2-loc_length_from_side)*side))
        p300.mix(mix_reps2, vol, mix_loc, rate = 5)

    def mixing_RPS_1(vol, index, loc, loc_length_from_side):
        ctx.comment('Mixing RPS')
        side = 1
        mix_loc_w = loc.bottom(z=3).move(
                Point(x=(loc.diameter/2-loc_length_from_side)*side))
        side = 0
        aspirate_loc = loc.bottom(z = 2).move(
                Point(x=(loc.diameter/2-loc_length_from_side)*side))
        
        for wash in range(15):
            p300.aspirate(vol, aspirate_loc, rate = 5)
            p300.dispense(vol, mix_loc_w, rate = 20)

    def mixing_lysis(vol, index, loc, loc_length_from_side):
        ctx.comment('Mixing RPS')
        side = 1
        mix_loc_w = loc.bottom(z=4).move(
                Point(x=(loc.diameter/2-loc_length_from_side)*side))
        side = 0
        aspirate_loc = loc.bottom(z = 2).move(
                Point(x=(loc.diameter/2-loc_length_from_side)*side))
        
        for wash in range(30):
            p300.aspirate(vol, aspirate_loc, rate = 5)
            p300.dispense(vol, mix_loc_w, rate = 20)

    def aspirate_elution(vol, index, loc, loc_length_from_side):
        ctx.comment('Aspirate Elution')
        side = 0
        aspirate_loc = loc.bottom(z = 3).move(
                Point(x=(loc.diameter/2-loc_length_from_side)*side))
        p300.aspirate(vol, aspirate_loc, rate = 5)

    def mixing_RPS_2(vol, index, loc, loc_length_from_side):
        ctx.comment('Mixing RPS')
        side = -1
        mix_loc_w = loc.bottom(z=8).move(
                Point(x=(loc.diameter/2-loc_length_from_side)*side))
        side = 0
        aspirate_loc = loc.bottom(z = 2).move(
                Point(x=(loc.diameter/2-loc_length_from_side)*side))

        for wash in range(30):
            p300.aspirate(vol, aspirate_loc, rate = 5)
            p300.dispense(vol, mix_loc_w, rate = 20)
    
    def mixing_elution(vol, index, loc, loc_length_from_side):
        ctx.comment('Mixing Elution')
        side = -1
        mix_loc_w = loc.bottom(z=8).move(
                Point(x=(loc.diameter/2-loc_length_from_side)*side))
        side = 0
        aspirate_loc = loc.bottom(z = 2).move(
                Point(x=(loc.diameter/2-loc_length_from_side)*side))

        for wash in range(30):
            p300.aspirate(vol, aspirate_loc, rate = 5)
            p300.dispense(vol, mix_loc_w, rate = 20)
    
    def remove_supernatant(vol, index, loc, loc_length_from_side):
        ctx.comment('Removing Supernatant')

        side = 0
        dispense_loc = loc.bottom(z = 13.5).move(
                Point(x=(loc.diameter/2-loc_length_from_side)*side))
        side = -1 
        aspirate_loc = loc.bottom(z = asp_height).move(
                Point(x=(loc.diameter/2-loc_length_from_side)*side))
        aspirate_bot = loc.bottom(z = 2).move(
                Point(x=(loc.diameter/2-loc_length_from_side)*side))
        aspirate_top = loc.bottom(z = 3).move(
                Point(x=(loc.diameter/2-loc_length_from_side)*side))
        aspirate_mid = loc.bottom(z = 2.5).move(
                Point(x=(loc.diameter/2-loc_length_from_side)*side))
        p300.dispense(0, dispense_loc)
        p300.aspirate(vol, aspirate_top, rate=0.1)
        p300.aspirate(vol, aspirate_mid, rate = 0.1)
        p300.aspirate(vol, aspirate_bot, rate=0.1) 
        p300.dispense(vol*3, waste)

    def remove_beadbuffer(vol, index, loc, loc_length_from_side):
        ctx.comment('Removing Supernatant')

        side = 0
        dispense_loc = loc.bottom(z = 13.5).move(
                Point(x=(loc.diameter/2-loc_length_from_side)*side))
        side = -1 
        aspirate_out = loc.bottom(z = 3).move(
                Point(x=(loc.diameter/2-loc_length_from_side)*side))
        p300.dispense(0, dispense_loc)
        p300.aspirate(vol, aspirate_out, rate=0.1) 
        p300.dispense(vol, waste)


    def remove_supernatant_2(vol, index, loc, loc_length_from_side):
        ctx.comment('Removing Supernatant')

        side = 0
        dispense_loc = loc.bottom(z = 13.5).move(
                Point(x=(loc.diameter/2-loc_length_from_side)*side))
        side = 1 
        aspirate_loc = loc.bottom(z = asp_height).move(
                Point(x=(loc.diameter/2-loc_length_from_side)*side))
        aspirate_bot = loc.bottom(z = 2).move(
                Point(x=(loc.diameter/2-loc_length_from_side)*side))
        aspirate_top = loc.bottom(z = 3).move(
                Point(x=(loc.diameter/2-loc_length_from_side)*side))
        aspirate_mid = loc.bottom(z = 3.5).move(
                Point(x=(loc.diameter/2-loc_length_from_side)*side))
        p300.dispense(0, dispense_loc)
        p300.aspirate(vol, aspirate_top, rate=0.1)
        p300.aspirate(vol, aspirate_mid, rate = 0.1)
        p300.aspirate(vol, aspirate_bot, rate=0.1) 
        p300.dispense(vol*3, waste)

    def dry_samp(vol, index, loc, loc_length_from_side):
        ctx.comment('dry sample')
        side = 0
        dispense_loc = loc.bottom(z = 13.5).move(
                Point(x=(loc.diameter/2-loc_length_from_side)*side))
        side = 1
        aspirate_loc = loc.bottom(z = 0.2).move(
                Point(x=(loc.diameter/2-loc_length_from_side)*side))
        p300.dispense(0, dispense_loc)
        p300.aspirate(vol, aspirate_loc, rate=0.1)
        p300.dispense(vol, waste)
        p300.drop_tip()

    def dispense_height(vol, index, loc, loc_length_from_side):
        ctx.comment('dispense')
        side = 0
        dispense_loc = loc.bottom(z = 10).move(
                Point(x=(loc.diameter/2-loc_length_from_side)*side))
        p300.dispense(vol, dispense_loc)
    
    def lysis_dispense(vol, index, loc, loc_length_from_side):
        ctx.comment('dispense')
        side = 0
        dispense_loc = loc.bottom(z = 20).move(
                Point(x=(loc.diameter/2-loc_length_from_side)*side))
        p300.dispense(vol, dispense_loc)

    # initialize temp mod and magnetic mod
    temp_mod.set_temperature(heating_module_temp)
    mag_mod.engage(height_from_base=mag_height_1)

    if not waste_water_mode:
        ctx.comment('\n\n\n\nNormal Mode')
        mag_mod.disengage()
    
    tips = [tip for rack in tiprack300 for tip in rack.wells()]
    tipcount = 0

    def pick_up():
        nonlocal tipcount
        p300.pick_up_tip(tips[tipcount])
        tipcount += 1
    mag_mod.engage(height_from_base=mag_height_1)
    
    # lysing samples *****************
    for wash in range(1):
        for i, lysis in enumerate(tuberack_lysis.wells()[:num_samp]):
            pick_up()
            p300.aspirate(100, lysis_buffer)
            lysis_dispense(100, i, lysis, 2)
            p300.return_tip()
    tipcount = 0
    for wash in range(1):
        for i, lysis in enumerate(tuberack_lysis.wells()[:num_samp]):
            pick_up()
            mixing_lysis(100, i, lysis, 2)
            p300.blow_out()
            p300.return_tip()
    ctx.delay(minutes = 3)
    tipcount = 0
    for wash in range(3):
        for i, lysis in enumerate(tuberack_lysis.wells()[:num_samp]):
            pick_up()
            mixing_lysis(90, i, lysis, 2)
            p300.dispense(100, lysis)
            p300.blow_out()
            p300.return_tip()
        tipcount = 0
        ctx.comment('End Normal Mode\n\n\n\n\n\n\n\n\n')
    
    # p300.reset_tipracks()
    ctx.delay(minutes=3)
    
    #Remove Storage Buffer From Beads
    for wash in range(1):
        for i, sample in enumerate(samples):
            pick_up()
            remove_beadbuffer(20, i, sample, 2)
            p300.return_tip()    
        tipcount = 0
    mag_mod.disengage()
    # Add CaCl2 and Move Lysed sample to mag Plate
    for wash in range(1):
        for i, (lysis, sample) in enumerate(zip(tuberack_lysis.wells()[:num_samp],
                                            samples)):
            pick_up300()
            p300.aspirate(100, calciumchloride_buffer)
            p300.dispense(100, lysis)
            mixing_lysis(150, i, lysis, 2)
            p300.drop_tip()
        ctx.delay(minutes = 3)
        for i, (lysis, sample) in enumerate(zip(tuberack_lysis.wells()[:num_samp],
                                            samples)):
            pick_up()
            mixing_RPS(150, i, lysis, 2)
            p300.aspirate(220, lysis)
            p300.dispense(220, sample)
            mixing_RPS(150, i, sample, 2)
            p300.return_tip()    
        tipcount = 0

    ctx.delay(minutes=7)

    # binding
    for wash in range(1):
        pick_up_filter()
        for i, sample in enumerate(samples):
            p300.aspirate(binding_buffer_vol, binding_buffer)
            dispense_height(binding_buffer_vol, i, sample, 2)
        p300.drop_tip()
        for i, sample in enumerate(samples):
            pick_up()
            mixing(300, i, sample, 1)
            p300.dispense(100, sample)
            p300.touch_tip()
            p300.drop_tip()
        mag_mod.engage(height_from_base=mag_height_1)
        ctx.delay(minutes=5)

        # remove supernatant
        for i, sample in enumerate(samples):
            pick_up300()
            dispense_height(0, i, sample, 2)
            remove_supernatant(100, i, sample, 1)
            remove_supernatant(50, i, sample, 1)
            p300.drop_tip()
        mag_mod.disengage()
        ctx.comment('End Normal Mode\n\n\n\n\n\n\n\n\n')

    if waste_water_mode:
        # remove storage buffer
        ctx.delay(minutes=settling_3)
        for i, sample in enumerate(samples):
            pick_up300(i, use_park=True)
            for _ in range(5):
                p300.aspirate(wash1_vol, sample)
                p300.dispense(wash1_vol, waste)
                p300.blow_out()
            p300.return_tip()
        mag_mod.disengage()
        ctx.comment('\n\n\n')

    #One RPS Wash and move
    ctx.comment('one wash')
    for wash in range(3):
        pick_up300()
        for i, sample in enumerate(samples):
            p300.aspirate(wash1_vol, rps_wash_buffer)
            dispense_height(wash1_vol, i, sample, 2)
        p300.drop_tip()    
        for i, (sample, samp_well2) in enumerate(zip(samples,
                                                 samples_second_well)):
            pick_up300()
            dispense_height(0, i, sample, 2)
            mixing_RPS_1(230, i, sample, 2)
            p300.aspirate(wash1_vol, sample)
            p300.dispense(wash1_vol, samp_well2)
            mixing_RPS_1(230, i, samp_well2, 2)
            p300.touch_tip()
            p300.drop_tip()
        mag_mod.engage(height_from_base=mag_height_1)
        ctx.delay(minutes=3)
        for i, samp_well2 in enumerate(samples_second_well):
            pick_up300()
            dispense_height(0, i, samp_well2, 2)
            remove_supernatant_2(100, i, samp_well2, 1)
            p300.drop_tip()
        mag_mod.disengage()

    #One Wash and moving sample to next clean well
    for wash in range(3):
         pick_up300()
         for i, samp_well2 in enumerate(samples_second_well):
             p300.aspirate(wash1_vol, wash_buffer)
             dispense_height(wash1_vol, i, samp_well2, 2)
         p300.drop_tip()
         for i, samp_well2 in enumerate(samples_second_well):
             pick_up300()
             dispense_height(0, i, samp_well2, 2)
             mixing_RPS_1(230, i, samp_well2, 2)
             p300.touch_tip()
             p300.drop_tip()
         mag_mod.engage(height_from_base=mag_height_1)
         ctx.delay(minutes=3)
         for i, samp_well2 in enumerate(samples_second_well):
            pick_up300()
            dispense_height(0, i, samp_well2, 2)
            remove_supernatant_2(100, i, samp_well2, 1)
            p300.drop_tip()
         mag_mod.disengage()
        
    #One Wash
    ctx.comment('One wash')
    for wash in range(2):
        pick_up300()
        for i, samp_well2 in enumerate(samples_second_well):
            p300.aspirate(wash1_vol, wash_buffer)
            dispense_height(wash1_vol, i, samp_well2, 2)
        p300.drop_tip()    
        for i, samp_well2 in enumerate(samples_second_well):
            pick_up300()
            dispense_height(0, i, samp_well2, 2)
            mixing_RPS_2(230, i, samp_well2, 2)
            p300.touch_tip()
            p300.drop_tip()
        mag_mod.engage(height_from_base=mag_height_1)
        ctx.delay(minutes=3)
        for i, samp_well2 in enumerate(samples_second_well):
            pick_up300()
            dispense_height(0, i, samp_well2, 2)
            remove_supernatant_2(100, i, samp_well2, 1)
            p300.drop_tip()
        mag_mod.disengage()

    ctx.delay(minutes=settling_drying)
    #drying sample
    for i, samp_well2 in enumerate(samples_second_well):
        pick_up_filter()
        dispense_height(0, i, samp_well2, 2)
        dry_samp(50, i, samp_well2, 1)
    ctx.delay(minutes=settling_3)
    mag_mod.disengage()

    #Transfer
    for wash in range(1):
        pick_up_filter()
        for samp_well2, source in zip(samples_second_well, temp_rack.rows()[0][:12:1]):
            p300.aspirate(elution_vol, elution_buffer)
            dispense_height(elution_vol, i, samp_well2, 2)
        p300.drop_tip()
        for samp_well2, source in zip(samples_second_well, temp_rack.rows()[0][:12:1]):
            pick_up_filter(i, use_park = True)
            dispense_height(0, i, samp_well2, 2)
            mixing_elution(50, i, samp_well2, 1)
            p300.aspirate(200, samp_well2)
            dispense_height(200, i, source, 2)
            p300.aspirate(50, samp_well2)
            dispense_height(50, i, source, 2)
            p300.blow_out()
            p300.return_tip()
        ctx.delay(minutes=2)
        

    ctx.comment('\n\n\n')

    # elution on Aluminum block
    
    for i, (source, samp_well3) in enumerate(zip(temp_rack.rows()[0][:12:1],
                                                 samples_third_well)):
        pick_up_filter(i, use_park=True)
        mixing(elution_vol, i, source, 2)
        p300.return_tip()
    ctx.delay(minutes=2)
    for i, (source, samp_well3) in enumerate(zip(temp_rack.rows()[0][:12:1],
                                                 samples_third_well)):
        pick_up_filter(i, use_park=True)
        mixing(100, i, source, 2)
        aspirate_elution(200, i, source, 2)
        p300.dispense(200, samp_well3)
        p300.drop_tip()
    ctx.comment('\n\n\n')

    # Transfer
    mag_mod.engage(height_from_base=mag_height_1)
    ctx.delay(minutes=2)
    
    ctx.comment('\n\n\n')
    
    ctx.comment('\n\n\n')

    num_one = regular_tip_count
    num_two = filter_tip_count

    # write updated tipcount to CSV
    new_tip_count = str(num_one)+", "+str(num_two)+"\n"
    if not ctx.is_simulating():
        with open(file_path, 'w') as outfile:
            outfile.write(new_tip_count)
