#!/usr/bin/env python3
import os
import sys
import argparse
import csv
import matplotlib.pyplot as plt
import zlib

class time_serise:
    time_serise = []
    cdf = []
    num = 0

    def __init__(self, ts):
        self.time_serise = ts
        self.cdf = ts.copy()
        self.cdf.sort()
        self.num = len(ts)

    def get_min(self):
        return self.get_percentile(0.0)

    def get_nmax(self):
        return self.get_percentile(100.0)

    def get_median(self):
        return self.get_percentile(50.0)

    def get_percentile(self, p):
        # p = [0:100]
        i = round( float(self.num - 1) * (p / 100.0) )
        return self.cdf[i]

    def get_average(self):
        return sum(self.cdf) / self.num

def load_mango_csv(csv_name):
    def get_sys_info(csv_rd):
        sys_info = {}
        keys = rd.__next__()
        values = rd.__next__()
        for (k, v) in zip(keys, values):
            sys_info[k] = v
        return sys_info

    with open(csv_name, 'r') as f:
        rd = csv.reader(f)
        try:
            # parse system information
            sys_info = get_sys_info(rd)
            # transpose a row-oriented format to column-oriented format
            cols = list( map(lambda c: [c], rd.__next__()) )
            for row in rd:
                for (i, elm) in enumerate(row):
                    cols[i].append( float(elm) )
        except csv.Error as e:
            sys.exit('Invalid CSV data at {}@{}: {}'.format( \
                    csv_name, rd.line_num, e))
        perf_data = {}
        for cl in cols:
            k, v = cl[0], time_serise( cl[1:] )
            perf_data[k] = v
        return (sys_info, perf_data)

def get_log_name(args, fea, out):
    log = os.path.join(args.outdir,
                       args.prefix + "-ginsight-" + fea + "." + out)
    return log

def reset_plot():
    plt.clf()
    plt.style.use('default')
    plt.rcParams['font.size'] = 7

def hash_rgb_from_str(s):
    h = zlib.crc32( bytes(s, 'utf-8') )
    g = (h & 0xFF0000) >> 16
    b = (h & 0x0000FF) >> 0
    r = (h & 0x00FF00) >> 8
    return (float(r)/0xFF, float(g)/0xFF, float(b)/0xFF)

def gen_dist_fig(args, fea, ts, pss, title, x_label, y_label, min_max):
    # prepare canvas
    reset_plot()
    fig, axs = plt.subplots(nrows=3, ncols=1, figsize=(3.5, 10))

    # 1) violin plot
    # - convert pss to quantile for violin plot
    quan = []
    for ps in pss:
        p, k = ps
        quan.append( p / 100.0 )
    # - plot
    ax = axs[0]
    violin = ax.violinplot([ts.time_serise], showmeans=True,  quantiles=[ quan ])
    violin['bodies'][0].set_facecolor( hash_rgb_from_str(y_label) )
    violin['cmeans'].set_edgecolor('red')
    # - set title
    ax.set_title(title)
    # - decoration
    ax.set_ylim(bottom = min_max[0], top = min_max[1])
    ax.set_xlabel(x_label)
    ax.set_ylabel(y_label)

    # 2) cdf plot
    # - plot
    ax = axs[1]
    ax.plot(range(ts.num), ts.cdf, label='CDF', 
            linewidth=1, color='black', marker='*')
    # - plot stats
    y = ts.get_average()
    ax.plot([0, ts.num], [y, y], label="Average: %.1f" % y)
    for p, l in pss:
        y = ts.get_percentile(p)
        ax.plot([0, ts.num], [y, y], label=l + ": %.1f" % y)
    # - decoration
    ax.set_ylim(bottom = min_max[0], top = min_max[1])
    ax.set_xlabel(x_label)
    ax.set_ylabel(y_label)
    ax.legend()

    # 3) time serise plot
    ax = axs[2]
    ax.plot(range(ts.num), ts.time_serise, 
            label='FPS', linewidth=1, color='black')
    # - decoration
    ax.set_ylim(bottom = min_max[0], top = min_max[1])
    ax.set_xlabel(x_label)
    ax.set_ylabel(y_label)

    # save to the file
    fig_name = get_log_name(args, fea, "svg")
    plt.savefig(fig_name)
    plt.close()

def gen_ts_fig(args, fea, ts, pss, title, x_label, y_label, min_max):
    # clear canvas
    reset_plot()
    plt.figure(figsize=(3.5,3))

    # plot FPS overtime
    plt.plot(range(ts.num), ts.time_serise, label='FPS', linewidth=1, color='black')

    # decoration
    plt.ylim(bottom = min_max[0], top = min_max[1])
    plt.xlabel(x_label)
    plt.ylabel(y_label)
    plt.title(title)

    # save to the file
    fig_name = get_log_name(args, fea + "-ts", "svg")
    plt.savefig(fig_name)
    plt.close()

def gen_violin_fig(args, fea, ts, pss, title, x_label, y_label, min_max):
    # clear canvas
    reset_plot()
    plt.figure(figsize=(3.5,3))

    # pss to quantile
    quan = []
    for ps in pss:
        p, k = ps
        quan.append( p / 100.0 )

    # plot
    violin = plt.violinplot([ts], showmeans=True,  quantiles=[ quan ])
    violin['bodies'][0].set_facecolor( hash_rgb_from_str(y_label) )
    violin['cmeans'].set_edgecolor('red')

    # decoration
    plt.ylim(bottom = min_max[0], top = min_max[1])
    plt.xlabel(x_label)
    plt.ylabel(y_label)
    plt.title(title)

    # save to the file
    fig_name = get_log_name(args, fea + "-violin", "svg")
    plt.savefig(fig_name)
    plt.close()

