#!/usr/bin/env python
'''
Plot summary of one or more shots.
'''

from __future__ import print_function, absolute_import, division
from argparse import ArgumentParser
from shotData import shotData
import signalAnalysis
import plotSettings
from hbtep.find_shots import overlaps, BadShot, get_steady_regions
from hbtep.mdsplus import read_data, signals_from_shot
from hbtep.misc import (init_logging, argparse_sensors_type, extract_equilibrium,
    calc_q, calc_r_major, BadSensor, argparse_pyfile_type, sensor_blacklist,
    true_regions, poly_fit)
from hbtep.plot import (red_green_colormap, get_color, sxr_rainbow_colormap,
    sxr_rainbow_colormap_w)
from hbtep import surfaceAnalysis
from matplotlib.colors import Normalize
from matplotlib.ticker import MaxNLocator, MultipleLocator
from matplotlib.font_manager import fontManager, FontProperties
import matplotlib.gridspec as gridspec
import matplotlib.patheffects as PathEffects
import MDSplus
import atexit
import logging
import matplotlib.pyplot as plt
import matplotlib as mpl
import numpy as np
import subprocess
import threading

import BandPassFilter as BandFilt
log = logging.getLogger('')

def parse_args(args=None):
    '''Parse command line'''

    parser = ArgumentParser()

    parser.add_argument("shotnos", metavar='<shot number>', type=int, nargs='+',
                      help="Shots to plot.  The contour plots are generated from the last shot listed")

    parser.add_argument("--end-time", metavar='<milliseconds>', type=float, default=8,
                      help="Plot this many seconds. Default %(default)s")

    parser.add_argument("--zoom-start", metavar='<milliseconds>', type=float, default=2,
                      help="The start of the zoomed window for the stripy plots")

    parser.add_argument("--zoom-end", metavar='<milliseconds>', type=float, default=4,
                      help="The end of the zoomed window for the stripy plots")

    parser.add_argument("--loop", action='store_true', default=False,
                      help="Redraw whenever hbtep2_complete MDS event is received.")

    parser.add_argument('--small',action='store_true',default=False,
                        help="Use a smaller window instead of the large display format")

    parser.add_argument('--short-title',action='store_true',default=False,
                        help="Removes the Last Shot Taken part of the title")

    parser.add_argument('--white',action='store_true',default=False,
                        help="Plots with a white background instead of black")

    parser.add_argument('--printable',action='store_true',default=False,
                        help="Creates a small plot with a white background for printing")

    parser.add_argument("--debug", action="store_true", default=False,
                      help="Activate debugging output")

    parser.add_argument("--quiet", action="store_true", default=False,
                      help="Be really quiet")

    parser.add_argument("--byrne", action="store_true", default=False,
                        help="Use Pat's subtraction method")

    parser.add_argument("--thomson", action="store_true", default=False,
                        help="Plot Te and Ne from Thomson data")

    parser.add_argument("--rot-probes", action="store_true", default=False,
                        help="Plot voltage and current of bias probe, as well as signals from Mach probe.")

    parser.add_argument("--plotsensors", action="store_true", default=False,
                        help="Plot individual magnetic sensors in separate figures")

    parser.add_argument("--no-avg-subtr", action="store_true", default=False,
                        help="Do not subtract the n=0 or m=0 components from the sensors.  This reduces the visibility of slowed modes.")

    parser.add_argument("--polynomial", action="store_true", default=False,
                        help="Use polynomial for equilibrium subtraction for contour plots")

    parser.add_argument("--plot-n1-sensors", action="store_true", default=False,
                        help="Plot the signal from subtraction of each n=1 pair of sensors.")

    parser.add_argument("--extra-fig", action="store_true", default=False,
                        help="Generate extra figure window with custom plots. Used for getting figures to show.")

    options = parser.parse_args(args)

    return options

def slope_fit(pts_cnt):
    '''Return weights to determine slope from *pts* points'''

    A = np.empty((pts_cnt, 2))
    for i in np.arange(2):
        A[:,i] = np.arange(pts_cnt)**i

    A_i = np.linalg.pinv(A)

    return A_i[1,::-1]

def setup_ax_arr(fig, numV, numH, ylim, sharey=True, hide_xlabels=True, hide_ylabels=True):
    # hide_label option only applies to subplots on the left most and bottom, other labels will always be hided.
    ax_arr = []
    for i in np.arange(numV*numH):
        if i==0:
            ax_arr.append(fig.add_subplot(numV,numH,i+1))
        else:
            if sharey:
                ax_arr.append(fig.add_subplot(numV,numH,i+1, sharex=ax_arr[0], sharey=ax_arr[0]))
            else:
                ax_arr.append(fig.add_subplot(numV,numH,i+1, sharex=ax_arr[0]))
        ax_arr[i].set_ylim(ylim[0],ylim[1])
        if (not hide_xlabels) and ( i > (numV-1)*numH):
            pass
        else:
            for label in ax_arr[i].get_xticklabels():
                label.set_visible(False)
        if (not hide_ylabels) and ( (i-1) % numH == 0):
            pass
        else:
            for label in ax_arr[i].get_yticklabels():
                label.set_visible(False)
    fig.tight_layout()
    fig.subplots_adjust(top=0.92)

    return ax_arr

def setup_axes(axes, ylim, ylabel, aligny=False, xlim=None, xlabel=None, ygrid=False, num_major_yticks=5, xtick_labels=False,
        vlines=[], hlines=[]):
    if ylim: axes.set_ylim(ylim[0], ylim[1])
    if ylabel: axes.set_ylabel(ylabel, multialignment='center')#, ha='center')
    if aligny: axes.yaxis.set_label_coords(-.3, 0.5)
    if xlim: axes.set_xlim(xlim[0], xlim[1])
    if xlabel: axes.set_xlabel(xlabel)
    if ygrid: axes.yaxis.grid(b=True)
    if num_major_yticks: axes.yaxis.set_major_locator(MaxNLocator(num_major_yticks))
    if not xtick_labels:
        for label in axes.get_xticklabels():
            label.set_visible(False)
    linecolor = 'k' if options.white else 'w'
    for vline in vlines:
        axes.axvline(vline, ls='--', color=linecolor, alpha=0.8)
    for hline in hlines:
        axes.axhline(hline, ls='-.', color=linecolor, alpha=0.8)

def get_puff_pressures(thistree):
	nodeloc_pufftrig = '.devices.basement:a14_4:input_6'
	nodeloc_iongauge = '.devices.basement:a14_04:input_5'
	voltagedivider = 2.0286    # ********************************** INCLUDES CORRECTION FOR A14 INPUT IMPEDANCE!
	t_puff_1,t_puff_2 = ((thistree.getNode('.timing:gas_puff')).data())*10**(-6)
	t_basementA14start = ((thistree.getNode('.timing.digitizer:basement_tf')).data())*10**(-6)
	t_tfbankstart = ((thistree.getNode('.timing.banks:TF_ST')).data())*10**(-6)
	p_base_times = [t_puff_1-0.001-1./60.,t_puff_1-0.001]
	p_fill_times = [t_tfbankstart-0.006-1./60.,t_tfbankstart-0.006]
	p_IG_rawsig = (thistree.getNode(nodeloc_iongauge)).data()
	t_p_IG = (thistree.getNode(nodeloc_iongauge)).dim_of().data()
	dt = t_p_IG[1]-t_p_IG[0]
	p_IG_smth = signalAnalysis.smooth(p_IG_rawsig,250)
	p_IG_proc = 10**(abs(p_IG_smth)*(voltagedivider) - 2.)
	meanbasepress = np.mean(p_IG_proc[int(round( (p_base_times[0] - t_p_IG[0])/dt ) ):int(round( (p_base_times[1] - t_p_IG[0])/dt ) )])/0.35
	meanfillpress = np.mean(p_IG_proc[int(round( (p_fill_times[0] - t_p_IG[0])/dt ) ):int(round( (p_fill_times[1] - t_p_IG[0])/dt ) )])/0.35
	return (t_puff_2-t_puff_1)*10**6,meanbasepress,meanfillpress  # in nTorr

def plot_signals(times, signals, ax_arr, color, vlines=[]):
    linecolor = 'k' if options.white else 'w'
    for (i,signal) in enumerate(signals):
        ax_arr[i].plot(times*1e3,signal, color=color)
        for line in vlines:
            ax_arr[i].axvline(line, ls='--',color=linecolor, alpha=0.8)

def plot_sensors(times, signals, ax_arr, avgSigs, fitmask, vlines=[]):
    plotcolor = 'k' if options.white else 'w'
    plot_signals(times, signals, ax_arr, plotcolor, vlines)
    sigs_temp = signals
    if not options.no_avg_subtr:
        sigs_temp = signals-avgSigs
        for i in np.arange(signals.shape[0]):
            ax_arr[i].plot(times*1e3, avgSigs, color='r')
        plot_signals(times, sigs_temp, ax_arr, plotcolor)

    sig_equil = np.zeros(sigs_temp.shape)
    sig_equil[:,fitmask] = determine_background(times[fitmask], sigs_temp[:,fitmask])

    plot_signals(times, sig_equil, ax_arr, color='r')
    plot_signals(times, sigs_temp-sig_equil, ax_arr, color='g')

def extra_fig(SSet):
    shotno = options.shotnos[0] if len(options.shotnos) == 1 else options.shotnos[-1]
    fig_ex = plt.figure(figsize=(7,12))

#    biasv_num   = 3
#    biasc_num   = 4
#    q_num       = 0
#    TAn1_num    = 1
#    FB4n1_num   = 2
#    lv_num      = 5
#    mid_sxr_num = 6
#    sxrfan_num  = 7
    fig_name_list = [\
                     'MR',
                     'q',
                     'mid_sxr',
                     'sxrfan',
                     'lv',
                     'TAn1',
                     'FB4n1',
                     'biasv',
                     'biasc',
                     'macht',
                     'machp',
                     'isat',
                     'tipEv'
                     ]

