#include <time.h>
#include <string.h>

#include "argon2-impl-select.h"

#include "argon2.h"

#define BENCH_SAMPLES 512
#define BENCH_MEM_BLOCKS 512

static argon2_impl selected_argon_impl = {
    "(default)", NULL, fill_segment_default
};

/* the benchmark routine is not thread-safe, so we can use a global var here: */
static block memory[BENCH_MEM_BLOCKS];

static uint64_t benchmark_impl(const argon2_impl *impl) {
    clock_t time;
    unsigned int i;
    uint64_t bench;
    argon2_instance_t instance;
    argon2_position_t pos;

    memset(memory, 0, sizeof(memory));

    instance.version = ARGON2_VERSION_NUMBER;
    instance.memory = memory;
    instance.passes = 1;
    instance.memory_blocks = BENCH_MEM_BLOCKS;
    instance.segment_length = BENCH_MEM_BLOCKS / ARGON2_SYNC_POINTS;
    instance.lane_length = instance.segment_length * ARGON2_SYNC_POINTS;
    instance.lanes = 1;
    instance.threads = 1;
    instance.type = Argon2_i;

    pos.lane = 0;
    pos.pass = 0;
    pos.slice = 0;
    pos.index = 0;

    /* warm-up cache: */
    impl->fill_segment(&instance, pos);

    /* OK, now measure: */
    bench = 0;
    time = clock();
    for (i = 0; i < BENCH_SAMPLES; i++) {
        impl->fill_segment(&instance, pos);
    }
    time = clock() - time;
    bench = (uint64_t)time;
    return bench;
}

static void select_impl(FILE *out, const char *prefix)
{
    argon2_impl_list impls;
    unsigned int i;
    const argon2_impl *best_impl = NULL;
    uint64_t best_bench = UINT_MAX;

    argon2_get_impl_list(&impls);

    for (i = 0; i < impls.count; i++) {
        const argon2_impl *impl = &impls.entries[i];
        uint64_t bench;

        bench = benchmark_impl(impl);

        if (bench < best_bench) {
            best_bench = bench;
            best_impl = impl;
        }
    }

    if (best_impl != NULL) {
        selected_argon_impl = *best_impl;
    } 
}

void fill_segment(const argon2_instance_t *instance, argon2_position_t position)
{
    selected_argon_impl.fill_segment(instance, position);
}

void argon2_select_impl(FILE *out, const char *prefix)
{
    if (prefix == NULL) {
        prefix = "";
    }
    select_impl(out, prefix);
}