'''Script for running the arch-2018 benchmark systems with hylaa-continuous'''

import time
import subprocess
import sys

from motor import run_hylaa as run_motor
from building import run_hylaa as run_building
from pde import run_hylaa as run_pde
from heat import run_hylaa as run_heat
from iss import run_hylaa as run_iss
from beam import run_hylaa as run_beam
from mna1 import run_hylaa as run_mna1
from fom import run_hylaa as run_fom
from mna5 import run_hylaa as run_mna5

def is_safe_result(res):
    '''check a result object and check if the tool's result is safe'''

    return res['safe']

def main():
    '''main entry point'''

    # list of model name (used to define filenames)
    models = []
    models.append('Motor')
    models.append('Building')
    models.append('PDE')
    models.append('Heat')
    models.append('ISS')
    models.append('Beam')
    models.append('MNA1')
    models.append('FOM')
    models.append('MNA5')

    funcs = []
    funcs.append(run_motor)
    funcs.append(run_building)
    funcs.append(run_pde)
    funcs.append(run_heat)
    funcs.append(run_iss)
    funcs.append(run_beam)
    funcs.append(run_mna1)
    funcs.append(run_fom)
    funcs.append(run_mna5)
    assert len(funcs) == len(models)

    old_times = []
    old_times.append(2.6)
    old_times.append(7.6)
    old_times.append(13.4)
    old_times.append(56.6)
    old_times.append(1 * 60 + 39)
    old_times.append(4 * 60 + 24)
    old_times.append(20 * 60 + 41)
    old_times.append(45 * 60 + 15)
    old_times.append(24 * 60 * 60 + 2 * 60)
    assert len(old_times) == len(models)

    start = time.time()
    lines = []

    with open('results.dat', "w") as f:
        line = '{\\bf Model~~~~} & {\\bf Dims~~} & {\\bf Krylov-Dims} & {\\bf New Runtime~~} & ' + \
                '{\\bf Old Runtime~\cite{bak2017arch}~~} & {\\bf Speedup} & {\\bf CE Error} \\\\\n'
        f.write(line)
        lines.append(line)

        f.write('\\midrule\n')

        for i in xrange(len(models)):
            print "\n\n----------------------\nRunning '{}' (safe)...".format(models[i])
            dims, res = funcs[i](True)
            assert res.safe, "Safe version of {} was not verified as safe!".format(models[i])

            new_secs = res.timers['total'].total_secs
            kry_dims = res.krylov_stats['arnoldi_iter']
            old_secs = old_times[i]
            speedup = old_secs / new_secs

            print "\n\n----------------------\nRunning '{}' (unsafe)...".format(models[i])
            dims, res = funcs[i](False)
            assert not res.safe, "Unsafe version of {} was not shown as unsafe!".format(models[i])

            print "Running counter-example simulation..."
            ce_data = get_counter_example_data('counterexample.py')
            ce_acc = "${}$".format(ce_data[1]) # 0 is abs error, 1 is rel error

            line = '{} & {} & {} & {} & {} & {:.1f}x & {} \\\\\n'.format( \
                models[i], dims, ', '.join([str(d) for d in kry_dims]), runtime_string(new_secs), runtime_string(old_secs), \
                speedup, ce_acc)
            lines.append(line)
            f.write(line)

            f.flush()

        f.write('\\bottomrule\n')

    # print summary
    print ""

    for line in lines:
        print line,

    dif = time.time() - start
    print "\nMeasurement Completed in {:2f} seconds".format(dif)

def runtime_string(secs):
    'convert a float of seconds into a string for the table'

    rv = ""

    if secs < 60:
        rv = "{:.1f}s".format(secs)
    elif secs < 60 * 60:
        mins = int(secs / 60)
        secs = int(secs - 60 * mins)
        rv = "{}m{}s".format(mins, secs)
    else:
        mins = secs / 60.0
        hours = int(mins / 60)
        mins = int(mins - 60 * hours)
        rv = "{}h{}m".format(hours, mins)

    return rv

def get_counter_example_data(trace_file):
    '''
    get the l-2 norm of the absolute / relative error of the counter-example trace

    returns a latex string tuple (abs_error, rel_error, final_time)
    '''

    params = [sys.executable, trace_file, 'noplot']
    rv = ["", "", ""]

    try:
        proc = subprocess.Popen(params, stdout=subprocess.PIPE)

        line = proc.stdout.readline()

        while line:
            # process line
            line = line.strip()
            print line

             # max difference (l-infinity norm): 3.75398923058e-08 in dimension 5
            if "Absolute Error (l-2 norm):" in line:
                abs_error = "{:.1E}".format(float(line.split(" ")[4]))
                # convert to latex
                parts = abs_error.split('E')
                rv[0] = "{} \\mkern1mu{{\\cdot}} 10^{{ {} }}".format(parts[0], int(parts[1]))

                print "Extracted absolute error: {}".format(rv[0])
            elif "Relative Error (l-2 norm):" in line:
                rel_error = "{:.1E}".format(float(line.split(" ")[4]))
                # convert to latex
                parts = rel_error.split('E')
                rv[1] = "{} \\mkern1mu{{\\cdot}} 10^{{ {} }}".format(parts[0], int(parts[1]))

                print "Extracted relative error: {}".format(rv[1])
            elif "Final Time:" in line:
                rv[2] = line.split(" ")[2]

            line = proc.stdout.readline()

        proc.wait()

    except OSError as e:
        raise RuntimeError("Error running counter-example trace: {}".format(e))

    return rv

def extract_model_data(cfgfile):
    'Extract data from the cfg file text. Returns a tuple of strings: (error_condition, dims, step, original_star)'

    error_condition = '-'
    dims = '-'
    step = '-'
    original_star = ''

    with open(cfgfile) as f:
        for line in f:
            line = line.strip()

            if 'dimensions: ' in line:
                dims = line.split(': ')[1]
            elif 'original system: ' in line:
                original_star = '*' if 'true' in line.split(': ')[1] else ''
            elif 'error condition: ' in line:
                error_condition = line.split(': ')[1]
            elif 'sampling-time = ' in line:
                step = line.split('= ')[1]

    return (error_condition, dims, step, original_star)

if __name__ == '__main__':
    main()