#    fig_name_list = ['q','TAn1','FB4n1','biasv','biasc','lv','mid_sxr','sxrfan','macht','machp','isat','tipEv]

    numV = len(fig_name_list)
    numH = 1
    ax_arr = setup_ax_arr(fig_ex, numV, numH, [0,1], sharey=False, hide_xlabels=False, hide_ylabels=False)
    #for label in ax_arr[numV-1].get_xticklabels():
    #    label.set_visible(True)
    fig_ex.subplots_adjust(hspace=0.04, left=0.14, right=0.96, top=0.98, bottom = 0.12)

    if 'biasv' in fig_name_list:
        biasv_num = fig_name_list.index('biasv')
        setup_axes(axes=ax_arr[biasv_num], ylim=[-125,125], ylabel='Probe\nVoltage',num_major_yticks=3)
    if 'biasc' in fig_name_list:
        biasc_num = fig_name_list.index('biasc')
        setup_axes(axes=ax_arr[biasc_num], ylim=[-10,80], ylabel='Probe\nCurrent',num_major_yticks=3)
    if 'TAn1' in fig_name_list:
        TAn1_num = fig_name_list.index('TAn1')
        setup_axes(axes=ax_arr[TAn1_num], ylim=[-19.9,19.9], ylabel='TA n=1',num_major_yticks=4)
    if 'FB4n1' in fig_name_list:
        FB4n1_num = fig_name_list.index('FB4n1')
        setup_axes(axes=ax_arr[FB4n1_num], ylim=[-19.9,19.9], ylabel='FB4 n=1',num_major_yticks=4)
    if 'lv' in fig_name_list:
        lv_num = fig_name_list.index('lv')
        setup_axes(axes=ax_arr[lv_num], ylim=[0,15], ylabel='Loop\nVoltage',num_major_yticks=4)
    if 'mid_sxr' in fig_name_list:
        mid_sxr_num = fig_name_list.index('mid_sxr')
        setup_axes(axes=ax_arr[mid_sxr_num], ylim=[0,2], ylabel='SXR\nmidplane',num_major_yticks=3)
    if 'sxrfan' in fig_name_list:
        sxrfan_num = fig_name_list.index('sxrfan')
        setup_axes(axes=ax_arr[sxrfan_num], ylim=[1,15], ylabel='SXR Fan', num_major_yticks=4)
    if 'q' in fig_name_list:
        q_num = fig_name_list.index('q')
        setup_axes(axes=ax_arr[q_num], ylim=[2.01,4], ylabel='Edge q',num_major_yticks=4)
    if 'Ip' in fig_name_list:
        Ip_num = fig_name_list.index('Ip')
        setup_axes(axes = ax_arr[Ip_num], ylim=[0,20], ylabel = 'Ip (kA)', num_major_yticks = 4)
    if 'MR' in fig_name_list:
        MR_num = fig_name_list.index('MR')
        setup_axes(axes = ax_arr[MR_num], ylim = [90,97], ylabel = 'MR (cm)', num_major_yticks = 4)
    if 'macht' in fig_name_list:
        macht_num = fig_name_list.index('macht')
        setup_axes(axes=ax_arr[macht_num], ylim=[-0.5,0.5], ylabel='Mach Tor',num_major_yticks=4, ygrid=True)
    if 'machp' in fig_name_list:
        machp_num = fig_name_list.index('machp')
        setup_axes(axes=ax_arr[machp_num], ylim=[-0.5,0.5], ylabel='Mach Pol',num_major_yticks=4, ygrid=True)
    if 'isat' in fig_name_list:
        isat_num = fig_name_list.index('isat')
        setup_axes(axes=ax_arr[isat_num], ylim=[-10,150], ylabel='$I_{sat}$',num_major_yticks=4)
    if 'tipEv' in fig_name_list:
        tipEv_num = fig_name_list.index('tipEv')
        setup_axes(axes=ax_arr[tipEv_num], ylim=[-60,40], ylabel='$V_{tipE}$',num_major_yticks=4)

    if (len(ax_arr) != 0):
        ax_arr[0].set_xlim([options.zoom_start,options.zoom_end])

    for i,label in enumerate(ax_arr[numV-1].get_xticklabels()):
        if i != 0:
            label.set_visible(True)
    ax_arr[numV-1].set_xlabel('Time (ms)')
    shot_data = shotData(shotno, startTime=options.zoom_start, endTime=options.zoom_end, lowPassMach=6.0)

    if 'biasv' in fig_name_list:
        shot_data.bias_v.plot(ax=ax_arr[biasv_num], color='k')

    if 'biasc' in fig_name_list:
        shot_data.bias_cur.plot(ax=ax_arr[biasc_num], color='k')

    if 'macht' in fig_name_list:
        (times, tipA_c) = read_data(tree, '.sensors.mach_probe:tipa_c', t_start=0, t_end=options.end_time)
        (times, tipB_c) = read_data(tree, '.sensors.mach_probe:tipb_c', t_start=0, t_end=options.end_time)
        (times, tipC_c) = read_data(tree, '.sensors.mach_probe:tipc_c', t_start=0, t_end=options.end_time)
        (times, tipD_c) = read_data(tree, '.sensors.mach_probe:tipd_c', t_start=0, t_end=options.end_time)
        tipA_c = BandFilt.LowPassProcess(tipA_c, times, order=1, freq=5.0e3)
        tipB_c = BandFilt.LowPassProcess(tipB_c, times, order=1, freq=5.0e3)
        tipC_c = BandFilt.LowPassProcess(tipC_c, times, order=1, freq=5.0e3)
        tipD_c = BandFilt.LowPassProcess(tipD_c, times, order=1, freq=5.0e3)
        tipB_c*=0.629
        tipC_c*=0.964
        tipD_c*=1.324
        machT = .5*np.log((tipB_c + tipC_c) / (tipA_c + tipD_c))
        machT_mask = np.isfinite(machT)
        ax_arr[macht_num].plot(times*1e3, machT, color='k')

    if 'machp' in fig_name_list:
        (times, tipA_c) = read_data(tree, '.sensors.mach_probe:tipa_c', t_start=0, t_end=options.end_time)
        (times, tipB_c) = read_data(tree, '.sensors.mach_probe:tipb_c', t_start=0, t_end=options.end_time)
        (times, tipC_c) = read_data(tree, '.sensors.mach_probe:tipc_c', t_start=0, t_end=options.end_time)
        (times, tipD_c) = read_data(tree, '.sensors.mach_probe:tipd_c', t_start=0, t_end=options.end_time)
        tipA_c = BandFilt.LowPassProcess(tipA_c, times, order=1, freq=5.0e3)
        tipB_c = BandFilt.LowPassProcess(tipB_c, times, order=1, freq=5.0e3)
        tipC_c = BandFilt.LowPassProcess(tipC_c, times, order=1, freq=5.0e3)
        tipD_c = BandFilt.LowPassProcess(tipD_c, times, order=1, freq=5.0e3)
        tipB_c*=0.629
        tipC_c*=0.964
        tipD_c*=1.324
        machP = .5*np.log((tipC_c + tipD_c) / (tipA_c + tipB_c))
        machP_mask = np.isfinite(machP)
        ax_arr[machp_num].plot(times*1e3, machP, color='k')

    if 'isat' in fig_name_list:
        (times, isat) = read_data(tree, '.sensors.mach_probe:tipe_c', t_start=0, t_end=options.end_time)
        isat = BandFilt.LowPassProcess(isat, times, order=1, freq=5.0e3)
        ax_arr[isat_num].plot(times*1e3, isat*1e3, color='k')

    if 'tipEv' in fig_name_list:
        (times, tipEv) = read_data(tree, '.sensors.mach_probe:tipe_v', t_start=0, t_end=options.end_time)
        tipEv = BandFilt.LowPassProcess(tipEv, times, order=1, freq=5.0e3)
        ax_arr[tipEv_num].plot(times*1e3, tipEv, color='k')

    if 'lv' in fig_name_list:
        (times, lv) = read_data(tree, '.devices.west_rack:cpci:input_69', t_start=0,
                                   t_end=options.end_time)
        lvmask = times < oh_bias_trig
        lv = -lv*101.0
        lv -= lv[lvmask].mean()
        ax_arr[lv_num].plot(times*1e3, lv, color='k')

    if 'TAn1' in fig_name_list:
        (times, TA01) = read_data(tree, '.sensors.magnetic:TA01_S1P', t_start=0, t_end=options.end_time)
        (times, TA09) = read_data(tree, '.sensors.magnetic:TA03_S3P', t_start=0, t_end=options.end_time)
        (times, TA17) = read_data(tree, '.sensors.magnetic:TA06_S2P', t_start=0, t_end=options.end_time)
        (times, TA25) = read_data(tree, '.sensors.magnetic:TA09_S1P', t_start=0, t_end=options.end_time)
        plot_mask = (times > options.zoom_start*1e-3) & (times < options.zoom_end*1e-3)
        TAn1_sin = (TA01 - TA17)*1e4
        #TAn1_sin -= np.average(TAn1_sin)
        TAn1_sin -= np.average(TAn1_sin[plot_mask])
        TAn1_cos = (TA09 - TA25)*1e4
        TAn1_cos -= np.average(TAn1_cos[plot_mask])
        ax_arr[TAn1_num].plot(times*1e3, TAn1_sin, color='k')
        ax_arr[TAn1_num].plot(times*1e3, TAn1_cos, color='b')

    if 'FB4n1' in fig_name_list:
        (times, FB01) = read_data(tree, '.sensors.magnetic:FB01_S1P', t_start=0, t_end=options.end_time)
        (times, FB04) = read_data(tree, '.sensors.magnetic:FB03_S1P', t_start=0, t_end=options.end_time)
        (times, FB06) = read_data(tree, '.sensors.magnetic:FB06_S1P', t_start=0, t_end=options.end_time)
        (times, FB09) = read_data(tree, '.sensors.magnetic:FB08_S1P', t_start=0, t_end=options.end_time)
        plot_mask = (times > options.zoom_start*1e-3) & (times < options.zoom_end*1e-3)
        FBn1_sin = (FB01 - FB06)*1e4
        #TAn1_sin -= np.average(TAn1_sin)
        FBn1_sin -= np.average(FBn1_sin[plot_mask])
        FBn1_cos = (FB04 - FB09)*1e4
        FBn1_cos -= np.average(FBn1_cos[plot_mask])
        ax_arr[FB4n1_num].plot(times*1e3, FBn1_sin, color='k')
        ax_arr[FB4n1_num].plot(times*1e3, FBn1_cos, color='b')

    if 'mid_sxr' in fig_name_list:
        (times, sxr) = read_data(tree, '.devices.north_rack:cpci:input_74', t_start=0,
                                t_end=options.end_time)
        sxrmask = times < oh_bias_trig
        sxr -= sxr[sxrmask].mean()
        sxr = -sxr
        ax_arr[mid_sxr_num].plot(times*1e3, sxr, color='k')

    if 'sxrfan' in fig_name_list:
        for i in np.arange(15):
            (times, sxr_ch) = read_data(tree, '.devices.west_rack:cpci:input_%02d' %(i+74), t_start=0,
                                t_end=options.end_time)

            if i == 0:
                sxr_array = np.empty((15, len(times)))

            sxr_array[i,:]=sxr_ch

        if shotno < 76879:
            sxr_array[9,:] *=0
            sxr_array[13,:] *=0
            sxr_array[14,:] *=0
        elif shotno > 79898:
            sxr_array[0,:] /= 8
            sxtemp=np.copy(sxr_array[9,:])
            sxr_array[9,:]=np.copy(sxr_array[14,:])
            sxr_array[14,:]=np.copy(sxtemp)
        else:
            sxtemp=np.copy(sxr_array[9,:])
            sxr_array[9,:]=np.copy(sxr_array[14,:])
            sxr_array[14,:]=np.copy(sxtemp)

        avemask = times > 1.5e-3
        plot_mask = (times > options.zoom_start*1e-3) & (times < options.zoom_end*1e-3)
        sig_max = np.abs(sxr_array[:,plot_mask]).max()
        sig_min = 0.03*sig_max
        if options.white:
            ax_arr[sxrfan_num].contourf(times*1e3,np.arange(15)+1,sxr_array,128,cmap=sxr_rainbow_colormap_w(128),
                norm=Normalize(vmin=sig_min, vmax=sig_max))
        else:
            ax_arr[sxrfan_num].contourf(times*1e3,np.arange(15)+1,sxr_array,128,cmap=sxr_rainbow_colormap(128),
                norm=Normalize(vmin=sig_min, vmax=sig_max))

    if 'q' in fig_name_list:
        q_num = fig_name_list.index('q')
        (times, r) = calc_r_major(tree, t_start=-.001, t_end=options.end_time, byrne = options.byrne)
        rmask = ~np.isnan(r)
        q = calc_q(tree, times, r_major=r, byrne = options.byrne)
        qmask = ~np.isnan(q)
        ax_arr[q_num].plot(times[qmask]*1e3, q[qmask], color='k')
        ax_arr[q_num].axhline(3,ls='-.',color='k')

    if 'Ip' in fig_name_list:
        ip_num = fig_name_list.index('Ip')
        (times, ip) = read_data(tree, '.sensors.rogowskis:ip', t_start=0,
                            t_end=options.end_time)
        ax_arr[ip_num].plot(times*1e3, ip/1e3, label='%d' % (shotno), color='k')

    if 'MR' in fig_name_list:
        MR_num = fig_name_list.index('MR')
        (times, r) = calc_r_major(tree, t_start=-.001, t_end=options.end_time, byrne = options.byrne)
        rmask = ~np.isnan(r)
        ax_arr[MR_num].plot(times[rmask]*1e3, r[rmask]*100, color='k')
        ax_arr[MR_num].axhline(92,ls='-.',color = 'k')

    if 'Ip' in fig_name_list:
        ip_num = fig_name_list.index('Ip')
        ax_arr[ip_num].legend(loc = 'best')

def plot_shot(shotno, color, shot_data, SSet, ip_ax, r_ax, q_ax, oh_ax, amp_ax, freq_ax, lv_ax, sp_ax, sxr_ax, last_shot,
                sxfan_ax, tap_ax, pa1_ax, pa2_ax, fb4_ax, Te_ax, Ne_ax, biasv_ax, biasc_ax, machT_ax, machP_ax, isat_ax, tipEv_ax, TAax_arr,
                PA1ax_arr, PA2ax_arr, FBax_arr, TA_n1ax_arr, FB_n1ax_arr):

    if last_shot:
        color='k' if options.white else 'w'

    tree = MDSplus.Tree('hbtep2', shotno)
    pufftime,pbase,pfill = get_puff_pressures(tree)
    del tree
    if not options.short_title:
        ip_ax.plot(shot_data.times*1e3, shot_data.Ip.data, label='%d, $p_{F}$ = %d$\mu$T' % (shot_data.shotno,pfill/1000.), color=color)
    else:
        ip_ax.plot(shot_data.times*1e3, shot_data.Ip.data, label='%d' % (shot_data.shotno), color=color)
        #ip_ax.plot(times*1e3, ip/1e3, label='%d' % shotno, color=color)

    shot_data.OH_cur.plot(ax=oh_ax, color=color, multiplier=1e-3)

    #(times, vf) = read_data(tree, '.sensors:vf_current', t_start=0,
    #                        t_end=options.end_time)
    #vf_ax.plot(times*1e3, vf*1e-3, color=color)

    if shot_data.Ip.data.max() > 10:
        shot_data.MR.plot(ax=r_ax, color=color, multiplier=1e2)

    if shot_data.Ip.data.max() > 10:
        shot_data.q.plot(ax=q_ax, color=color)

    if options.rot_probes:
        try:
            shot_data.bias_v.plot(ax=biasv_ax, color=color)
            shot_data.bias_cur.plot(ax=biasc_ax, color=color)

            shot_data.machT.plot(ax=machT_ax, color=color)
            shot_data.machP.plot(ax=machP_ax, color=color)
            shot_data.isat.plot(ax=isat_ax, color=color, multiplier=1e3)
            shot_data.tipE_v.plot(ax=tipEv_ax, color=color)
        except:
            if options.debug:
                raise
            pass

