
"""
Reads the data generated by performance tests and generates a savable
report which can be viewed over time to examine the performance effects of code changes on
various parts of the system.
"""

import sqlite3
from lxml.builder import E
import lxml.html
#import click


DB_NAME = 'block_times.db'


class HTMLTable(object):
    """
    Simple wrapper for an HTML table.
    """
    def __init__(self, hdr_columns):
        self.table = E.TABLE()
        col_headers = [E.TH(x) for x in hdr_columns]
        header_row = E.TR(*col_headers)
        self.table.append(header_row)

    def add_row(self, items):
        """Add row to table."""
        row_items = [E.TD(x) for x in items]
        self.table.append(E.TR(*row_items))

    def tostring(self):
        """Output table HTML as string."""
        return lxml.html.tostring(self.table)

    @staticmethod
    def style():
        """ Return a hard-coded table style."""
        return E.style("""
            table, th, td {
                border: 1px solid black;
                border-collapse: collapse;
            }
            th, td {
                padding: 5px;
            }"""
                       )  # pylint: disable=bad-continuation


class HTMLDocument(object):
    """
    Simple wrapper for an entire HTML document.
    """
    def __init__(self, title):
        self.html = E.html(E.head(E.title(title), HTMLTable.style()))
        self.body = E.body()
        self.html.append(self.body)

    def add_header(self, level, text):
        """Add a header to the document."""
        func_name = "H{}".format(level)
        self.body.append(getattr(E, func_name)(text))

    def add_to_body(self, elem):
        """Add to document body."""
        self.body.append(elem)

    def tostring(self, pretty_print=False):
        """Output HTML document as string."""
        return lxml.html.tostring(self.html, pretty_print=pretty_print)


def read_timing_data():
    """
    Read in the timing data from the sqlite DB and save into a dict.
    """
    run_data = {}

    # Read data from all modulestore combos.
    conn = sqlite3.connect(DB_NAME)
    conn.row_factory = sqlite3.Row
    sel_sql = 'select id, run_id, block_desc, elapsed, timestamp FROM block_times ORDER BY run_id DESC'
    cur = conn.cursor()
    cur.execute(sel_sql)
    all_modulestore_combos = set()
    for row in cur.fetchall():
        time_taken = row[3]

        # Split apart the description into its parts.
        desc_parts = row[2].split(':')
        modulestores = desc_parts[1]
        all_modulestore_combos.add(modulestores)
        amount_md = desc_parts[2]
        test_phase = 'all'
        if len(desc_parts) > 3:
            test_phase = desc_parts[3]

        # Save the data in a multi-level dict - { phase1: { amount1: {ms1->ms2: duration, ...}, ...}, ...}.
        phase_data = run_data.setdefault(test_phase, {})
        amount_data = phase_data.setdefault(amount_md, {})
        __ = amount_data.setdefault(modulestores, time_taken)

    return all_modulestore_combos, run_data


def generate_html(all_ms_combos, run_data):
    """
    Generate HTML.
    """

    html = HTMLDocument("Results")

    # Output comparison of each phase to a different table.
    for phase in run_data.keys():
        if phase in ('fake_assets',):
            continue
        per_phase = run_data[phase]
        html.add_header(1, phase)

        title_map = {
            'duration': 'Total Duration (ms)',
            'ratio': 'Total Duration Per Number of Assets (ms/asset)',
            'variable_cost': 'Asset Export Duration Per Number of Assets (ms/asset)'
        }
        for table_type in ('duration', 'ratio', 'variable_cost'):
            if phase == 'all' and table_type in ('ratio', 'variable_cost'):
                continue
            # Make the table header columns and the table.
            columns = ["Asset Metadata Amount", ]
            ms_keys = sorted(all_ms_combos)
            for k in ms_keys:
                columns.append("{} ({})".format(k, table_type))
            phase_table = HTMLTable(columns)

            # Make a row for each amount of asset metadata.
            for amount in sorted(per_phase.keys()):
                per_amount = per_phase[amount]
                num_assets = int(amount)
                row = [amount, ]
                for modulestore in ms_keys:
                    if table_type == 'duration':
                        value = per_amount[modulestore]
                    elif table_type == 'ratio':
                        if num_assets != 0:
                            value = per_amount[modulestore] / float(amount)
                        else:
                            value = 0
                    elif table_type == 'variable_cost':
                        if num_assets == 0:
                            value = 0
                        else:
                            value = (per_amount[modulestore] - per_phase['0'][modulestore]) / float(amount)
                    row.append("{}".format(value))
                phase_table.add_row(row)

            # Add the table title and the table.
            html.add_header(2, title_map[table_type])
            html.add_to_body(phase_table.table)

    return html


# @click.command()
# @click.argument('outfile', type=click.File('w'), default='-', required=False)
# def cli(outfile):
#     """
#     Generate an HTML report from the sqlite timing data.
#     """
#     all_ms_combos, run_data = read_timing_data()
#     html = generate_html(all_ms_combos, run_data)
#     click.echo(html.tostring(), file=outfile)

# if __name__ == '__main__':
#     cli()  # pylint: disable=no-value-for-parameter
