#!/usr/local/jython-2.7b3/bin/jython

'''Test one datastructure for a given number pattern, heap state, and size'''

# pylint: disable=superfluous-parens
# Parentheses are good for clarity and portability

# port re
import sys
import time
import random
# port subprocess

import python2x3
import linked_list_mod

import common
import memuse_mod


def usage(retval):
    '''Output a usage message'''
    if retval == 0:
        write = sys.stdout.write
    else:
        write = sys.stderr.write
    write('Usage: {}\n'.format(sys.argv[0]))
    write('    --datastructure ds\n')
    write('    --size size\n')
    write('    --just-run\n')
    write('    --help\n')

    sys.exit(retval)


def my_range(top):
    '''range generator with consistent semantics across 2.x and 3.x'''
    number = 0
    while number < top:
        yield number
        number += 1


# We use a default argument to compile once, use many
# def get_load_factor(regex=re.compile(br'^ .*load average: ([0-9]*\.[0-9]*), [0-9]*\.[0-9]*, [0-9]*\.[0-9]*$')):
def get_load_factor():
    '''Obtain the system load factor'''

#     process = subprocess.Popen('uptime', stdout=subprocess.PIPE)
#     (input_, output) = process.communicate()
#     dummy = output
#     retval = process.returncode
#     assert retval in {0, None}
#     lines = input_.rstrip(b'\n').split(b'\n')
#     assert len(lines) == 1
#
#     match_obj = regex.match(lines[0])
#
#     assert match_obj is not None
#
#     return float(match_obj.group(1))

    with open('/proc/loadavg') as file_:
        line = file_.readline()
    fields = line.split()
    one_minute_load_average_string = fields[0]
    one_minute_load_average_float = float(one_minute_load_average_string)
    return one_minute_load_average_float


def in_good_load_factor():
    '''True iff load factor is below 1.0'''
    load_factor = get_load_factor()
    if load_factor >= 1.0:
        return False
    else:
        return True

    # return indicates a good load factor


def get_hour():
    '''Get the hour of the day'''

    hour = time.localtime(time.time()).tm_hour

    return hour


def in_good_time_window():
    '''True iff in a good time window'''

    hour = get_hour()
    dummy = hour

    return True

    # 11PM to 5:59AM
# All times are good, because this machine is dedicated to these tests now, other
# than twice-weekly backups.
#    if hour <= 5 or hour >= 23:
#        return True
#    else:
#        return False


def commas(string):
    '''Output a number with commas'''

    reverse_string = string[::-1]

    list_ = []
    for elementno, character in enumerate(reverse_string):
        if elementno and elementno % 3 == 0:
            list_.append(',')
        list_.append(character)

    reversed_list = list_[::-1]

    return ''.join(reversed_list)


def prefragment():
    '''Fragment the heap'''

    time0 = time.time()

    linked_list = linked_list_mod.Linked_list()

    # 4.0 gigabytes
    max_size = int(2**30 * 5.0)
    num_strings = 0

    sys.stderr.write('Starting prefragment: {}\n'.format(time.ctime(time0)))
    while True:
        # 1 to 1000
        string_length = common.get_random_number(999) + 1

        string = b'a' * string_length
        linked_list.append(string)

        bytes_in_use = memuse_mod.memory()
        gig_in_use = float(bytes_in_use) / (1024 * 1024 * 1024)

        num_strings += 1

        if num_strings and num_strings % 1000000 == 0:
            sys.stderr.write('num_strings: {} gig_in_use: {}\n'.format(num_strings, gig_in_use))

        if bytes_in_use >= max_size:
            break

    time1 = time.time()

    seconds = time1 - time0
    minutes = seconds / 60.0
    sys.stderr.write('{}: prefragment required {} minutes and {} strings\n'.format(sys.argv[0], minutes, num_strings))

    # when we return, linked_list goes out of scope and can be garbage collected - no need for a del