#    (times, m3_sin) = read_data(tree, '.sensors.rogowskis:sin_3a', t_start=0,
#                           t_end=options.end_time)
#    m3_cos = read_data(tree, '.sensors.rogowskis:cos_3a', times=times)
#    # m3_cos is zeroed out because the channel is being used by the sxr array
#    m3 = smoothe(np.sqrt((m3_sin)**2 + (m3_cos*0)**2), int(options.smoothe // (times[1]-times[0])))
#    m3_ax.plot(times*1e3, m3, color=color)

    #tf = read_data(tree, '.sensors:tf_probe', times=times)
    #tf *= tree.getNode('.sensors:tf_probe:mr').data() / 0.92
    #tf_ax.plot(times*1e3, tf, color=color)

    shot_data.LV.plot(ax=lv_ax, color=color)

    if shotno >= 70000:
        shot_data.DAlpha.plot(ax=sp_ax, color=color)
        if last_shot:
            spec_max = np.amax(shot_data.DAlpha.data)
            sp_ax.set_ylim(0,1.2*spec_max)

    shot_data.sxrmid.plot(ax=sxr_ax, color=color)

    if amp_ax is not None:
        # Mode amplitude and frequency using feedback sensors 4
        shot_data.FBamp.plot(ax=amp_ax, color=color)
        shot_data.FBfreq.plot(ax=freq_ax, color=color)

    if options.thomson:
        Te = []
        Ne = []
        locations = []
        tree = MDSplus.Tree('hbtep2', shotno)
        for polyNum in range(1,11):
            try:
                TeNode = tree.getNode('sensors.thomson.poly_{0:02d}:Te'.format(polyNum))
                NeNode = tree.getNode('sensors.thomson.poly_{0:02d}:Ne'.format(polyNum))
                locNode = tree.getNode('sensors.thomson.poly_{0:02d}:location'.format(polyNum))
                Te.append(TeNode.data())
                Ne.append(NeNode.data())
                locations.append(locNode.data())
            except:
                continue
        Te_ax.scatter(locations, Te, color=color)
        Ne_ax.scatter(locations, Ne, color=color)
        laserTime = tree.getNode('.timing.thomson:fire').data()/1000
        r_ax.axvline(laserTime, ls='--', color=color, alpha=0.8)

    if options.plot_n1_sensors:
        tree = MDSplus.Tree('hbtep2', shotno)
        ta_sensors = argparse_sensors_type('TA*p')
        ta_sensors = SSet.filterBadSensors(ta_sensors)

        (times, tasigs) = signals_from_shot(tree, [ '.sensors.magnetic:%s' % x for x in ta_sensors],
                    t_start=0, t_end=options.end_time)

        n1_subtr_sigs = np.zeros(tasigs[:15,:].shape)
        for i in np.arange(5):
            for j in np.arange(3):
                if 'TA{0:02}_S{1}P'.format(i+1,j+1) in ta_sensors and 'TA{0:02}_S{1}P'.format(i+6,j+1) in ta_sensors:
                    n1_subtr_sigs[3*i+j,:] = tasigs[3*i+j,:] - tasigs[3*(i+5)+j,:]

        plot_signals(times, n1_subtr_sigs, TA_n1ax_arr, color=color)

        fb_sensors = argparse_sensors_type('FB*p')
        (times, fbsigs) = signals_from_shot(tree, [ '.sensors.magnetic:%s' % x for x in fb_sensors],
                    t_start=0, t_end=options.end_time)
        fb_sensors = SSet.filterBadSensors(fb_sensors)

        n1_subtr_sigs = np.zeros(fbsigs[:20,:].shape)
        for i in np.arange(5):
            for j in np.arange(4):
                if 'FB{0:02}_S{1}P'.format(i+1,j+1) in fb_sensors and 'FB{0:02}_S{1}P'.format(i+6,j+1) in fb_sensors:
                    n1_subtr_sigs[4*i+j,:] = fbsigs[4*i+j,:] - fbsigs[4*(i+5)+j,:]

        plot_signals(times, n1_subtr_sigs, FB_n1ax_arr, color=color)

    if not last_shot:
        return

    # SXR array
    times = shot_data.times
    avemask = times > 1.5e-3
    sig_max = np.abs(shot_data.sxrfan.data[:,avemask]).max()
    sig_min = 0.03*sig_max
    if options.white:
        sxfan_ax.contourf(times*1e3,np.arange(15)+1,shot_data.sxrfan.data,128,cmap=sxr_rainbow_colormap_w(128),
            norm=Normalize(vmin=sig_min, vmax=sig_max))
    else:
        sxfan_ax.contourf(times*1e3,np.arange(15)+1,shot_data.sxrfan.data,128,cmap=sxr_rainbow_colormap(128),
            norm=Normalize(vmin=sig_min, vmax=sig_max))

    windowLen=720
    fitExtend=0.7
    fitmask = (times > (options.zoom_start - fitExtend)/1000) & (times < (options.zoom_end + fitExtend)/1000)
    # Settings for plotsensors
    plotsensors_color = 'k' if options.white else 'w'
    vline_color = '#888888'

    # Toroidal Array ----------------------------------------------------------------------
    shot_data.TAp.plotContour(startTime=options.zoom_start, endTime=options.zoom_end, ax=tap_ax, windowLen=windowLen, fitExtend=fitExtend)

    if options.plotsensors:
        plot_sensors(times, signals = tasigs[[i for i in np.arange(len(ta_sensors)) if 'S2P' not in ta_sensors[i]]],
                ax_arr = TAax_arr, avgSigs = np.average(tasigs, axis=0), fitmask=fitmask, vlines=[options.zoom_start, options.zoom_end])

    # Poloidal Array 1  -----------------------------------------------------------------------
    shot_data.PA1p.plotContour(startTime=options.zoom_start, endTime=options.zoom_end, ax=pa1_ax, windowLen=windowLen, fitExtend=fitExtend)

    if options.plotsensors:
        plot_sensors(times, signals = pa1sigs[0::2],
                ax_arr = PA1ax_arr, avgSigs = np.average(pa1sigs, axis=0), fitmask=fitmask, vlines=[options.zoom_start, options.zoom_end])

    # Poloidal Array 2  -----------------------------------------------------------------------
    shot_data.PA2p.plotContour(startTime=options.zoom_start, endTime=options.zoom_end, ax=pa2_ax, windowLen=windowLen, fitExtend=fitExtend)

    if options.plotsensors:
        plot_sensors(times, signals = pa2sigs[0::2],
                ax_arr = PA2ax_arr, avgSigs = np.average(pa2sigs, axis=0), fitmask=fitmask, vlines=[options.zoom_start, options.zoom_end])

    # Feedback Sensors Poloidal 4 -----------------------------------------------------------------------
    shot_data.FB4p.plotContour(startTime=options.zoom_start, endTime=options.zoom_end, ax=fb4_ax, windowLen=windowLen, fitExtend=fitExtend)

    if options.plotsensors:
        for j in np.arange(4):
            plot_fb_sensors = argparse_sensors_type('FB*{0}p'.format(j+1))
            plot_fb_sensors = SSet.filterBadSensors(plot_fb_sensors)
            (times, plot_fbsigs) = signals_from_shot(tree, [ '.sensors.magnetic:%s' % x for x in plot_fb_sensors],
                    t_start=0, t_end=options.end_time)

            plot_sensors(times, signals = plot_fbsigs[0::2,:], ax_arr = FBax_arr[5*j:5*j+5],
                    avgSigs = np.average(plot_fbsigs, axis=0), fitmask=fitmask, vlines=[options.zoom_start, options.zoom_end])

