#!/usr/bin/env python

import os
import sys
import argparse
import glob
import re
import pprint
from collections import defaultdict
import numpy

sys.path.append('./myplot')
import myplot
import plot_byte_overhead
import plot_time_scenarios
import browser_byte_overhead

# configuration
FIG_DIR = './fig/myplot'
RESULT_DIR = './final'
PROTOCOLS = {
    2: ('spp', 'ssl', 'fwd', 'pln', 'spp_mod'),
    3: ('spp', 'ssl', 'fwd', 'pln', 'spp_mod'),
    4: ('spp', 'ssl', 'fwd', 'pln', 'spp_mod'),
    5: ('spp', 'ssl', 'fwd', 'pln', 'spp_mod'),
    #6: ('spp', 'ssl', 'fwd', 'pln', 'spp_mod'),
    6: ('spp', 'ssl_mod', 'fwd_mod', 'pln_mod', 'spp_mod'),
    7: ('spp', 'ssl', 'fwd', 'spp_mod', 'spp_2', 'spp_4'),
    8: ('spp', 'ssl', 'fwd', 'pln'),
    9: ('spp', 'ssl', 'fwd', 'pln', 'spp_mod'),
}
LEGEND_STRINGS = {
    'pln': 'NoEncrypt',
    'fwd': 'E2E-TLS',
    'ssl': 'SplitTLS',
    'spp': 'mcTLS',
    'spp_mod': 'mcTLS (Nagle off)',
    'ssl_mod': 'SplitTLS (Nagle off)',
    'fwd_mod': 'E2E-TLS (Nagle off)',
    'pln_mod': 'NoEncrypt (Nagle off)',
    'spp_2': 'mcTLS (2 mbox)',
    'spp_4': 'mcTLS (4 mbox)',
}
EXPERIMENT_NAMES = {
    2: 'timeFirstByte_slice',
    3: 'timeFirstByte_latency',
    4: 'timeFirstByte_proxy',
    5: 'downloadTime',
    6: 'page_load_time',
    7: 'connections_slice',
    8: 'byteOverhead_scenarios',
    9: 'timeFirstByte_scenarios',
    60: 'byteOverhead_browser',
}
X_AXIS = {
    2: 'Number of Contexts',
    3: 'Link Latency (ms)',
    4: 'Number of Middleboxes',
    5: 'File Size (kB)',
    6: 'Load Time (s)',
    7: 'Number of Contexts',
}
Y_AXIS = {
    2: 'Time to First Byte (ms)',
    3: 'Time to First Byte (ms)',
    4: 'Time to First Byte (ms)',
    5: 'Download Time (s)',
    6: 'CDF',
    7: 'Connections per Second',
    8: 'Size (kB)',
    9: 'Download Time (s)',
}
DATA_TRANSFORMS = {
    2: lambda x: x*1000,
    3: lambda x: x*1000,
    4: lambda x: x*1000,
    5: lambda x: x,
    6: lambda x: x,
    7: lambda x: x,
    8: lambda x: float(x)/1024.,
    9: lambda x: float(x),
    60: lambda x: float(x)/1024.,
}
SHOW_RTTS = {
    2: 4,
    3: 4,
    4: 4,
    5: 3,
    6: 0,
    7: 0,
    8: 0,
    9: 4,
}

##
## Manually tweak per-plot settings
##
MANUAL_ARGS=defaultdict(dict)
MANUAL_ARGS['connections_slice_local_tid.system-ns.net.pdf'] = {
    'ylim':(0, 500),
}
MANUAL_ARGS['timeFirstByte_slice_local_local.pdf'] = {
    'legend_loc': 'upper left',
    'xlim': (0, 16),
}
MANUAL_ARGS['page_load_time_local_local_slicing-comparison.pdf'] = {
    'xlim':(0, 12),
}
MANUAL_ARGS['page_load_time_remote_local_slicing-comparison.pdf'] = {
    'legend_text_size':17,
}
MANUAL_ARGS['page_load_time_local_local_proto-comparison.pdf'] = {
    'xlim':(0, 12),
}
MANUAL_ARGS['page_load_time_remote_local_proto-comparison.pdf'] = {
    'xlim':(0, 25),
}
MANUAL_ARGS['timeFirstByte_scenarios_local_local.pdf'] = {
    'width_scale':3,
    'yscale':'log',
}
MANUAL_ARGS['connections_slice_local_server_54-67-37-251.pdf'] = {
    'ylim':(0, 600),
}
MANUAL_ARGS['connections_slice_local_server_54-76-148-166.pdf'] = {
    'ylim':(0, 600),
}


