#!/usr/bin/env python3
import math
import os
import random
import string
import statistics

import pandas as pd
from redis import Redis as RedisClient
from measure import Measure

DESC = "Redis measure driver for Opsani Optune"
VERSION = "0.0.1"
HAS_CANCEL = True

records = pd.read_csv('hn-posts.csv')

profiles = {
    '1': 'rank',
    '2': 'rank_decayed',
}
profile = os.environ.get('PROFILE', '1')
try:
    rank_field = profiles[profile]
except KeyError:
    raise Exception('Benchmark profile "{}" does not exist.'.format(profile))
r_total = len(records)
r_popular_rank_cutoff = int(os.environ.get('B_RANK_CUTOFF', 115))
r_popular = records[records[rank_field] > r_popular_rank_cutoff]
r_popular_count = len(r_popular)
r_unpopular = records[records[rank_field] <= r_popular_rank_cutoff]
r_unpopular_len = len(r_unpopular)

popular_posts_frac = float(os.environ.get('B_POPULAR_POSTS_FRAC', .8))
average_post_size_in_bytes = int(os.environ.get('B_AVG_POST_SIZE', 50 * 1024))
post_random_contents_fpath = '/tmp/post-random-contents.txt'


def write_random_contents_file(path):
    with open(path, 'w') as f:
        f.write(''.join(random.choice(string.ascii_uppercase + string.digits)
                        for _ in range(average_post_size_in_bytes)))


if not os.path.isfile(post_random_contents_fpath):
    write_random_contents_file(post_random_contents_fpath)
else:
    if os.path.getsize(post_random_contents_fpath) < average_post_size_in_bytes:
        os.remove(post_random_contents_fpath)
        write_random_contents_file(post_random_contents_fpath)

post_contents = open(post_random_contents_fpath, 'r').read()[:average_post_size_in_bytes]

u_posts_read = int(os.environ.get('B_POSTS_PER_USER', 500))
u_popular_count = int(u_posts_read * popular_posts_frac)
u_unpopular_count = u_posts_read - u_popular_count

client = RedisClient(host=os.environ.get('REDIS_HOST'))


def execute_access_pattern():
    r_pop = r_popular.sample(n=u_popular_count, replace=True)
    r_unpop = r_unpopular.sample(n=u_unpopular_count, replace=True)
    ds = r_pop.append(r_unpop).sample(n=u_posts_read, replace=True)
    hits = 0

    for _, r in ds.iterrows():
        if client.get(name=r.id) is None:
            client.set(name=r.id, value=post_contents)
            continue
        hits += 1

    return hits / u_posts_read * 100


class RedisDriver(Measure):
    def measure(self):
        # user_data = self.input_data.setdefault('control', {}).setdefault('userdata', {})

        self.progress = 0
        self.progress_message = "Benchmark started"
        self.print_progress()

        def get_evictions_count():
            return int(client.info('stats')['evicted_keys'])

        # Fill up cache before sampling measurements
        client.config_resetstat()
        fill_up_iters = 10000
        self.progress_message = 'Filling up cache'
        dps = []
        for i in range(fill_up_iters):
            hits = execute_access_pattern()
            self.progress = int((i / fill_up_iters) * 50)

            dps.append(hits)
            if len(dps) > 50:
                dps.pop(0)
            stdev = math.sqrt(statistics.pvariance(dps)) if len(dps) == 50 else 100
            self.progress_message = 'Filling up cache. Stdev: {}'.format(stdev)
            # Evictions become to appear - cache is filled up
            if get_evictions_count() > 0 and stdev < 1.5:
                self.progress = 50
                break

        # Sample measurements (hits to total requests ratio)
        samples = []
        samples_take = int(os.environ.get('B_SAMPLES_COUNT', 50))
        for i in range(samples_take):
            samples.append(execute_access_pattern())
            self.progress = 50 + int(i / samples_take * 50)

        metrics = {
            'hits': {
                'values': [{'data': [(i, frac) for i, frac in enumerate(samples)]}],
                'annotation': "Percentage of hits",
            },
            'perf': {
                'value': None,
                'unit': '% hits',
            },
        }

        self.progress = 100
        self.progress_message = "Benchmark completed"
        self.print_progress()

        return metrics, {}

    def describe(self):
        return {
            'hits': {
                'unit': "percentage",
            },
            'perf': {
                'unit': '% hits',
            }
        }

    def handle_cancel(self, *_, **__):
        print("\nCancelling measurement")
        quit(0)


if __name__ == '__main__':
    driver = RedisDriver(cli_desc=DESC, supports_cancel=HAS_CANCEL, version=VERSION, progress_interval=1)
    driver.run()