def main(SSet, shot_data_dict, fig, figTA, figPA1, figPA2, figFB, figTA_n1, figFB_n1):
    bw_color = 'k' if options.white else 'w'
    #tf_ax = fig.add_subplot(1,3,1)
    #tf_ax.set_ylabel('TF at 0.92 (T)')
    #tf_ax.yaxis.set_major_locator(MaxNLocator(4))
    #for label in tf_ax.get_xticklabels():
    #    label.set_visible(False)

    #Setup grid of plots using matplotlib.GridSpec
    cols = [None] * 3
    cols_nrows = [None] * 3 # number of rows in each column
    cols_nrows[0] = 5
    if options.short_title:
        #fig.suptitle('Shot '+str(options.shotnos[-1]), fontsize=20)
        grid_top = 0.91
        cols_nrows[0]-=1
        col1_shift = 1
    else:
        grid_top = 0.97
        col1_shift=0

    if options.thomson:
        cols_nrows[0]+=2
        col1_row_ratios=[]
        for i in xrange(col1_num_rows+1):
            col1_row_ratios.append(4)
        col1_row_ratios.append(1)
        col1_row_ratios.append(3)
        cols[0] = gridspec.GridSpec(cols_nrows[0], 6, height_ratios=col1_row_ratios)
        Te_ax = fig.add_subplot(cols[0][-1,:3])
        Ne_ax = fig.add_subplot(cols[0][-1,3:])
    else:
        cols[0] = gridspec.GridSpec(cols_nrows[0], 6)

    cols[1] = gridspec.GridSpec(5, 6)
    cols[2] = gridspec.GridSpec(5, 6)

    if not options.short_title:
        shot_info_ax = fig.add_subplot(cols[0][0,:3])
    else:
        shot_info_ax = None

    tap_ax = fig.add_subplot(cols[0][1-col1_shift,:])

    pa1_ax = fig.add_subplot(cols[0][2-col1_shift,:])

    pa2_ax = fig.add_subplot(cols[0][3-col1_shift,:])

    fb4_ax = fig.add_subplot(cols[0][4-col1_shift,:])

    ip_ax = fig.add_subplot(cols[1][0,:])

    r_ax = fig.add_subplot(cols[1][1,:], sharex=ip_ax)

    q_ax = fig.add_subplot(cols[1][2,:], sharex=ip_ax)

    amp_ax = fig.add_subplot(cols[1][3,:], sharex=ip_ax)

    freq_ax = fig.add_subplot(cols[1][4,:], sharex=ip_ax)

    sp_ax = fig.add_subplot(cols[2][0,:], sharex=ip_ax)

    sxr_ax = fig.add_subplot(cols[2][1,:], sharex=ip_ax)

    sxfan_ax = fig.add_subplot(cols[2][2,:], sharex=ip_ax)

    lv_ax = fig.add_subplot(cols[2][3,:], sharex=ip_ax)

    oh_ax = fig.add_subplot(cols[2][4,:], sharex=ip_ax)

    m3_ax = None

    if options.rot_probes:
        cols.append(gridspec.GridSpec(6, 6))
        cols[0].update(left=0.06, right=0.26, top=grid_top, bottom=0.06, wspace=0.05, hspace=0.02)
        cols[1].update(left=0.31, right=0.50, top=grid_top, bottom=0.06, wspace=0.05, hspace=0.02)
        cols[2].update(left=0.55, right=0.74, top=grid_top, bottom=0.06, wspace=0.05, hspace=0.02)
        cols[3].update(left=0.79, right=0.98, top=grid_top, bottom=0.06, wspace=0.05, hspace=0.02)

        biasv_ax = fig.add_subplot(cols[3][0,:], sharex=ip_ax)
        biasc_ax = fig.add_subplot(cols[3][1,:], sharex=ip_ax)
        machT_ax = fig.add_subplot(cols[3][2,:], sharex=ip_ax)
        machP_ax = fig.add_subplot(cols[3][3,:], sharex=ip_ax)
        isat_ax = fig.add_subplot(cols[3][4,:], sharex=ip_ax)
        tipEv_ax = fig.add_subplot(cols[3][5,:], sharex=ip_ax)
    else:
        cols[0].update(left=0.06, right=0.32, top=grid_top, bottom=0.06, wspace=0.05, hspace=0.02)
        cols[1].update(left=0.38, right=0.65, top=grid_top, bottom=0.06, wspace=0.05, hspace=0.02)
        cols[2].update(left=0.71, right=0.98, top=grid_top, bottom=0.06, wspace=0.05, hspace=0.02)
        biasv_ax = None
        biasc_ax = None
        machT_ax = None
        machP_ax = None
        isat_ax = None
        tipEv_ax = None

    current_shot = MDSplus.Tree.getCurrent('hbtep2')

    if not options.short_title:
        shot_info_ax.set_frame_on(False)
        shot_info_ax.axes.get_yaxis().set_visible(False)
        shot_info_ax.axes.get_xaxis().set_visible(False)
        current_shot_name = shot_info_ax.text(0.3, 0.75, current_shot, fontsize=70, color='b', va="top", ha="center")
        if options.white:
            shot_info_ax.text(0.3, 0.8, "Last shot taken:", fontsize=24, color='k', va="bottom", ha="center")
            current_shot_name.set_path_effects([PathEffects.withStroke(linewidth=3, foreground="k")])
        else:
            shot_info_ax.text(0.3, 0.8, "Last shot taken:", fontsize=24, color='w', va="bottom", ha="center")
            current_shot_name.set_path_effects([PathEffects.withStroke(linewidth=3, foreground="w")])

    setup_axes(axes=ip_ax, ylim=[0,20], ylabel='Plasma Current (kA)',xlim=[0,options.end_time*1000],  num_major_yticks=4,
            vlines=[options.zoom_start, options.zoom_end])

    setup_axes(axes=r_ax, ylim=[88,98], ylabel='Major Radius (cm)', num_major_yticks=4,
            vlines=[options.zoom_start, options.zoom_end],
            hlines=[90,92])

    setup_axes(axes=q_ax, ylim=[2,5.1], ylabel='Safety Factor',  num_major_yticks=4,
            vlines=[options.zoom_start, options.zoom_end],
            hlines=[3,])

    if amp_ax: setup_axes(axes=amp_ax, ylim=[0,3], ylabel='Mode Amplitude', num_major_yticks=4,
            vlines=[options.zoom_start, options.zoom_end],
            hlines=[0,])

    if freq_ax: setup_axes(axes=freq_ax, ylim=[-2,10], ylabel='Mode Frequency (kHz)', xlabel='Time (ms)', xtick_labels=True, num_major_yticks=4,
            vlines=[options.zoom_start, options.zoom_end],
            hlines=[0,])

    if biasv_ax: setup_axes(axes=biasv_ax, ylim=[-125,125], ylabel='Bias Probe Voltage', num_major_yticks=5,
            vlines=[options.zoom_start, options.zoom_end],
            hlines=[0,])

    if biasc_ax: setup_axes(axes=biasc_ax, ylim=[-20,90], ylabel='Bias Probe Current', xlabel='Time (ms)', xtick_labels=True, num_major_yticks=5,
            vlines=[options.zoom_start, options.zoom_end],
            hlines=[0,])

    if machT_ax: setup_axes(axes=machT_ax, ylim=[-0.5,0.5], ylabel='Mach Tor.', num_major_yticks=5,
            vlines=[options.zoom_start, options.zoom_end],
            hlines=[0,])

    if machP_ax: setup_axes(axes=machP_ax, ylim=[-0.5,0.5], ylabel='Mach Pol.', num_major_yticks=5,
            vlines=[options.zoom_start, options.zoom_end],
            hlines=[0,])

    if isat_ax: setup_axes(axes=isat_ax, ylim=[-10,150], ylabel='Isat current', num_major_yticks=5,
            vlines=[options.zoom_start, options.zoom_end],
            hlines=[0,])

    if machP_ax: setup_axes(axes=tipEv_ax, ylim=[-60,40], ylabel='tipE V', num_major_yticks=5,
            vlines=[options.zoom_start, options.zoom_end],
            hlines=[0,])

    setup_axes(axes=lv_ax, ylim=[0,15], ylabel='Loop Voltage', num_major_yticks=4,
            vlines=[options.zoom_start, options.zoom_end])

    setup_axes(axes=sp_ax, ylim=[0,0.20], ylabel='Spectrometer', num_major_yticks=4,
            vlines=[options.zoom_start, options.zoom_end])

    setup_axes(axes=sxr_ax, ylim=[0,3.5], ylabel='Soft X-ray\nMidplane', num_major_yticks=4,
            vlines=[options.zoom_start, options.zoom_end])

    if m3_ax: setup_axes(axes=m3_ax, ylim=[0,5], ylabel='m=3 Rogowski', num_major_yticks=4,
            vlines=[options.zoom_start, options.zoom_end])

    #if vf_ax: setup_axes(axes=vf_ax, ylim=None, ylabel='VF Current (kA)', num_major_yticks=4)

    setup_axes(axes=oh_ax, ylim=None, ylabel='OH Current (kA)', xlabel='Time (ms)', xtick_labels=True, num_major_yticks=4,
            vlines=[options.zoom_start, options.zoom_end])

    setup_axes(axes=sxfan_ax, ylim=None, ylabel='SXR Fan Array', num_major_yticks=None)

    tap_ax.set_ylabel('TAp (degrees)')
    fluct_shot = options.shotnos[-1]
    if fluct_shot <= 0:
        fluct_shot += current_shot

    if options.short_title:
        pass
        #tap_ax.set_title('Poloidal Sensor Fluctuations for Shot '+str(fluct_shot),fontsize=20)
    else:
        #tap_ax.set_title('Poloidal Sensor Fluctuations for Shot '+str(fluct_shot),fontsize=20)
        tap_ax.yaxis.set_major_locator(MaxNLocator(4))

    for label in tap_ax.get_xticklabels():
        label.set_visible(False)

    pa1_ax.set_ylabel('PA1p (degrees)')
    pa1_ax.yaxis.set_major_locator(MaxNLocator(4))
    for label in pa1_ax.get_xticklabels():
        label.set_visible(False)

    pa2_ax.set_ylabel('PA2p (degrees)')
    pa2_ax.yaxis.set_major_locator(MaxNLocator(4))
    for label in pa2_ax.get_xticklabels():
        label.set_visible(False)

    fb4_ax.set_ylabel('FB4p (degrees)')
    fb4_ax.set_xlabel('Time (ms)')
    fb4_ax.yaxis.set_major_locator(MaxNLocator(4))
    fb4_ax.xaxis.set_major_locator(MaxNLocator(4))

    if options.thomson:
        #locMajorLocator = MultipleLocator(4)
        TeMajorLocator = MultipleLocator(50)
        TeMinorLocator = MultipleLocator(25)
        NeMajorLocator = MultipleLocator(2)
        Te_ax.set_ylabel('Te (eV)')
        Te_ax.set_xlabel('Radius (cm)')
        Te_ax.set_ylim(0,200)
        Te_ax.set_xlim(78,106)
        #Te_ax.xaxis.set_major_locator(locMajorLocator)
        Te_ax.yaxis.set_major_locator(TeMajorLocator)
        Te_ax.yaxis.set_minor_locator(TeMinorLocator)
        #Te_ax.set_yticks(np.arange(0,175,25))
        Te_ax.yaxis.grid(b=True, which='minor')
        #Te_ax.xaxis.grid(b=True)
        Ne_ax.set_ylabel('Ne (arb)')
        Ne_ax.set_xlabel('Radius (cm)')
        Ne_ax.set_ylim(0,10)
        Ne_ax.set_xlim(78,106)
        Ne_ax.yaxis.tick_right()
        Ne_ax.yaxis.set_label_position("right")
        #Ne_ax.xaxis.set_major_locator(locMajorLocator)
        Ne_ax.yaxis.set_major_locator(NeMajorLocator)
        #Ne_ax.set_yticks(np.arange(0,12,2))
        Ne_ax.yaxis.grid(b=True)

    TAax_arr=None
    PA1ax_arr=None
    PA2ax_arr=None
    FBax_arr=None
    if options.plotsensors:
        TAax_arr = setup_ax_arr(figTA, numV=5, numH=4, ylim=[-0.01,0.03])
        PA1ax_arr = setup_ax_arr(figPA1, numV=4, numH=4, ylim=[-0.01,0.03])
        PA2ax_arr = setup_ax_arr(figPA2, numV=4, numH=4, ylim=[-0.01,0.03])
        FBax_arr = setup_ax_arr(figFB, numV=4, numH=5, ylim=[-0.01,0.03])

    TA_n1ax_arr=None
    FB_n1ax_arr=None
    if options.plot_n1_sensors:
        TA_n1ax_arr = setup_ax_arr(figTA_n1, numV=4, numH=4, ylim=[-0.01,0.01])
        FB_n1ax_arr = setup_ax_arr(figFB_n1, numV=4, numH=5, ylim=[-0.01,0.01])

    # Clean up saved shot data
    current_shotnos = [ shotno + current_shot if shotno <= 0 else shotno for shotno in options.shotnos ]
    for prev_shotnum in shot_data_dict.keys():
        if prev_shotnum not in current_shotnos:
            del shot_data_dict[prev_shotnum]

    for (i, shotno) in enumerate(options.shotnos):
        if shotno <= 0:
            shotno += current_shot

        last_shot=1 if i == len(options.shotnos)-1 else 0

        log.info('Processing %d...', shotno)
        try:
            if shotno not in shot_data_dict:
                start_time = 0.0
                shot_data_dict[shotno] = shotData(shotno, start_time, options.end_time, lowPassMach=6.0)
            shot_data = shot_data_dict[shotno]

            idx = len(options.shotnos)-2-i
            color = bw_color if last_shot else get_color(idx)
            #tree = MDSplus.Tree('hbtep2', shotno)
            #plot_tree_data(oh_ax, tree, '.sensors:oh_current', color, coeff=1e-3)
            plot_shot(shotno, get_color(idx), shot_data, SSet, ip_ax, r_ax, q_ax, oh_ax, amp_ax, freq_ax, lv_ax, sp_ax, sxr_ax, last_shot,
                sxfan_ax if last_shot else None,
                tap_ax if last_shot else None,
                pa1_ax if last_shot else None,
                pa2_ax if last_shot else None,
                fb4_ax if last_shot else None,
                Te_ax if options.thomson else None,
                Ne_ax if options.thomson else None,
                biasv_ax if options.rot_probes else None,
                biasc_ax if options.rot_probes else None,
                machT_ax if options.rot_probes else None,
                machP_ax if options.rot_probes else None,
                isat_ax if options.rot_probes else None,
                tipEv_ax if options.rot_probes else None,
                TAax_arr, PA1ax_arr, PA2ax_arr, FBax_arr, TA_n1ax_arr, FB_n1ax_arr)
        except BadShot as exc:
            log.debug(str(exc))
            continue
        except BadSensor as exc:
            log.warn("Shot %d seems to have bad sensors, not calculating perturbation amplitude.", shotno)
            log.warn('Worst sensor: %s', options.eq_sensors[exc.index])
            continue
        except Exception as exc:
            log.exception('Error processing shot %d, skipping', shotno)
            continue

    handles, labels = ip_ax.get_legend_handles_labels()
    if options.small:
        #legfont = FontProperties(size='medium')
        ip_ax.legend(handles[::-1], labels[::-1], 'lower right')#, prop=legfont)
    else:
        #legfont = FontProperties(size='x-small')
        ip_ax.legend(handles[::-1], labels[::-1], bbox_to_anchor=(-.20,1.12))#, prop=legfont)
#    ip_ax.legend(handles[::-1], labels[::-1], bbox_to_anchor=(1.01,0.67), prop=legfont, ncol=1 )
#    else:
#        ip_ax.legend(handles[::-1], labels[::-1], 'lower right')
#    ip_ax.legend(handles[::-1], labels[::-1], bbox_to_anchor=(1.01,0.67), prop=legfont, ncol=1 )