# returns 3 arrays: rtts, num_mboxes, num_slices
def get_params(opt, remote, data):
    rtts = []
    num_mboxes = []
    num_slices = []

    if opt == 2:
        num_slices = data[:, 0]
        num_mboxes = data[:, 2]
        if remote:
            rtts = data[:, 5]
        else:
            rtts = 2*numpy.multiply(data[:, 1], data[:, 2]+1)

    elif opt == 3:
        num_slices = data[:, 1]
        num_mboxes = data[:, 2]
        rtts = 2*numpy.multiply(data[:, 0], data[:, 2]+1)
        if remote:
            print 'WARNING: shouldn\'t get here'

    elif opt == 4:
        num_mboxes = data[:, 0]
        num_slices = data[:, 1]
        if remote:
            rtts = data[:, 5]
        else:
            rtts = 2*numpy.multiply(data[:, 0]+1, data[:, 2])

    elif opt == 5:
        num_slice = data[:, 1]
        num_mboxes = [1]*data.shape[0]
        if remote:
            print 'WARNING: don\'t know what to do here'
        else:
            rtts = 2*numpy.multiply(data[:, 2], numpy.array(num_mboxes)+1)/1000

    elif opt == 6:
        num_slice = data[1, 2]
        rtt = data[1, 3]

    elif opt == 7:
        num_slices = data[:, 0]
        num_mboxes = data[:, 2]
        if remote and data.shape[1] >= 6:
            rtts = data[:, 5]
        else:
            rtts = 2*numpy.multiply(data[:, 1], data[:, 2]+1)

    return rtts, num_mboxes, num_slices


def title(opt, remote, data):
    title = ''
    if opt == 2 or opt == 7:
        rtt = data[1, 2]
        N = data[1, 3]  # TODO: off by one?
        if not remote:
            title += 'LinkLatency=%d ms   NumMbox=%d' % (rtt, N)
        else:
            title += 'NumMbox=%d' % N

    elif opt == 3:
        num_slice = data[1, 2]
        N = data[1, 3]
        title += 'NumSlice=%d   NumMbox=%d' % (num_slice, N)

    elif opt == 4:
        num_slice = data[1, 2]
        rtt = data[1, 3]
        title += 'NumSlice=%d   LinkLatency=%d ms' % (num_slice, rtt)

    elif opt == 5:
        num_slice = data[1, 2]
        rtt = data[1, 3]
        title += 'NumSlice=%d   LinkLatency=%d ms   Rate=5Mbps' % (num_slice, rtt)

    elif opt == 6:
        num_slice = data[1, 1]
        rtt = data[1, 0]
        if not remote:
            title += 'NumSlice=%d   LinkLatency=%d ms' % (num_slice, rtt)
        else:
            title += 'NumSlice=%d   ' % num_slice

    if remote:
        title += '   AMAZON'
    else:
        title += '   LOCAL'

    return title


def outfile(opt, remote, machine, extra_tag=''):
    remote_str = 'remote' if remote else 'local'
    machine_str = machine if machine else 'local'
    filename = '%s_%s_%s%s.pdf' % (EXPERIMENT_NAMES[opt], remote_str, machine_str, extra_tag)
    filepath = os.path.join(FIG_DIR, filename)
    return filename, filepath