def possible_pause(options):
    '''Pause if relevant, until good time window and good load factor, but only if not options.just_run'''
    if not options.just_run:
        while True:
            is_good_time_window = in_good_time_window()
            is_good_load_factor = in_good_load_factor()
            if is_good_time_window:
                if is_good_load_factor:
                    # Both conditions are good, return
                    break
                else:
                    # Good time window, bad load factor
                    sys.stderr.write('{}: good time, bad load: {}\n'.format(sys.argv[0], time.ctime()))
                    time.sleep(20)
            else:
                if is_good_load_factor:
                    # Bad time window, good load factor
                    sys.stderr.write('{}: bad time, good load: {}\n'.format(sys.argv[0], time.ctime()))
                    time.sleep(60 * 10)
                else:
                    # Bad time window, bad load factor
                    sys.stderr.write('{}: bad time, bad load: {}\n'.format(sys.argv[0], time.ctime()))
                    time.sleep(60 * 10)


def test(options):
    '''
    Perform tests for a given datastructure, number_pattern (sequential or random),
    number of operations, and heap state.
    '''

    possible_pause(options)

    random.seed(0)

    get_percent = 5
    set_percent = 95

    total = get_percent + set_percent

    time0 = time.time()

    obj = options.datastructure.initializer()

    something_set = False

    for operationno in my_range(options.size):
        time1 = time.time()
        delta_time = time1 - time0

        if options.too_long is not None and delta_time > options.too_long:
            # -1 Signifies a timeout
            return -1

        dummy = operationno
        if common.get_random_number(total) < get_percent:
            # Do a get
            if not something_set:
                # ...unless there's nothing there to get yet
                continue
            key = options.number_pattern.next_get_key()
            if options.number_pattern.key_verifies(key, obj):
                pass
            else:
                sys.stderr.write('%s: key failed to verify: %s\n' % (sys.argv[0], key))
                sys.exit(1)
        else:
            # Do a set
            something_set = True
            key = options.number_pattern.next_set_key()
            obj[key] = str(key)

    return delta_time


class Options(object):
    # pylint: disable=too-few-public-methods,too-many-instance-attributes
    '''Hold and check command line options'''

    def __init__(self):
        self.datastructure = None
        self.number_pattern = None
        self.size = None
        self.just_run = False
        self.too_long = None

        while sys.argv[1:]:
            if sys.argv[1] == '--datastructure':
                self.datastructure = common.DATASTRUCTURES[python2x3.string_to_binary(sys.argv[2])]
                del sys.argv[1]
            elif sys.argv[1] == '--number-pattern':
                self.number_pattern = common.NUMBER_PATTERN[python2x3.string_to_binary(sys.argv[2])]
                del sys.argv[1]
            elif sys.argv[1] == '--size':
                self.size = int(sys.argv[2])
                del sys.argv[1]
            elif sys.argv[1] == '--just-run':
                self.just_run = True
            elif sys.argv[1] == '--too-long':
                self.too_long = float(sys.argv[2])
                del sys.argv[1]
            elif sys.argv[1] in {'-h', '--help'}:
                usage(0)
            else:
                sys.stderr.write('{}: Unrecognized option: {}\n'.format(sys.argv[0], sys.argv[1]))
                usage(1)
            del sys.argv[1]

    def check_options(self):
        '''Make sure command line options were reasonable'''
        if self.datastructure is None:
            sys.stderr.write('{}: --datastructure is a required option\n'.format(sys.argv[0]))
            usage(1)
        if self.number_pattern is None:
            sys.stderr.write('{}: --number-pattern is a required option\n'.format(sys.argv[0]))
            usage(1)
        if self.size is None:
            sys.stderr.write('{}: --size is a required option\n'.format(sys.argv[0]))
            usage(1)


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

    sys.setrecursionlimit(2 ** 20)

    options = Options()
    options.check_options()

    duration = test(options)

    print('good {}'.format(duration))

    sys.exit(0)


main()