if __name__ == '__main__':
    options = parse_args()
    init_logging(options.quiet, options.debug)

    if not options.debug:
        np.seterr(all='ignore')
    # Put end_time in seconds
    options.end_time /= 1000
    if options.printable:
        options.small = True
        options.white = True

    if options.plotsensors or options.plot_n1_sensors:
        options.small = True

    if options.small:
        options.short_title = True
        mpl.rcParams.update({'font.size': 20})
        mpl.rcParams.update({'legend.fontsize': 14})
    else:
        mpl.rcParams.update({'font.size': 20})
        mpl.rcParams.update({'legend.fontsize': 14})

    if options.white:
        mpl.rcParams.update({'figure.facecolor': 'w'})
        #mpl.rcParams['axes.color_cycle'] = get_color_cycle(len(options.shotnos),reverse=True)
    else:
        mpl.rcParams.update({'text.color': 'w'})
        mpl.rcParams.update({'axes.facecolor': 'k'})
        mpl.rcParams.update({'axes.edgecolor': 'w'})
        mpl.rcParams.update({'axes.labelcolor': 'w'})
        mpl.rcParams.update({'xtick.color': 'w'})
        mpl.rcParams.update({'ytick.color': 'w'})
        mpl.rcParams.update({'grid.color': 'w'})
        mpl.rcParams.update({'figure.facecolor': 'k'})
        mpl.rcParams.update({'figure.edgecolor': 'k'})
        #mpl.rcParams['axes.color_cycle'] = get_color_cycle(len(options.shotnos))

    SSet=surfaceAnalysis.SensorSet()    # Contains magnetic sensor information

    fig = plt.figure(figsize=(15,8))
    figTA=None
    figPA1=None
    figPA2=None
    figFB=None
    if options.plotsensors:
        figTA = plt.figure(figsize=(15,8))
        figTA.suptitle('TA sensors', fontsize=20)
        figTA.canvas.set_window_title('TA sensors')
        figPA1 = plt.figure(figsize=(15,8))
        figPA1.suptitle('PA1 sensors', fontsize=20)
        figPA1.canvas.set_window_title('PA1 sensors')
        figPA2 = plt.figure(figsize=(15,8))
        figPA2.suptitle('PA2 sensors', fontsize=20)
        figPA2.canvas.set_window_title('PA2 sensors')
        figFB = plt.figure(figsize=(15,8))
        figFB.suptitle('FB sensors', fontsize=20)
        figFB.canvas.set_window_title('FB sensors')

    figTA_n1=None
    figFB_n1=None
    if options.plot_n1_sensors:
        figTA_n1 = plt.figure(figsize=(15,8))
        figTA_n1.suptitle('n=1 signals of TA sensors', fontsize=20)
        figFB_n1 = plt.figure(figsize=(15,8))
        figFB_n1.suptitle('n=1 signals of FB sensors', fontsize=20)

    # Dictionary to contain a shotData instance for each shot number
    shot_data_dict = {}

    if options.loop:
        import socket
        screensaveroff = subprocess.call(['xset','s','off','-dpms'])
        hostname=socket.gethostname()
        if not hostname == 'spitzer':
            subprocess.call(['gnome-screensaver-command','--exit'])

        @atexit.register
        def screensaveron():
            if not hostname == 'spitzer':
                subprocess.call(['gnome-screensaver-command','-d'])
            subprocess.call(['xset','s','on','+dpms'])

        import gobject
        def loop():
            while True:
                try:
                    log.debug("Waiting for MDSplus event...")
                    MDSplus.event.Event.wfevent('hbtep2_complete')
                    log.debug("Received MDSplus event for shot complete")
                except TypeError:
                    print("Type error in loop.")
                    pass
                except:
                    raise
                gobject.idle_add(fig.clear)
                gobject.idle_add(main, SSet, shot_data_dict, fig, None, None, None, None, None, None)
                gobject.idle_add(plt.draw)

        gobject.threads_init()
        t = threading.Thread(target=loop)
        t.daemon = True
        t.start()

    main(SSet, shot_data_dict, fig, figTA, figPA1, figPA2, figFB, figTA_n1, figFB_n1)
    if options.extra_fig:
        extra_fig(SSet)
    if not plt.isinteractive():
        if not options.small:
            mng = plt.get_current_fig_manager()
            mng.window.maximize()
        plt.show()


# import curses
# from curses.textpad import Textbox, rectangle
from colorama import Style, Fore, Back,init
import sys,os
from typing import *
init()
import platform
if platform.system().lower() == "windows":
    from colorama import just_fix_windows_console
    just_fix_windows_console()

# Test of a console using curses

BANNER = r"""
 _  _   ___  ____  _  _  ____   __   __ _  ____  __
( \/ ) / __)(  _ \( \/ )(  _ \ / _\ (  ( \(  __)(  )
/ \/ \( (__  ) __/ )  /  ) __//    \/    / ) _) / (_/\
\_)(_/ \___)(__)  (__/  (__)  \_/\_/\_)__)(____)\____/"""

_BANNER_SIZE = (len(max(BANNER.split("\n"))),len([x for x in BANNER.split("\n") if x != ""]))

class DummyParent:
    BANNER = BANNER
    _BANNER_SIZE = _BANNER_SIZE

"""
class Console:
    def __init__(self,):
        ################################
        self.stdscr = curses.initscr()
        curses.noecho()
        curses.cbreak()
        self.stdscr.keypad(True)
        self.stdscr.nodelay(1)
        ################################
        self._size = (curses.LINES,curses.COLS)
        self.windows = {"log":curses.newwin(curses.LINES,40, 0, 0)}
        self.current_window = "log"
        try:
            self.run()
        finally:
            ################################
            curses.nocbreak()
            self.stdscr.keypad(False)
            curses.echo()
            curses.endwin()
            ################################

    def run(self):
        self._init()
        while 1:
            key = self.stdscr.getch()

            self.windows["log"].erase()
            self.windows["log"].insstr(1,2," "*18+"LOG "+" "*18)
            self.windows["log"].hline(2,2,"#",self.windows["log"].getmaxyx()[1]-1)
            if key != -1:
                self.windows["log"].insstr(chr(key))
            self.windows["log"].refresh()
            if key == ord("q"):
                break
            elif key in [curses.KEY_UP,curses.KEY_DOWN,curses.KEY_LEFT,curses.KEY_RIGHT]:
                curses.beep()
                #self._handle_keys(key)
            \"""
            elif key == curses.KEY_RESIZE:
                curses.resize_term(*self.stdscr.getmaxyx())
                self.stdscr.clear()
                self._resize()
            \"""
            self.stdscr.refresh()

    def _init(self):
        y,x=self.windows["log"].getmaxyx()
        i,j=self.windows["log"].getparyx()
        rectangle(self.stdscr,i+1,j+1,y-1,x)

        self.windows["log"].insstr(1,1," "*18+"LOG "+" "*18)
        self.windows["log"].hline(2,1,"#",self.windows["log"].getmaxyx()[1]-1)

        self.windows["log"].refresh()
        self.stdscr.refresh()
        curses.doupdate()

    def write(self,text,window=None):
        \"""
        Write text to the current screen.

        Can be used as a waky solution, with print:
            print("Hello world",file=console)
        \"""
        if window is None:
            window = self.windows["log"]
"""

class Console:
    USED_COLORS = [Style.RESET_ALL,Fore.LIGHTMAGENTA_EX,Fore.LIGHTBLUE_EX,Fore.LIGHTGREEN_EX,Fore.LIGHTRED_EX]
    def __init__(self,parent,master="Console"):
        self.parent = parent
        self._commands:dict[tuple[Callable,str]] = {}
        self._alias:dict[str] = {}
        self._master = master
        self._colored = True

    def _handle_command(self,inp):
        if inp is None:
            exit()
        cmd,*args = inp.split(" ")
        subcmds = []
        settings = []
        for a in args:
            if a.startswith("-"):
                settings.append(a)
            else:
                subcmds.append(a)

        if cmd not in self._commands :
            self._print_error("Syntax Error",f"Command {cmd} not found.")
            return
        _func = self._commands[cmd]
        return _func(subcmds = subcmds,settings = settings)

    def _register_command(self,func,command,alias = []):
        self._commands[command] = func
        for i in alias:
            self._commands[i] = func

    def _cmd_run(self):
        try:
            self._handle_command(self.ask_input("$"))
        except Exception as e:
            self._print_error(str(e.__class__.__name__),str(e))

    def _print_header(self):
        if self._colored:print("\033[H\033[J",end="")
        self._print(Fore.LIGHTGREEN_EX+"#"*(self.parent._BANNER_SIZE[0]+4))
        for i in range(1,self.parent._BANNER_SIZE[1]+1):
            self._print(Fore.LIGHTGREEN_EX+"# "+Fore.LIGHTMAGENTA_EX+self.parent.BANNER.split("\n")[i]+Fore.LIGHTGREEN_EX+" #")
        self._print(Fore.LIGHTGREEN_EX+"#"*(self.parent._BANNER_SIZE[0]+4))

    def _print(self,data):
        if self._colored:
            print(data+Style.RESET_ALL)
        else:
            for i in self.USED_COLORS:
                data = data.replace(i,"")
            print(data)
    def _fancy_print(self,data):
        self._print("["+self._master+"] "+Fore.LIGHTBLUE_EX+data)
    def _print_error(self,error_class="Error",error=""):
        error = Fore.LIGHTRED_EX+error_class+(": "+error) if error else ""
        self._print("["+self._master+"] "+error)

    def ask_input(self,prompt,default=None):
        if self._colored:
            prompt = "["+self._master+"] "+Fore.LIGHTBLUE_EX+prompt+Fore.LIGHTGREEN_EX+(" (default: "+str(default)+")" if default else "")
        else:
            prompt = "["+self._master+"] "+prompt+(" (default: "+str(default)+")" if default else "")
        try:
            return input(prompt+" > "+(Style.RESET_ALL if self._colored else "")) or default
        except KeyboardInterrupt:
            print()
            return None
        except:
            return None
    def ask_yes_no(self,prompt,default=True):
        if self._colored:
            prompt = "["+self._master+"] "+Fore.LIGHTBLUE_EX+prompt+Fore.LIGHTGREEN_EX+(" [Y/n]" if default else "[y/N]")
        else:
            prompt = "["+self._master+"] "+prompt+(" [Y/n]" if default else "[y/N]")
        try:
            data = input(prompt+" > "+(Style.RESET_ALL if self._colored else ""))
            if not data:return default
            return data.lower()[0] == "y"
        except KeyboardInterrupt:
            print()
            return None
        except:
            return None

class Commands:
    def __init__(self,parent):
        self.parent = parent
        for i in dir(self):
            if i.startswith("cmd"):
                self.parent.console._register_command(getattr(self,i))
        self.console = self.parent.console
    def  cmd_servers(self,sub,settings): # Sub is the sub commands
        if not sub: sub = ("list",)
        if sub[0] == "list":
            with_infos = "-i" in settings
            if self.parent._with_proxy:
                self.console._fancy_print("Here is a list of all the servers linked to this instance of mcpypanel:")
                for server in self.parent.proxy.servers:
                    self.console._fancy_print("  - "+server.name+(": running: "+("yes, " if server.running else "no, ")+"player count: "+str(len(server.players))) if with_infos else "")
            else:
                self.console._fancy_print("Here is the server linked to this instance of mcpypanel:")
                server = self.parent.server
                self.console._fancy_print("  - "+server.name+(": running: "+("yes, " if server.running else "no, ")+"player count: "+str(len(server.players))) if with_infos else "")
    def _print_subcmd_not_found(self,subcmd):
        self.console._print_error("Syntax Error",f"This subcommand ({subcmd}) was not found.")

if __name__ == "__main__":
    # For tests only
    def test(subcmds,settings):
        print("Hello",subcmds,settings)
    def _exit(subcmds,settings):
        exit()
    c = Console(DummyParent)
    c._print_header()
    c._register_command(test,"test")
    c._register_command(_exit,"exit",["quit","e"])
    while 1:
        c._cmd_run()


import pytest
from unittest.mock import patch

from src.converters.labelstograph.eventconnector import connect_events

from src.data.labels import EventLabel, SubLabel
from src.data.graph import EventNode, IntermediateNode

@pytest.mark.integration
def test_connection():
    sentence = 'If an error is present or the debugger is active and an exception is triggered'

    cause1 = EventLabel(id="T2", name="Cause1", begin=3, end=22)
    cause1var = SubLabel(id="T3", name="Variable", begin=3, end=11)
    cause1cond = SubLabel(id="T4", name="Condition", begin=12, end=22)
    cause1.add_child(cause1var)
    cause1.add_child(cause1cond)

    disj = SubLabel(id="T5", name="Disjunction", begin=23, end=25)

    cause2 = EventLabel(id="T6", name="Cause2", begin=26, end=48)
    cause2var = SubLabel(id="T7", name="Variable", begin=26, end=38)
    cause2cond = SubLabel(id="T8", name="Condition", begin=39, end=48)
    cause2.add_child(cause2var)
    cause2.add_child(cause2cond)
    cause1.set_successor(cause2, junctor='OR')

    conj = SubLabel(id="T9", name="Conjunction", begin=49, end=52)

    cause3 = EventLabel(id="T10", name="Cause3", begin=53, end=78)
    cause3var = SubLabel(id="T11", name="Variable", begin=53, end=65)
    cause3cond = SubLabel(id="T12", name="Condition", begin=66, end=78)
    cause3.add_child(cause3var)
    cause3.add_child(cause3cond)
    cause2.set_successor(cause3, junctor='AND')

    # Events
    event1 = EventNode(id='E1', labels=[cause1])
    event1.variable = 'an error'
    event1.condition = 'is present'
    event2 = EventNode(id='E2', labels=[cause2])
    event2.variable = 'the debugger'
    event2.condition = 'is active'
    event3 = EventNode(id='E3', labels=[cause3])
    event3.variable = 'an exception'
    event3.condition = 'is triggered'

    events = [event1, event2, event3]

    causes, _ = connect_events(events)

    # there should be 5 cause nodes: 3 event nodes + 2 intermediate nodes
    assert len(causes) == 5

    root: IntermediateNode = causes[0]
    assert root.conjunction == False