def make_rtt_lines(endpoints, num=3):
    # TODO: use num
    lines = []
    for i in range(1, num+1):
        line = {
            'endpoints':((endpoints[0][0], endpoints[0][1]*i),
                         (endpoints[1][0], endpoints[1][1]*i)),
            'stretch':True,
            'line_args':{
                'linewidth':1,
                'color':'gray',
                'alpha':0.7,
            },

            'label':'%d RTT' % i,
            'label_args':{
                'color':'gray',
                'alpha':0.7,
                'size':'small',
            },
        }
        lines.append(line)
    return lines


#def dump_to_file_for_xkcd(xs, ys, labels, filepath):
#    filepath += '.tsv'
#    with open(filepath, 'w') as f:
#        header_str = ''
#        for label in labels:
#            header_str += '%s_X\t%s\t' % (label, label)
#        f.write('%s\n' % header_str)
#        
#        # FIXME: assumes all series have same num points
#        for point in range(len(xs[0])):
#            line = ''
#            for series in range(len(xs)):
#                line += '%s\t%s\t' % (xs[series][point], ys[series][point])
#            f.write('%s\n' % line)

def dump_to_file_for_xkcd(xs, ys, yerrs, labels, filepath):
    filepath += '.tsv'
    with open(filepath, 'w') as f:
        f.write('protocol\tX\tY\tstddev\n')
        
        for series in range(len(xs)):
            line = ''
            for point in range(len(xs[0])):
                line += '%s\t%d\t%f\t%f\n' % (labels[series], xs[series][point], ys[series][point], yerrs[series][point])
            f.write(line)
                







# result_files is a dict: protocol -> result file path
def plot_series(machine, remote, result_files):

    out_filename, out_filepath = outfile(args.opt, remote, machine)

    xs = []  # holds arrays of x values, 1 per series
    ys = []  # holds arrays of y values, 1 per series
    yerrs = []  # holds arrays of std devs, 1 per series
    labels = []
    plot_title = ''

    # maps X value (e.g., num slices or num mboxes) to a list of measured or
    # calculated RTTs for that value (sometimes RTT depends on X, e.g., #mbox)
    x_to_rtts = defaultdict(list)

    for protocol in PROTOCOLS[args.opt]:
        if protocol not in result_files: continue
        filepath = result_files[protocol]
        print '[IN]', protocol, filepath
        data = numpy.loadtxt(filepath)
        if len(data) == 0 or\
           len(data.shape) != 2 or\
           data.shape[1] not in (5, 7):  # should be either 5 or 7 cols
            print 'WARNING: malformed data: %s' % filepath
            continue

        transform = numpy.vectorize(DATA_TRANSFORMS[args.opt])
        
        xs.append(data[:,0])
        ys.append(transform(data[:,3]))
        yerrs.append(transform(data[:,4]))
        labels.append(LEGEND_STRINGS[protocol])
        plot_title = title(args.opt, remote, data)

        # RTT measurements
        rtts, _, _ = get_params(args.opt, remote, data)
        for i in range(data.shape[0]):
            x_to_rtts[data[i,0]].append(rtts[i])

    rtt_lines = []
    if SHOW_RTTS[args.opt]:
        # average RTT measurements
        for x in x_to_rtts:
            x_to_rtts[x] = numpy.mean(x_to_rtts[x])
        # find RTT line endpoints
        e1 = (xs[0][0], x_to_rtts[xs[0][0]])
        e2 = (xs[0][-1], x_to_rtts[xs[0][-1]])
        rtt_lines = make_rtt_lines((e1, e2), SHOW_RTTS[args.opt])
            

    # TODO: avg RTT measurment
    rtt = 75

    if len(xs) != len(ys) or len(ys) != len(yerrs) or\
       len(yerrs) != len(labels) or len(ys) == 0:
        print 'ERROR: no well-formed data to plot ***'
        return

    print '[OUT]', out_filepath
    myplot.plot(xs, ys, yerrs=yerrs, labels=labels, xlabel=X_AXIS[args.opt],\
        ylabel=Y_AXIS[args.opt], guide_lines=rtt_lines[1:],\
        #title=plot_title,\
        #builds = [[], [3], [3, 1, 2], [3, 1, 2, 0], [3, 1, 2, 0, 4]],\
        #builds = [[], [1, 2], [0, 1, 2], [0, 1, 2, 3, 4]],\
        filename=out_filepath, **MANUAL_ARGS[out_filename])

    dump_to_file_for_xkcd(xs, ys, yerrs, labels, out_filepath)
            

