#!/usr/local/cpython-3.4/bin/python3

'''Test a bunch of implicitly-sorted dictionary-like datastructures'''

import dbm
import sys

import common
import python2x3


def to_key(interpreter_name, number_pattern, datastructure, size):
    '''Create a database key from a few variables'''
    bytes_size = python2x3.string_to_binary(str(size))
    key = interpreter_name + b'/' + number_pattern + b'/' + datastructure + b'/' + bytes_size
    return key


class Test_parameters(object):
    # pylint: disable=too-few-public-methods,too-many-instance-attributes
    '''A container class that holds some tests, to make passing things around easier'''
    def __init__(self, too_long, max_reps, interpreter_path, number_pattern, size, exponent, max_exponent):
        # pylint: disable=too-many-arguments
        self.max_reps = max_reps
        self.interpreter_path = interpreter_path
        self.number_pattern = number_pattern
        self.size = size
        self.too_long = too_long
        self.exponent = exponent
        self.max_exponent = max_exponent


def inner_loops(database, test_parameters):
    '''Perform the benchmark inner loops'''

    interpreter_name = common.get_interpreter(test_parameters.interpreter_path)

    datastructures = list(common.DATASTRUCTURES)
    datastructures.sort()

    for datastructure in datastructures:
        key = to_key(
            interpreter_name=interpreter_name,
            number_pattern=test_parameters.number_pattern,
            datastructure=datastructure,
            size=test_parameters.size,
        )

        if key in database:
            # This could be because we've already computed
            # this, or it could be because a previous
            # test of this datastructure took too long.
            if database[key] == 'too_long':
                sys.stderr.write('Skipping {} because things were taking too long\n'.format(key))
            else:
                sys.stderr.write('Skipping {} because it has already been done\n'.format(key))
            continue

        durations = []
        for repno in range(test_parameters.max_reps):
            dummy = repno
            sys.stderr.write('Testing {}\n'.format(key))
            duration = common.invoke_test_one(
                test_parameters.interpreter_path,
                datastructure,
                test_parameters.number_pattern,
                test_parameters.size,
                test_parameters.too_long,
                )
            if duration == -1:
                sys.stderr.write('    Timed out\n')
                break
            else:
                sys.stderr.write('    Got duration {}\n'.format(duration))
                durations.append(duration)
        if duration == -1:
            # This indicates that the time was too high (signified with a number that's WAAAY too high)
            mean = 1e100
            standard_deviation = 0
        else:
            mean = common.compute_mean(durations)
            standard_deviation = common.compute_standard_deviation(durations, mean)
        if mean > test_parameters.too_long:
            value = 'too_long'
            # Write "too_long" to all sizes from exponent to max_exponent
            for too_long_exponent in range(test_parameters.exponent, test_parameters.max_exponent + 1):
                size = 2 ** too_long_exponent
                key = to_key(
                    interpreter_name=interpreter_name,
                    number_pattern=test_parameters.number_pattern,
                    datastructure=datastructure,
                    size=size,
                )
                database[key] = value
        else:
            value = '%s %s' % (mean, standard_deviation)
            database[key] = value


def outer_loops(database, min_exponent, max_exponent, max_reps, too_long):
    '''Perform the benchmark outer loops'''
    for exponent in range(min_exponent, max_exponent + 1):
        size = 2 ** exponent
        for interpreter_path in common.INTERPRETERS:
            for number_pattern in common.NUMBER_PATTERN:
                test_parameters = Test_parameters(
                    max_reps=max_reps,
                    interpreter_path=interpreter_path,
                    number_pattern=number_pattern,
                    size=size,
                    too_long=too_long,
                    exponent=exponent,
                    max_exponent=max_exponent,
                )
                inner_loops(
                    database,
                    test_parameters,
                )


def main():
    '''Main function'''

    production = True
    if production:
        min_exponent = 11
        max_exponent = 30
        max_reps = 5
        too_long = 30.0 * 60.0
    else:
        min_exponent = 10
        max_exponent = 11
        max_reps = 2
        too_long = 20.0

    database = dbm.open('results', 'c')

    outer_loops(database, min_exponent, max_exponent, max_reps, too_long)


main()