@pytest.mark.integration
@patch('src.converters.labelstograph.eventconnector.generate_initial_nodenet')
@patch('src.converters.labelstograph.eventconnector.get_junctors')
def test_connection_overruled_precedence(mock_gj, mock_gin):
    """Test the event connection in a scenario with overruled precedence (e.g., "If A and either B or C" which should resolve to (A && (B || C)). The root node should be the conjunction, because the precedence of the disjunction overrules the conjunction in this case."""

    cause1 = EventNode(id='c1', labels=None, variable="v1")
    cause2 = EventNode(id='c2', labels=None, variable="v2")
    cause3 = EventNode(id='c3', labels=None, variable="v3")

    edgelist = []
    i1 = IntermediateNode(id='i1', conjunction=True)
    edgelist.append(i1.add_incoming(cause1))
    edgelist.append(i1.add_incoming(cause2))
    i2 = IntermediateNode(id='i2', conjunction=False, precedence=True)
    edgelist.append(i2.add_incoming(cause2))
    edgelist.append(i2.add_incoming(cause3))

    mock_gj.return_value = None
    mock_gin.return_value = ([i1, i2], edgelist)

    nodes, edges = connect_events([cause1, cause2, cause3])
    root = nodes[0]

    # make sure the root node is an intermediate node
    assert type(root) == IntermediateNode
    root: IntermediateNode = root

    # assert that this intermediate node is the conjunction, not the disjunction
    assert root.conjunction == True


import math
import statistics
import numpy as np

class SmartMeter:
    """
    Single Smart Meter

    Args:
        readings(np.ndarray): last t records of consumption
    """

    def __init__(self, readings: np.ndarray, attack_status: str) -> None:
        self.attack_status = attack_status
        self.readings = np.copy(readings)
        self.salt = np.array(np.random.rand(1, len(readings))[0] * 100)
        self.swarm_size = None
        self.sum = np.copy(readings)
        self.avg_per_reading = None
        self.num_flags = 0
        self.num_swarm_realisations = 0
        self.sum_of_avg = 0
        self.avg_of_avg_readings = None

    def increment_swarm_count(self):
        self.num_swarm_realisations += 1

    def reset(self):
        self.swarm_size = None
        self.sum = np.copy(self.readings)
        self.avg_per_reading = None
        self.sum_of_avg = 0
        self.avg_of_avg_readings = None

    def set_swarm_size(self, swarm_size: int):
        self.swarm_size = swarm_size

    def __decrypt_sum(self):
        self.sum -= (self.salt + self.readings)

    def encrypt_sum(self):
        self.sum += self.salt

    def __decrypt_avg_sum(self):
        self.sum_of_avg -= self.salt

    def encrypt_avg_sum(self):
        self.sum_of_avg += self.salt

    def __receive_sum(self, previous_sum: np.ndarray):
        self.sum += previous_sum

    def __receive_avg(self, previous_sum: np.ndarray):
        self.sum_of_avg += previous_sum

    def send_sum(self, next_meter):
        next_meter.__receive_sum(self.sum)
        self.sum = np.copy(self.readings)

    def send_avg(self, next_meter):
        next_meter.__receive_avg(self.sum_of_avg)
        self.sum_of_avg = 0

    def calc_avg(self):
        self.__decrypt_sum()
        self.avg_per_reading = self.sum / self.swarm_size
        self.sum = np.copy(self.readings)
        self.sum_of_avg = self.avg_per_reading

    def calc_avg_avg(self):
        self.__decrypt_avg_sum()
        self.avg_of_avg_readings = self.sum_of_avg / self.swarm_size

    def calc_flag(self, delta_boundary: float, deltas: dict):
        combined_data = np.concatenate((self.avg_of_avg_readings, self.readings))
        bin_edges = np.histogram_bin_edges(combined_data, bins="auto")

        own_mean = statistics.mean(self.readings)
        own_hist, _ = np.histogram(self.readings, bins=bin_edges, density=True)

        swarm_mean = statistics.mean(self.avg_of_avg_readings)
        swarm_hist, _ = np.histogram(self.avg_of_avg_readings, bins=bin_edges, density=True)

        bin_width = np.diff(bin_edges)

        own_hist[own_hist == 0] = 0.001
        swarm_hist[swarm_hist == 0] = 0.001

        pdf_swarm = swarm_hist * bin_width
        pdf_own = own_hist * bin_width

        own_entropy = -np.sum(pdf_own * np.log(pdf_own.astype("float64")))
        swarm_entropy = -np.sum(pdf_swarm * np.log(pdf_swarm.astype("float64")))

        delta = math.sqrt((swarm_mean - own_mean)**2 + (swarm_entropy - own_entropy)**2)
        if self.attack_status == "malfunctioning":
            deltas["malfunctioning"].append(delta)
        else:
            if self.attack_status == "stealing":
                deltas["stealing"].append(delta)
            else:
                deltas["good"].append(delta)
        self.num_flags += int(delta > delta_boundary)

    def calc_kld_distance(self, delta_boundary: float, klds: dict):
        combined_data = np.concatenate((self.avg_of_avg_readings, self.readings))
        bin_edges = np.histogram_bin_edges(combined_data, bins="auto")

        swarm_hist, swarm_bins = np.histogram(self.avg_of_avg_readings, bins=bin_edges, density=True)
        own_hist, own_bins = np.histogram(self.readings, bins=bin_edges, density=True)

        # Bin width
        bin_width = np.diff(swarm_bins)

        # Normalization
        pdf_swarm = swarm_hist * bin_width
        pdf_own = own_hist * bin_width

        pdf_own[pdf_own == 0] = 0.001
        pdf_swarm[pdf_swarm == 0] = 0.001

        kld = np.sum(pdf_own * np.log((pdf_own / pdf_swarm).astype('float64')))

        if self.attack_status == "malfunctioning":
            klds["malfunctioning"].append(kld)
        else:
            if self.attack_status == "stealing":
                klds["stealing"].append(kld)
            else:
                klds["good"].append(kld)
        self.num_flags += int(kld > delta_boundary)


import sqlite3
import time
import tkinter
import tkinter as tk
from tkinter import *
from tkinter import ttk
from tkcalendar import DateEntry
from tkinter import messagebox
import receipt_backend