# result_files is a dict: protocol -> result file path
def plot_browser(machine, remote, result_files):

    ##
    ## plot 1: compare 4-slice version of all protocols
    ##
    out_filename, out_filepath = outfile(args.opt, remote, machine,\
        extra_tag='_proto-comparison')

    # need to make alternate dict of result paths
    temp_result_files = {}
    if 'spp_four-slices' in result_files:
        temp_result_files['spp'] = result_files['spp_four-slices']
    if 'spp_mod_four-slices' in result_files:
        temp_result_files['spp_mod'] = result_files['spp_mod_four-slices']
    if 'ssl_four-slices' in result_files:
        temp_result_files['ssl'] = result_files['ssl_four-slices']
    if 'ssl_mod_four-slices' in result_files:
        temp_result_files['ssl_mod'] = result_files['ssl_mod_four-slices']
    if 'fwd_four-slices' in result_files:
        temp_result_files['fwd'] = result_files['fwd_four-slices']
    if 'fwd_mod_four-slices' in result_files:
        temp_result_files['fwd_mod'] = result_files['fwd_mod_four-slices']
    if 'pln_four-slices' in result_files:
        temp_result_files['pln'] = result_files['pln_four-slices']
    if 'pln_mod_four-slices' in result_files:
        temp_result_files['pln_mod'] = result_files['pln_mod_four-slices']
        
    ys = []  # holds arrays of y values, 1 per series
    labels = []
    plot_title = ''

    for protocol in PROTOCOLS[args.opt]:
        if protocol not in temp_result_files: continue
        filepath = temp_result_files[protocol]
        print '[IN]', protocol, filepath
        data = numpy.loadtxt(filepath)
        if len(data) == 0 or\
           len(data.shape) != 2 or\
           data.shape[1] != 3:
            print 'WARNING: malformed data: %s' % filepath
            continue
        
        transform = numpy.vectorize(DATA_TRANSFORMS[args.opt])

        print protocol, max(transform(data[:,2]))
        ys.append(transform(data[:,2]))
        if protocol == 'spp':
            labels.append(LEGEND_STRINGS[protocol] + ' (4 Ctx)')
        elif protocol == 'spp_mod':
            labels.append('mcTLS (4 Ctx, Nagle Off)')
        else:
            labels.append(LEGEND_STRINGS[protocol])
        plot_title = title(args.opt, remote, data)

    print '[OUT]', out_filepath
    myplot.cdf(ys, labels=labels, xlabel=X_AXIS[args.opt],\
        #title=plot_title,\
        #builds = [[], [3], [3, 1, 2], [3, 1, 2, 0], [3, 1, 2, 0, 4]],\
        filename=out_filepath, **MANUAL_ARGS[out_filename])
    
    
    ##
    ## plot 2: compare slice strategies (SPP only)
    ##
    out_filename, out_filepath = outfile(args.opt, remote, machine,\
        extra_tag='_slicing-comparison')

    ys = []  # holds arrays of y values, 1 per series
    labels = []
    plot_title = ''

    SLICE_LEGEND = {
        'spp_one-slice': 'mcTLS (1 Ctx)',
        'spp_mod_one-slice': 'mcTLS (1 Ctx, Nagle Off)',
        'spp_four-slices': 'mcTLS (4 Ctx)',
        'spp_mod_four-slices': 'mcTLS (4 Ctx, Nagle Off)',
        'spp_slice-per-header': 'mcTLS (Ctx per Hdr)',
        'spp_mod_slice-per-header': 'mcTLS (Ctx per Hdr, Nagle Off)',
    }

    for protocol in ('spp_one-slice', 'spp_mod_one-slice', 'spp_four-slices', 'spp_mod_four-slices', 'spp_slice-per-header', 'spp_mod_slice-per-header'):
        if protocol not in result_files: continue
        filepath = result_files[protocol]
        print '[IN]', protocol, filepath
        data = numpy.loadtxt(filepath)
        if len(data) == 0 or\
           len(data.shape) != 2 or\
           data.shape[1] != 3:
            print 'WARNING: malformed data: %s' % filepath
            continue
        
        transform = numpy.vectorize(DATA_TRANSFORMS[args.opt])

        ys.append(transform(data[:,2]))
        labels.append(SLICE_LEGEND[protocol])
        plot_title = title(args.opt, remote, data)

    print '[OUT]', out_filepath
    myplot.cdf(ys, labels=labels, xlabel=X_AXIS[args.opt],\
        #title=plot_title,\
        filename=out_filepath, **MANUAL_ARGS[out_filename])