def gen_cdf_fig(args, fea, ts, pss, title, x_label, y_label, min_max):
    # clear canvas
    reset_plot()
    plt.figure(figsize=(3.5,3))

    # plot cdf
    plt.plot(range(ts.num), ts.cdf, label='CDF', 
             linewidth=1, color='black', marker='*')
    # plot stats
    y = ts.get_average()
    plt.plot([0, ts.num], [y, y], label="Average: %.1f" % y)
    for p, l in pss:
        y = ts.get_percentile(p)
        plt.plot([0, ts.num], [y, y], label=l + ": %.1f" % y)

    # decoration
    plt.ylim(bottom = min_max[0], top = min_max[1])
    plt.xlabel(x_label)
    plt.ylabel(y_label)
    plt.title(title)
    plt.legend()

    # save to the file
    fig_name = get_log_name(args, fea + "-cdf", "svg")
    plt.savefig(fig_name)
    plt.close()

def gen_cdf_csv(args, ts, pss, y_label, f):
    # header
    print("%s, %s" % ("{0:^20}".format("Stat"), "{0:^20}".format(y_label)), 
          file = f )

    # stat
    y = ts.get_average()
    print("%s, %s" % ("{0:<20}".format("Average"), "{0:>20}".format("%.4f" % y)),
          file = f)
    for p, l in pss:
        y = ts.get_percentile(p)
        print("%s, %s" % ("{0:<20}".format(l), "{0:>20}".format("%.4f" % y)),
              file = f)
    low1 = ts.get_percentile(1.0)
    med  = ts.get_percentile(50.0)
    y = low1 / med
    print("%s, %s" % ("{0:<20}".format("Low1-Med ratio"), 
                      "{0:>20}".format("%.4f" % y)),
          file = f)

def report_stat(args, sys_info, perf_data):
    class report_conf:
        key = ""
        pss = []
        title = ""
        x_label = ""
        y_label = ""
        min_max = (0.0, 0.0)
        def __init__(self, k, p, t, x, y, mX):
            self.key = k
            self.pss = p
            self.title = t
            self.x_label = x
            self.y_label = y
            self.min_max = mX

    # FIXME: ad-hoc code
    y_fps= 1.0
    if args.prefix == "troy-low-battle-benchmark":
        y_fps= 2.0
    title = args.prefix
    confs = [report_conf("fps", 
                         [(50.0, "Median"), (0.0, "Min"), (100.0, "Max"),
                          (0.1, "Low 0.1%"), (1.0, "Low 1%"), (97.0, "Low 97%"),],
                         title, "frames", "FPS", (0.0, 120.0 * y_fps)),
             report_conf("frametime",
                         [(50.0, "Median"), (0.0, "Min"), (100.0, "Max"),
                          (99.0, "High 1%"), (99.9, "High 0.1%"), ],
                         title, "frames", "frametime (usec)", (0.0, 200000.0)),
             report_conf("cpu_load",
                         [(50.0, "Median"), (0.0, "Min"), (100.0, "Max")],
                         title, "", "cpu load (%)", (0.0, 100.0)),
             report_conf("gpu_load",
                         [(50.0, "Median"), (0.0, "Min"), (100.0, "Max")],
                         title, "", "gpu load (%)", (0.0, 100.0)),
             report_conf("ram_used", 
                         [(50.0, "Median"), (0.0, "Min"), (100.0, "Max")], 
                         title, "", "ram used (GB)", (0.0, 16.0)),]

    for c in confs:
        ts = perf_data[c.key]
        # generate cdf stat
        log = get_log_name(args, c.key, "csv")
        with open(log, 'w') as f:
           gen_cdf_csv(args, ts, c.pss, c.y_label, f)
           if args.quiet == False:
               gen_cdf_csv(args, ts, c.pss, c.y_label, sys.stdout)
        # generate distribution graphs
        gen_violin_fig(args, c.key, ts.time_serise, c.pss, c.title, c.x_label, c.y_label, c.min_max)
        gen_cdf_fig(args, c.key, ts, c.pss, c.title, c.x_label, c.y_label, c.min_max)
        gen_ts_fig(args, c.key, ts, c.pss, c.title, c.x_label, c.y_label, c.min_max)
        gen_dist_fig(args, c.key, ts, c.pss, c.title, c.x_label, c.y_label, c.min_max)

def get_cmd_options(argv):
    parser = argparse.ArgumentParser(
            prog = "ginsight",
            description = "Generarte a report from MangoHud log")
    parser.add_argument('-l', '--log', action='store', required=True,
                        help='MangoHud log file in a CSV format') 
    parser.add_argument('-o', '--outdir', action='store', required=True,
                        help='output directory') 
    parser.add_argument('-p', '--prefix', action='store', required=True,
                        help='output file prefix') 
    parser.add_argument('-q', '--quiet', action='store_true',
                        help='do not print result to stdout' ) 
    args = parser.parse_args(argv)
    return args

if __name__ == "__main__":
    args = get_cmd_options(sys.argv[1:])
    sys_info, perf_data = load_mango_csv(args.log)
    report_stat(args, sys_info, perf_data)