class Receipt(tk.Frame):
    def __init__(self, master=None, **kw):
        super().__init__(master, **kw)
        self.master = master
        self.pack()

        self.RECEIPT = StringVar()
        self.Student_Name = StringVar()
        self.Admission = StringVar()
        self.Date = StringVar()
        self.Branch = StringVar()
        self.Semester = StringVar()
        self.Total_amount = StringVar()
        self.Paid = StringVar()
        self.Balance = StringVar()

        self.create_frame()
        self.create_label()
        self.create_entry()
        self.create_scroll_bar()
        self.create_button()
        self.display()

    def OneSelected(self, event):
        global id
        try:
            self.curItem = self.receipt_table.focus()
            self.contents = (self.receipt_table.item(self.curItem))
            self.selecteditem = self.contents['values']
            self.id = self.selecteditem[0]

            self.RECEIPT.set('')
            self.RECEIPT.set(self.selecteditem[1])

            self.Student_Name.set('')
            self.Student_Name.set(self.selecteditem[2])

            self.Admission.set('')
            self.Admission.set(self.selecteditem[3])

            self.Date.set('')
            self.Date.set(self.selecteditem[4])

            self.Branch.set('')
            self.Branch.set(self.selecteditem[5])

            self.Semester.set('')
            self.Semester.set(self.selecteditem[6])

            self.Total_amount.set('')
            self.Total_amount.set(self.selecteditem[7])

            self.Paid.set('')
            self.Paid.set(self.selecteditem[8])

            self.Balance.set('')
            self.Balance.set(self.selecteditem[9])
        except IndexError:
            tkinter.messagebox.showwarning('', 'Please Select A Something !', icon="warning")

    def Save(self):
        if self.RECEIPT.get() == "" or self.Student_Name.get() == "" or self.Admission.get() == "" or \
                self.Date.get() == "" or self.Branch.get() == "" or self.Semester.get() == "" or \
                self.Total_amount.get() == "" or self.Paid.get() == "":
            tkinter.messagebox.showwarning('', 'Please Complete The Required Field', icon="warning")
        else:
            receipt_backend.insert(self.RECEIPT.get(), self.Student_Name.get(), self.Admission.get(),
                                   self.Date.get(), self.Branch.get(), self.Semester.get(),
                                   self.Total_amount.get(), self.Paid.get(), self.Balance.get())
            tkinter.messagebox.showinfo("Success", "Receipt Saved Successfully")

    def Update(self):
        self.receipt_table.delete(*self.receipt_table.get_children())
        self.conn = sqlite3.connect('UE.db')
        self.cur = self.conn.cursor()
        self.cur.execute("UPDATE `fee` SET `receipts` = ?, `name` = ?, `admin` =?, `date` = ?,  "
                         "`branch` = ?, `semester` = ?, `total` = ?, `paid` = ?, "
                         "`due` = ? WHERE `id` = ?",
                         (str(self.RECEIPT.get()), str(self.Student_Name.get()),
                          str(self.Admission.get()), str(self.Date.get()),
                          str(self.Branch.get()), str(self.Semester.get()),
                          str(self.Total_amount.get()), str(self.Paid.get()),
                          str(self.Balance.get()), str(id)))
        self.conn.commit()
        self.cur.execute("SELECT * FROM `fee` ORDER BY `name` ASC")
        self.fetch = self.cur.fetchall()
        for data in self.fetch:
            self.receipt_table.insert('', 'end', values=(data))
        self.cur.close()
        self.conn.close()

    def display(self):
        if len(receipt_backend.view()) != 0:
            self.receipt_table.delete(*self.receipt_table.get_children())
            for row in receipt_backend.view():
                self.receipt_table.insert('', END, values=row)

    def Reset(self):
        self.RECEIPT.set('')
        self.Student_Name.set('')
        self.Admission.set('')
        self.Branch.set('')
        self.Semester.set('')
        self.Paid.set('')
        self.Balance.set('')

    def delete(self):
        if not self.receipt_table.selection():
            result = tkinter.messagebox.showwarning('', 'Please Select Something First!', icon="warning")
        else:
            result = tkinter.messagebox.askquestion('', 'Are you sure you want to delete this record?', icon="warning")
            if result == 'yes':
                self.curItem = self.receipt_table.focus()
                self.contents = (self.receipt_table.item(self.curItem))
                self.selecteditem = self.contents['values']
                self.receipt_table.delete(self.curItem)
                self.conn = sqlite3.connect("UE.db")
                self.cursor = self.conn.cursor()
                self.cursor.execute("DELETE FROM `fee` WHERE `id` = %d" % self.selecteditem[0])
                self.conn.commit()
                self.cursor.close()
                self.conn.close()

    def Receipt(self):
        self.display_receipt.delete('1.0', END)
        self.display_receipt.insert(END, '\t\tRECEIPT' + '\n\n')
        self.display_receipt.insert(
            END, '\tReceipt No.\t      :' + self.RECEIPT.get() + '\n')
        self.display_receipt.insert(END, '\tStudent Name  :' +
                                    self.Student_Name.get() + '\n')
        self.display_receipt.insert(END, '\tAdmission No.\t:' +
                                    self.Admission.get() + '\n')
        self.display_receipt.insert(
            END, '\tDate\t          : ' + self.Date.get() + '\n')
        self.display_receipt.insert(
            END, '\tBranch\t          :' + self.Branch.get() + '\n')
        self.display_receipt.insert(
            END, '\tSemester \t        :' + self.Semester.get() + '\n\n')

        try:
            x1 = int(self.Total_amount.get())
            x2 = int(self.Paid.get())
            x3 = int(x1 - x2)

            self.display_receipt.insert(END, '\tTotal Amount  :' + str(x1) + '\n')
            self.display_receipt.insert(END, '\tPaid Amount   :' + str(x2) + '\n')
            self.display_receipt.insert(END, '\tBalance\t         :' + str(x3) + '\n')
            self.Balance.set(x3)
        except ValueError:
            pass

    def exit(self):
        Exit = tkinter.messagebox.askyesno("Logout System", "Confirm, if you want to Exit")
        if Exit > 0:
            self.master.destroy()
            return

    # -------------------------------- Frames -------------------------------------------------------
    def create_frame(self):
        global frame_title
        global Frame_2
        global Frame_3

        frame_title = Frame(self.master, bg="aquamarine4", bd=10, relief=SUNKEN)
        frame_title.pack()
        self.Main_Frame = LabelFrame(self.master, font=('Courier', 15, 'bold'),
                                     bg='aquamarine4', bd=15, fg='White', relief='ridge')
        self.Main_Frame.place(x=10, y=80, width=1330, height=350)

        self.Frame_1 = LabelFrame(self.master,
                                  font=('Courier', 15, 'bold'),
                                  relief='ridge', bd=10, bg='aquamarine4', fg='White', text='INFORMATION')
        self.Frame_1.place(x=35, y=105, width=700, height=300)

        self.Frame_2 = LabelFrame(self.master,
                                  font=('Courier', 15, 'bold'),
                                  relief='ridge', bd=10, fg='White', bg='aquamarine4', text='FEE RECEIPT')
        self.Frame_2.place(x=770, y=105, width=550, height=300)

        self.Frame_3 = LabelFrame(self.master, width=1180, height=70, font=('Courier', 10, 'bold'),
                                  bg='aquamarine4', relief='ridge', bd=13, fg='White')
        self.Frame_3.place(x=75, y=665)

        self.Frame_4 = LabelFrame(self.master, font=('Courier', 10, 'bold'),
                                  bg='aquamarine4', relief='ridge', bd=13, fg='White')
        self.Frame_4.place(x=22, y=450, width=1300, height=200)

    # -------------------------------- Labels -------------------------------------------------------
    def create_label(self):
        label_title1 = Label(frame_title, text='FEE REPORT', font=('Courier', 20, 'bold'),
                             bg='aquamarine4', fg='White').pack()

        self.label_Receipt = Label(self.Frame_1, text='Receipt No', font=('Courier', 16), bg='aquamarine4',
                                   fg='White')
        self.label_Receipt.place(x=10, y=20)

        self.Student_name_label = Label(self.Frame_1, text='Student Name', font=('Courier', 16), bg='aquamarine4',
                                        fg='White')
        self.Student_name_label.place(x=10, y=60)

        self.label_Admission = Label(self.Frame_1, text='Admission No', font=('Courier', 16), bg='aquamarine4',
                                     fg='White')
        self.label_Admission.place(x=10, y=100)

        self.Date_label = Label(self.Frame_1, text='Date', font=('Courier', 16), bg='aquamarine4',
                                fg='White')
        self.Date_label.place(x=10, y=140)

        self.Branch_label = Label(self.Frame_1, text='Branch', font=('Courier', 16), bg='aquamarine4',
                                  fg='White')
        self.Branch_label.place(x=10, y=180)

        self.Semester_label = Label(self.Frame_1, text='Session', font=('Courier', 16), bg='aquamarine4',
                                    fg='White')
        self.Semester_label.place(x=10, y=220)

        self.TOTAL_AMOUNT_label = Label(self.Frame_1, text='TOTAL AMOUNT', font=('Courier', 16), bg='aquamarine4',
                                        fg='White')
        self.TOTAL_AMOUNT_label.place(x=375, y=100)

        self.PAID_label = Label(self.Frame_1, text='PAID AMOUNT', font=('Courier', 16), bg='aquamarine4',
                                fg='White')
        self.PAID_label.place(x=375, y=140)

        self.BALANCE_label = Label(self.Frame_1, text='BALANCE', font=('Courier', 16), bg='aquamarine4',
                                   fg='White')
        self.BALANCE_label.place(x=375, y=180)

    # -------------------------------- Entry -------------------------------------------------------
    def create_entry(self):
        self.Entry_receipt = Entry(self.Frame_1, font=('arial', 10), textvariable=self.RECEIPT)
        self.Entry_receipt.focus()
        self.Entry_receipt.place(x=190, y=20)

        self.Entry_StudentName = Entry(self.Frame_1, font=('arial', 10), textvariable=self.Student_Name)
        self.Entry_StudentName.place(x=190, y=60)

        self.Entry_Admission = Entry(self.Frame_1, font=('arial', 10), textvariable=self.Admission)
        self.Entry_Admission.place(x=190, y=100)

        self.Entry_date = DateEntry(self.Frame_1, font=('arial', 12), date_pattern='dd/mm/yyyy', textvariable=self.Date)
        self.Entry_date.place(x=190, y=140, width=145)

        self.Entry_branch = ttk.Combobox(self.Frame_1, values=(' ', 'Computer Science', 'Science Of Education',
                                                               'Business Management'),
                                         font=('arial', 10), width=21, textvariable=self.Branch)
        self.Entry_branch.place(x=190, y=180, width=145)

        self.Entry_semester = ttk.Combobox(self.Frame_1, values=(' ', 'FIRST', 'SECOND'),
                                           font=('arial', 10), textvariable=self.Semester)
        self.Entry_semester.place(x=190, y=220, width=145)

        self.Entry_total_amount = Entry(self.Frame_1, font=('arial', 12), bg='Azure2', textvariable=self.Total_amount,
                                        state='readonly')
        self.Entry_total_amount.place(x=540, y=100, width=100, height=25)
        self.Total_amount.set(24000)

        self.Entry_paid = Entry(self.Frame_1, font=('arial', 12), bg='Azure2', textvariable=self.Paid)
        self.Entry_paid.place(x=540, y=140, width=100, height=25)
        self.Paid.set(0.0)

        self.Entry_balance = Entry(self.Frame_1, font=('arial', 12), bg='Azure2', textvariable=self.Balance, )
        self.Entry_balance.place(x=540, y=180, width=100, height=25)
        self.Balance.set(0.0)

    # -------------------------------- Scroll Bar And Text -------------------------------------------------------
    def create_scroll_bar(self):
        global receipt_table
        global display_receipt

        self.x_scroll = Scrollbar(self.Frame_4, orient=HORIZONTAL)
        self.y_scroll = Scrollbar(self.Frame_4, orient=VERTICAL)
        self.receipt_table = tkinter.ttk.Treeview(self.Frame_4,
                                                  column=('No', 'receipt_no', 'student_name', 'admission_no', 'date',
                                                          'branch', 'semester', 'total_amount', 'paid_amount'),
                                                  xscrollcommand=self.x_scroll.set, yscrollcommand=self.y_scroll.set)

        self.x_scroll.pack(side=BOTTOM, fill=X)
        self.y_scroll.pack(side=RIGHT, fill=Y)
        self.x_scroll.config(command=self.receipt_table.xview)
        self.y_scroll.config(command=self.receipt_table.yview)

        self.receipt_table.heading('receipt_no', text='Receipt No')
        self.receipt_table.heading('student_name', text='Student Name')
        self.receipt_table.heading('admission_no', text='Admission No')
        self.receipt_table.heading('date', text='Date')
        self.receipt_table.heading('branch', text='Branch')
        self.receipt_table.heading('semester', text='Semester')
        self.receipt_table.heading('total_amount', text='Total Amount')
        self.receipt_table.heading('paid_amount', text='Paid Amount')
        self.receipt_table.heading('No', text='No')
        self.receipt_table['show'] = 'headings'
        self.receipt_table.column('receipt_no', width=70)
        self.receipt_table.column('No', width=30)
        self.receipt_table.pack(fill=BOTH, expand=1)
        self.receipt_table.bind('<Double-Button-1>', self.OneSelected)

        self.display_receipt = Text(self.Frame_2, font=('arial', 14, 'bold'), bg='Azure2')
        self.display_receipt.place(x=0, y=0, width=530, height=265)

    # -------------------------------- Button -------------------------------------------------------

    def create_button(self):
        self.button_save = Button(self.Frame_3, text="SAVE", font=('Courier', 15, 'bold'), bg='aquamarine4', fg="White",
                                  width=12, command=self.Save)
        self.button_save.place(x=5, y=2)
        self.button_save.config(activebackground="#41B77F", relief=RAISED)

        self.button_display = Button(self.Frame_3, text='DISPLAY', font=('Courier', 15, 'bold'), bg='aquamarine4',
                                     fg="White", width=12, command=self.display)
        self.button_display.place(x=170, y=2)
        self.button_display.config(activebackground="#41B77F", relief=RAISED)

        self.button_reset = Button(self.Frame_3, text='RESET', font=('Courier', 15, 'bold'), bg='aquamarine4',
                                   fg="White", width=12, command=self.Reset)
        self.button_reset.place(x=335, y=2)
        self.button_reset.config(activebackground="#41B77F", relief=RAISED)

        self.button_update = Button(self.Frame_3, text='UPDATE', font=('Courier', 15, 'bold'), bg='aquamarine4',
                                    fg="White", width=12, command=self.Update)
        self.button_update.place(x=500, y=2)
        self.button_update.config(activebackground="#41B77F", relief=RAISED)

        self.button_delete = Button(self.Frame_3, text='DELETE', font=('Courier', 15, 'bold'), bg='aquamarine4',
                                    fg="White", width=12, command=self.delete)
        self.button_delete.place(x=665, y=2)
        self.button_delete.config(activebackground="#41B77F", relief=RAISED)

        self.button_receipt = Button(self.Frame_3, text='RECEIPT', font=('Courier', 15, 'bold'), bg='aquamarine4',
                                     fg="White", width=12, command=self.Receipt)
        self.button_receipt.place(x=830, y=2)
        self.button_receipt.config(activebackground="#41B77F", relief=RAISED)

        self.button_exit = Button(self.Frame_3, text='EXIT', font=('Courier', 15, 'bold'), bg='aquamarine4',
                                  fg="White", width=12, command=self.exit)
        self.button_exit.place(x=995, y=2)
        self.button_exit.config(activebackground="#41B77F", relief=RAISED)

        self.localtime = time.asctime(time.localtime(time.time()))
        self.lbl_time = Label(self.master, text=self.localtime, fg="white", font=("Courier", 16), bg="aquamarine4")
        self.lbl_time.place(x=1017, y=50)

root = tk.Tk()
root.geometry("1350x750")
root.resizable(False, False)
root.title("Université Espoir")
root.iconbitmap("logo1.ICO")
root.config(background="aquamarine4")

app = Receipt(master=root)
app.mainloop()


import sente
import glob
import re
import pandas as pd
import itertools
import json
import analysisengine
import os
from sgf2img import GetAllThemes, GameImageGenerator
from ast import literal_eval

position_label_dict = {
    'KL2L':[(3,2),(2,4),(2,7)],
    'HLA':[(3,3),(2,5),(3,5)],
    'SAV':[(3,2),(3,4),(2,4),(3,3),(2,3),(4,2)],
    'HLKA':[(3,3),(2,5),(5,2),(2,3),(2,2),(1,2)],
    'KH1L':[(3,2),(3,4),(2,6)],
    'KH2L':[(3,2),(3,4),(2,7)],
    'KL1H':[(3,2),(2,4),(3,6)],
    'KL1L':[(3,2),(2,4),(2,6)],
    'ENCLOSUREONEOFF':[(2,3)]
}
position_label_descriptions = {
    'KL2L':'Komoku, Low Approach, 2 Space Low Pincer',
    'HLA':'Hoshi, Low Approach, Attach On Top',
    'SAV':'Small Avalanche',
    'HLKA':'Hoshi, Low Approach, Knights move, Attach',
    'KH1L':'Komoku, High Approach, 1 Space Low Pincer',
    'KH2L':'Komoku, High Approach, 2 Space Low Pincer',
    'KL1H':'Komoku, Low Approach, 1 Space High Pincer',
    'KL1L':'Komoku, Low Approach, 1 Space Low Pincer',
    'ENCLOSUREONEOFF':'Komoku enclosures'
}

letter_map = {'A': 0,
 'B': 1,
 'C': 2,
 'D': 3,
 'E': 4,
 'F': 5,
 'G': 6,
 'H': 7,
 'J': 8,
 'K': 9,
 'L': 10,
 'M': 11,
 'N': 12,
 'O': 13,
 'P': 14,
 'Q': 15,
 'R': 16,
 'S': 17,
 'T': 18,
 '0': 'A',
 '1': 'B',
 '2': 'C',
 '3': 'D',
 '4': 'E',
 '5': 'F',
 '6': 'G',
 '7': 'H',
 '8': 'J',
 '9': 'K',
 '10': 'L',
 '11': 'M',
 '12': 'N',
 '13': 'O',
 '14': 'P',
 '15': 'Q',
 '16': 'R',
 '17': 'S',
 '18': 'T'}

def ref_seq(seq: list) -> list:
    return [sente.Move(m.get_y(),m.get_x(),m.get_stone()) for m in seq]

def rot_seq(seq: list) -> list:
    return [sente.Move(18 - m.get_y(),m.get_x(),m.get_stone()) for m in seq]

def seq_to_vert(seq: list) -> list:
    return [f'({m.get_x()},{m.get_y()})' for m in seq]