def main():

    # Discover result files for this experiment type. Result files may be
    # available some subset of the protocols, remote or local, and from different
    # machines.

    # machine name -> protocol -> result file path
    remote_files = defaultdict(lambda:defaultdict(list))  
    local_files = defaultdict(lambda:defaultdict(list))
    for result_file in glob.glob(RESULT_DIR + '/*%s*' % EXPERIMENT_NAMES[args.opt]):
        m = re.match(r'.*res_((.{3}(_mod)?)(_one-slice|_four-slices|_slice-per-header|_[0-9])?)_(remote_)?%s(_(.*))?' %\
            EXPERIMENT_NAMES[args.opt], result_file)
        if m:
            protocol = m.group(1)
            remote = (m.group(5) is not None)
            remainder = m.group(7)
            machine = None

            if remainder:  # decide if it's garbage or machine name
                if re.match('(server|mbox|client)?_tid.system-ns.net', remainder) or \
                   re.match('(server|mbox|client)?_localhost', remainder) or \
                   re.match('(server|mbox|client)?_[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}', remainder):
                    machine = remainder
                    machine = machine.replace('.', '-')

                    
                else:
                    print 'WARNING: unexpected file name: %s' % result_file
                    continue

            if remote:
                remote_files[machine][protocol] = result_file
            else:
                local_files[machine][protocol] = result_file

        else:
            print 'WARNING: unexpected file name: %s' % result_file
            continue

    if args.opt in (1, 2, 3, 4, 5, 7):
        for machine, result_files in remote_files.iteritems():
            plot_series(machine, True, result_files)

        for machine, result_files in local_files.iteritems():
            plot_series(machine, False, result_files)

    elif args.opt == 6:
        for machine, result_files in remote_files.iteritems():
            plot_browser(machine, True, result_files)

        for machine, result_files in local_files.iteritems():
            plot_browser(machine, False, result_files)

    elif args.opt in (8,):
        for machine, result_files in remote_files.iteritems():
            plot_byte_overhead.plot_byte_scenarios(machine, True, result_files)

        for machine, result_files in local_files.iteritems():
            plot_byte_overhead.plot_byte_scenarios(machine, False, result_files)

    elif args.opt == 9:
        for machine, result_files in remote_files.iteritems():
            plot_time_scenarios.plot_time_scenarios(machine, True, result_files)

        for machine, result_files in local_files.iteritems():
            plot_time_scenarios.plot_time_scenarios(machine, False, result_files)

    elif args.opt == 60:
        for machine, result_files in remote_files.iteritems():
            browser_byte_overhead.analyze_browser_bytes(machine, True, result_files)

        for machine, result_files in local_files.iteritems():
            browser_byte_overhead.analyze_browser_bytes(machine, False, result_files)
        
        
    

    

if __name__ == '__main__':
    # set up command line args
    parser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter,\
                                     description='Plot mcTLS experiment results.')
    parser.add_argument('opt', type=int, help='Experiment type')
    args = parser.parse_args()

    main()