def get_scenarios(position_label: str) -> pd.DataFrame:
    matches_path = position_label + '\\matches.txt'
    matches_df = pd.read_csv(matches_path)
    matches_df['scenario_label'] = [f'{i:03}' for i in range(len(matches_df))]
    mod_seqs = []
    transformed_sequences = []
    for i in range(len(matches_df)):
        game = sente.sgf.load('..\\go4go_collection\\'+matches_df.fname[i])
        seq = game.get_default_sequence()[:matches_df.startindex[i]]
        mod_seq = seq.copy()
        for j in range(matches_df.reflection[i]):
            mod_seq = ref_seq(mod_seq)
        for j in range(matches_df.rotation[i]):
            mod_seq = rot_seq(mod_seq)
        mod_seqs.append(mod_seq)
        transformed_sequences.append(seq_to_vert(mod_seq))
    matches_df['mod_seq'] = mod_seqs
    matches_df['transformed_sequence'] = transformed_sequences
    return matches_df

def get_nodes(position_label: str) -> pd.DataFrame:
    tree = sente.sgf.load(position_label + '\\tree.sgf')
    tree_coords = [seq_to_vert(s) for s in tree.get_all_sequences()]
    dup_nodes = [l[:i] for l in tree_coords for i in range(len(l)+1)]
    dup_nodes.sort()
    tree_nodes = list(x for x, _ in itertools.groupby(dup_nodes))
    nodenames = [re.sub(r'[(),]','',"".join(x)) for x in tree_nodes]
    nodes_df = pd.DataFrame({'node_name':nodenames,
        'coord_list':tree_nodes})
    return nodes_df

def get_merge(position_label: str) -> pd.DataFrame:
    matches_df = get_scenarios(position_label)
    nodes_df = get_nodes(position_label)
    matches_df['dummy']="a"
    nodes_df['dummy']="a"
    merge_df = pd.merge(nodes_df,matches_df,on = 'dummy')
    merge_df['id'] = position_label + "_" + merge_df['node_name'] + "_" + merge_df['scenario_label']
    merge_df['verts'] = merge_df['transformed_sequence'] + merge_df['coord_list']
    merge_df['moves'] = [[[p,vert] for vert,p in zip(l,("BW"*200)[:len(l)])] for l in merge_df['verts']]
    return merge_df

def get_query(position_label: str) -> list:
    merge_df = get_merge(position_label)
    dictlist = [{
        'id': merge_df.id[i],
        'moves': merge_df.moves[i],
        "rules": "tromp-taylor",
        "komi": 7.5,
        "boardXSize": 19,
        "boardYSize": 19
    } for i in range(len(merge_df))]
    return dictlist

def run_query(position_label: str, max_queries: int = 1000) -> list:
    out_path = position_label + '\\raw_output.txt'
    existing_stringlist = []
    if glob.glob(out_path):
        with open(out_path,'r') as in_f:
            existing_stringlist = in_f.read().split(";")
    existing_ids = [json.loads(x)['id'] for x in existing_stringlist]
    full_query = get_query(position_label)
    q = [x for x in full_query if x['id'] not in existing_ids][:max_queries]
    print(len(q))
    if len(q) > 0:
        g = analysisengine.gtp(analysisengine.cmd)
        new_out_list = g.run_lists(q)
        g.close()
        g.terminate()
    else:
        new_out_list = []
    out_s = ";".join(existing_stringlist + new_out_list)
    with open(out_path,'w') as out_f:
        out_f.write(out_s)
    return out_s

def node_id_to_verts(node_id: str) -> list:
    node_id_parts = node_id.split("_")
    start_verts = position_label_dict[node_id_parts[0]]
    s = [int(x) for x in node_id_parts[1]]
    return start_verts + list(zip(s[::2],s[1::2]))

def vertlist_to_qimage(vertlist: list, size: int = 1024):
    g = sente.Game(19)
    for vert in vertlist:
        g.play(vert[0]+1,vert[1]+1)
    sente.sgf.dump(g,'temp.sgf')
    gig = GameImageGenerator(GetAllThemes()['print'],True)
    img = gig.get_game_image('temp.sgf',
    size,
    1,
    len(vertlist),
    len(vertlist),
    [1,9,10,19])
    os.remove('temp.sgf')
    return img

def vertlist_to_image(vertlist: list, seq_length: int = 1):
    g = sente.Game(19)
    for vert in vertlist:
        g.play(vert[0]+1,vert[1]+1)
    sente.sgf.dump(g,'temp.sgf')
    gig = GameImageGenerator(GetAllThemes()['subdued'],False)
    img = gig.get_game_image('temp.sgf',
    400,
    1,
    len(vertlist) + 1 - seq_length,
    len(vertlist),
    [1,1,19,19])
    os.remove('temp.sgf')
    return img

def get_results_df(position_label: str) -> pd.DataFrame:
    out_s = run_query(position_label,0)
    out_dicts = [json.loads(x) for x in out_s.split(";")]
    results_df = pd.DataFrame([{
        'id': d['id'],
        'top_move': d['moveInfos'][0]['move'],
        'top_seq': d['moveInfos'][0]['pv'],
        'scoreLead': -d['moveInfos'][0]['scoreLead'],
        'winrate': 1 - d['moveInfos'][0]['winrate']
    } for d in out_dicts])
    results_df['prev_id'] = [x[:-6] + x[-4:] if (len(x)>9) else None for x in results_df.id]
    return results_df

def get_delta_df(position_label: str) -> pd.DataFrame:
    results_df = get_results_df(position_label)
    prev_df = pd.DataFrame({
        'prev_id': results_df.id,
        'prev_move': results_df.top_move,
        'prev_scoreLead': results_df.scoreLead,
        'prev_winrate': results_df.winrate
    })
    delta_df = pd.merge(results_df,prev_df,on = 'prev_id')
    delta_df['delta_p'] = delta_df.scoreLead + delta_df.prev_scoreLead
    delta_df['delta_wr'] = delta_df.winrate + delta_df.prev_winrate - 1
    delta_df['node_name'] = delta_df.id.str.slice(0,-4)
    return delta_df.drop(columns=['prev_id', 'prev_scoreLead', 'prev_winrate'])

def save_qimages(position_label: str):
    merge_df = get_merge(position_label)
    merge_df['node_name'] = merge_df.id.str.slice(0,-4)
    for node_id in set(merge_df.node_name):
        outp = position_label + '\\qimages\\' + node_id +'.png'
        vertlist_to_qimage(node_id_to_verts(node_id)).save(outp)

def save_qimages_s(position_label: str):
    merge_df = get_merge(position_label)
    merge_df['node_name'] = merge_df.id.str.slice(0,-4)
    for node_id in set(merge_df.node_name):
        outp = position_label + '\\qimages\\' + node_id +'_s.png'
        vertlist_to_qimage(node_id_to_verts(node_id), 250).save(outp)

def save_images(position_label: str):
    """maybe change this so that it uses the raw output instead of merge. Would give diagrams for index"""
    merge_df = pd.merge(get_merge(position_label),get_results_df(position_label).loc[:,['id','top_seq']],on = 'id')
    for i in range(len(merge_df)):
        outp = position_label + '\\images\\' + merge_df.id[i] +'.png'
        vertlist = [literal_eval(x) for x in merge_df.verts[i]]
        vertlist += [(letter_map[x[0]],19-int(x[1:])) for x in merge_df.top_seq[i]]
        vertlist_to_image(vertlist,len(merge_df.top_seq[i])).save(outp)

def save_histograms(position_label: str):
    delta_df = get_delta_df(position_label)
    for node_id in set(delta_df.node_name):
        outp = position_label + '\\histograms\\' + node_id +'hist.png'
        ax = delta_df[delta_df.node_name == node_id].delta_p.plot.hist()
        fig = ax.get_figure()
        fig.savefig(outp)
        fig.clf()

def get_scenario_table_html(position_label: str) -> str:
    delta_df = get_delta_df(position_label)
    delta_df['img_html'] = '\n'
    return delta_df.loc[:,['img_html','delta_p','delta_wr']].sort_values('delta_p').to_html()

def save_summaries(position_label: str):
    delta_df = get_delta_df(position_label)
    delta_df['img_html'] = 'zzzimg src="..\\images\\' + delta_df.id +'.png" alt="fig"yyy'#height=300 width=300yyy'
    for node_id in set(delta_df.node_name):
        filt_df = delta_df[delta_df.node_name == node_id]
        summary_table = filt_df.describe().loc[:,['delta_p','delta_wr']]
        outp = position_label + '\\summaries\\' + node_id +'.html'
        summary_table_html = summary_table.to_html()
        full_table_html = filt_df.loc[:,['img_html','top_seq','delta_p','delta_wr']].sort_values('delta_p').to_html()
        page = (
            '<!DOCTYPE html>\n\n'
            '<link rel="stylesheet" href="..\\..\\basic.css">'
            '\n\n'
            f'
{position_label_descriptions[position_label]}
'
            f'
{get_breadcrumb(node_id)}
\n'
            '
'
            '
Previous moves
'
            f'
{get_breadcrumb(node_id)}
\n'
            '
Alternatives
'
            #f'
Alternatives to {nodeid_to_lastboard(node_id)} in this corner pattern include..
'
            f'
{"
".join(get_alternatives(node_id))}
'
            #'
Katago follow ups
'
            #f'
Katago considers the following moves after {nodeid_to_lastboard(node_id)} in this corner pattern..
'
            '
Human follow ups
'
            #f'
Humans consider the following moves after {nodeid_to_lastboard(node_id)} in this corner pattern..
'
            f'
{"
".join(get_followups(node_id))}
'
            '
'
            f'\n'
            '
'
            f'\n'
            f'\n{summary_table_html}\n'
            '
'
            f'\n{full_table_html}\n'
            '\n\n'
        )
        page = page.replace('zzz','<')
        page = page.replace('yyy','>')
        with open(outp,'w') as f:
            f.write(page)

def save_position_summary(position_label: str):
    #delta_df = get_delta_df(position_label)
    #delta_df['img_html'] = 'zzzimg src="..\\images\\' + delta_df.id +'.png" alt="fig"yyy'#height=300 width=300yyy'
    #filt_df = delta_df[delta_df.node_name == node_id]
    #summary_table = filt_df.describe().loc[:,['delta_p','delta_wr']]
    outp = position_label + '\\index.html'
    #summary_table_html = summary_table.to_html()
    #full_table_html = filt_df.loc[:,['img_html','top_seq','delta_p','delta_wr']].sort_values('delta_p').to_html()
    page = (
        '<!DOCTYPE html>\n\n'
        '<link rel="stylesheet" href="..\\basic.css">'
        '\n\n'
        f'
{position_label_descriptions[position_label]}
'
        '
'
        '
Human follow ups
'
        f'
{"
".join(get_followups(position_label+"_"))}
'
        '
'
        f'\n'
        #f'\n{full_table_html}\n'
        '\n\n'
    )
    page = page.replace('zzz','<')
    page = page.replace('yyy','>')
    with open(outp,'w') as f:
        f.write(page)

def nodeid_to_lastboard(nodeid: str) -> str:
    underscore_loc = nodeid.find('_')+1
    if len(nodeid)>underscore_loc:
        lb = letter_map[nodeid[-2:-1]] + str(19 - int(nodeid[-1:]))
    else:
        lb = "Root"
    return lb

def node_to_link(nodeid: str) -> str:
    return f'{nodeid_to_lastboard(nodeid)}'

def get_breadcrumb(nodeid: str) -> str:
    underscore_loc = nodeid.find('_')+1
    prevnodes = [nodeid[:underscore_loc+2*n] for n in range(int((len(nodeid)-5)/2))]
    prevlinks = [node_to_link(nodeid) for nodeid in prevnodes]
    return " _ ".join(prevlinks)

def get_alternatives(nodeid: str) -> str:
    underscore_loc = nodeid.find('_')
    pattern_name = nodeid[:underscore_loc]
    altnos = [x for x in get_nodes(pattern_name).node_name if x[:-2] == nodeid[underscore_loc+1:-2]]
    return [node_to_link(pattern_name + '_' + no) for no in altnos]

def get_followups(nodeid: str) -> str:
    underscore_loc = nodeid.find('_')
    pattern_name = nodeid[:underscore_loc]
    folnos = [x for x in get_nodes(pattern_name).node_name if x[:-2] == nodeid[underscore_loc+1:]]
    return [node_to_link(pattern_name + '_' + no) for no in folnos]

def run_position(position_label: str, max_queries: int = 1000):
    run_query(position_label,max_queries)
    save_qimages(position_label)
    save_qimages_s(position_label)
    save_images(position_label)
    save_histograms(position_label)
    save_summaries(position_label)