/*
 * Test open hash map.
 */

#include <assert.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include "openhashmap.h"
#include "hashfuncs.h"

#define intequal(a,b) ((a) == (b))

declareOpenHashMap(IMap,int,char const*,hash_int,intequal)
implementOpenHashMap(IMap,int,char const*,hash_int,intequal)

/*
 * Check if M2 is a subset of M1.
 */
static int subset(IMap& m1, IMap& m2) {
    for (IMap_Bindings i = &m1; i.ok(); i.next()) {
	if (! m2.contains(i.key())) {
	    return 0;
	}
	if (m2.fetch(i.key()) != i.val()) {
	    return 0;
	}
    }
    return 1;
}

/*
 * Compare two maps.
 */
static int compare(IMap& m1, IMap& m2) {
    return (subset(m1, m2) && subset(m2, m1));
}

/*
 * Copy map contents into another map.
 */
static void copy(IMap& m1, IMap& m2) {
    /* Clear destination */
    m2.clear();

    /* Copy into destination */
    for (IMap_Bindings iter = &m1; iter.ok(); iter.next()) {
	m2.store(iter.key(), iter.val());
    }
}

/*
 * Get map size by using iterators.
 */
static int num_iterations(const IMap& m) {
    int count = 0;
    for (IMap_Bindings i = &m; i.ok(); i.next()) {
	count++;
    }
    return count;
}

/*
 * Check that iteration over map yields specified key,value pair
 */
static int iteration_contains(const IMap& m, int key, const char* value) {
    for (IMap_Bindings i = &m; i.ok(); i.next()) {
	if (i.key() == key) {
	    assert(i.val() == value);
	    return 1;
	}
    }
    return 0;
}

/*
 * Some common strings.
 */
static const char* one = "one";
static const char* two = "two";
static const char* three = "three";
static const char* four = "four";
static const char* five = "five";

static void black_empty();
static void black_single();
static void black_multiple();

static void black_box() {
    /*
     * Testing strategy -
     *
     * - Operations on empty maps.
     * - Operations on singleton maps.
     * - Operations on larger maps.
     */

    black_empty();
    black_single();
    black_multiple();
}

static void black_empty() {
    /* Empty map tests. */

    int i;
    const char* value;

    IMap empty;
    empty.check();

    /* Check size */
    assert(empty.size() == 0);

    /* Check contains */
    for (i = -3; i <= 3; i++) {
	assert(! empty.contains(i));
    }

    /* Cannot check unary fetch because of requires clause */

    /* Check fetch */
    for (i = -5; i <= 5; i++) {
	assert(! empty.fetch(i, value));
    }

    /* Check iterator */
    {
	assert(num_iterations(empty) == 0);

	IMap_Bindings iter = &empty;
	assert(! iter.ok());
    }

    /* Check store */
    {
	IMap single;
	single.check();
	single.store(1, one);
	single.check();

	assert(single.size() == 1);
	assert(single.contains(1));
	assert(single.fetch(1, value));
	assert(value == one);
    }

    /* Check insert */
    {
	IMap single;
	single.check();
	single.store(1, one);
	single.check();
	
	assert(single.size() == 1);
	assert(single.contains(1));
	assert(single.fetch(1, value));
	assert(value == one);
    }

    /* Check remove */
    {
	IMap empty2;
	empty2.check();

	assert(empty2.size() == 0);
	empty2.remove(1);
	empty2.check();
	assert(empty2.size() == 0);
    }
}

static void black_single() {
    /* Single element map tests */

    int i;
    const char* value;

    IMap single;
    single.check();
    single.store(2, two);
    single.check();

    assert(single.size() == 1);

    /* Check contains */
    for (i = -3; i <= 3; i++) {
	assert(single.contains(i) == (i == 2));
    }

    /* Check fetch */
    assert(single.fetch(2) == two);

    /* Check fetch */
    for (i = -5; i <= 5; i++) {
	assert(single.fetch(i, value) == ((i == 2) && (value == two)));
    }

    /* Check iterator */
    {
	assert(num_iterations(single) == 1);
	assert(iteration_contains(single, 2, two));
    }

    /* Check store */
    {
	IMap temp;
	temp.check();
	copy(single, temp);
	temp.check();

	assert(temp.size() == 1);
	temp.store(2, three);
	temp.check();
	assert(temp.size() == 1);
	assert(temp.contains(2));
	assert(temp.fetch(2) == three);

	copy(single, temp);
	assert(temp.size() == 1);
	temp.store(3, three);
	temp.check();
	assert(temp.size() == 2);
	assert(temp.contains(2));
	assert(temp.contains(3));
	assert(temp.fetch(2) == two);
	assert(temp.fetch(3) == three);

	assert(num_iterations(temp) == 2);
	assert(iteration_contains(temp, 2, two));
	assert(iteration_contains(temp, 3, three));
    }

    /* Check insert */
    {
	IMap temp;
	temp.check();
	copy(single, temp);
	temp.check();

	assert(temp.size() == 1);
	temp.store(3, three);
	temp.check();
	assert(temp.size() == 2);
	assert(temp.contains(2));
	assert(temp.contains(3));
	assert(temp.fetch(2) == two);
	assert(temp.fetch(3) == three);

	assert(num_iterations(temp) == 2);
	assert(iteration_contains(temp, 2, two));
	assert(iteration_contains(temp, 3, three));
    }

    /* Check remove */
    {
	IMap temp;
	temp.check();
	copy(single, temp);
	temp.check();

	temp.remove(5);
	temp.check();
	assert(compare(temp, single));

	temp.remove(2);
	temp.check();
	assert(temp.size() == 0);
	assert(! temp.contains(2));
    }
}

static void black_multiple() {
    int i;
    const char* value;
    IMap multi3, multi4, multi5;

    multi3.check();
    multi3.store(1, one);
    multi3.check();
    multi3.store(2, two);
    multi3.check();
    multi3.store(3, three);
    multi3.check();

    multi4.check();
    multi4.store(1, one);
    multi4.check();
    multi4.store(2, two);
    multi4.check();
    multi4.store(3, three);
    multi4.check();
    multi4.store(4, four);
    multi4.check();

    multi5.check();
    multi5.store(1, one);
    multi5.check();
    multi5.store(2, two);
    multi5.check();
    multi5.store(3, three);
    multi5.check();
    multi5.store(4, four);
    multi5.check();
    multi5.store(5, five);
    multi5.check();

    /* Check size */
    assert(multi3.size() == 3);
    assert(multi4.size() == 4);
    assert(multi5.size() == 5);

    /* Check contains. */
    assert(multi3.contains(1));
    assert(multi3.contains(2));
    assert(multi3.contains(3));

    assert(multi4.contains(1));
    assert(multi4.contains(2));
    assert(multi4.contains(3));
    assert(multi4.contains(4));

    assert(multi5.contains(1));
    assert(multi5.contains(2));
    assert(multi5.contains(3));
    assert(multi5.contains(3));
    assert(multi5.contains(5));

    /* Check fetch */
    assert(multi3.fetch(1) == one);
    assert(multi3.fetch(2) == two);
    assert(multi3.fetch(3) == three);

    assert(multi4.fetch(1) == one);
    assert(multi4.fetch(2) == two);
    assert(multi4.fetch(3) == three);
    assert(multi4.fetch(4) == four);

    assert(multi5.fetch(1) == one);
    assert(multi5.fetch(2) == two);
    assert(multi5.fetch(3) == three);
    assert(multi5.fetch(4) == four);
    assert(multi5.fetch(5) == five);

    /* Check fetch */
    for (i = -5; i <= 0; i++) {
	assert(! multi3.fetch(i, value));
	assert(! multi4.fetch(i, value));
	assert(! multi5.fetch(i, value));
    }
    assert(multi3.fetch(1, value) && (value == one));
    assert(multi3.fetch(2, value) && (value == two));
    assert(multi3.fetch(3, value) && (value == three));
    assert(! multi3.fetch(4, value));
    assert(! multi3.fetch(5, value));

    assert(multi4.fetch(1, value) && (value == one));
    assert(multi4.fetch(2, value) && (value == two));
    assert(multi4.fetch(3, value) && (value == three));
    assert(multi4.fetch(4, value) && (value == four));
    assert(! multi4.fetch(5, value));

    assert(multi5.fetch(1, value) && (value == one));
    assert(multi5.fetch(2, value) && (value == two));
    assert(multi5.fetch(3, value) && (value == three));
    assert(multi5.fetch(4, value) && (value == four));
    assert(multi5.fetch(5, value) && (value == five));

    /* Check iterator */
    {
	assert(num_iterations(multi3) == 3);
	assert(iteration_contains(multi3, 1, one));
	assert(iteration_contains(multi3, 2, two));
	assert(iteration_contains(multi3, 3, three));

	assert(num_iterations(multi4) == 4);
	assert(iteration_contains(multi4, 1, one));
	assert(iteration_contains(multi4, 2, two));
	assert(iteration_contains(multi4, 3, three));
	assert(iteration_contains(multi4, 4, four));

	assert(num_iterations(multi5) == 5);
	assert(iteration_contains(multi5, 1, one));
	assert(iteration_contains(multi5, 2, two));
	assert(iteration_contains(multi5, 3, three));
	assert(iteration_contains(multi5, 4, four));
	assert(iteration_contains(multi5, 5, five));
    }

    /* Check store */
    {
	IMap temp;
	temp.check();
	copy(multi3, temp);
	temp.check();

	assert(compare(multi3, temp));

	/* Store existing element */
	temp.store(2, five);
	temp.check();
	assert(temp.size() == multi3.size());
	assert(temp.contains(2));
	assert(temp.fetch(2) == five);
	temp.remove(2);
	temp.check();
	temp.store(2, multi3.fetch(2));
	temp.check();
	assert(compare(multi3, temp));

	/* Store non-existent element */
	copy(multi4, temp);
	temp.check();
	assert(compare(multi4, temp));
	temp.store(5, five);
	temp.check();
	assert(compare(multi5, temp));
	temp.remove(5);
	temp.check();
	assert(compare(multi4, temp));
    }

    /* Check insert */
    {
	IMap temp;
	temp.check();
	copy(multi4, temp);
	temp.check();

	assert(compare(multi4, temp));
	assert(temp.size() == 4);
	temp.store(5, five);
	temp.check();
	assert(compare(multi5, temp));

	copy(multi3, temp);
	temp.store(4, four);
	temp.check();
	temp.store(5, five);
	temp.check();
	assert(compare(multi5, temp));
    }

    /* Check remove */
    {
	IMap temp, empty;

	/* Check removal of existing elements */
	temp.check();
	copy(multi3, temp);
	temp.check();
	assert(compare(multi3, temp));
	temp.remove(1);
	temp.check();
	temp.remove(2);
	temp.check();
	temp.remove(3);
	temp.check();
	assert(compare(empty, temp));

	copy(multi3, temp);
	temp.check();
	assert(compare(multi3, temp));
	temp.remove(3);
	temp.check();
	temp.remove(2);
	temp.check();
	temp.remove(1);
	temp.check();
	assert(compare(empty, temp));

	copy(multi5, temp);
	temp.check();
	assert(compare(multi5, temp));
	temp.remove(5);
	temp.check();
	assert(compare(multi4, temp));
	temp.remove(4);
	temp.check();
	assert(compare(multi3, temp));
	temp.remove(1);
	temp.check();
	temp.remove(2);
	temp.check();
	temp.remove(3);
	temp.check();
	assert(compare(empty, temp));

	/* Check removal of non-existent elements */
	copy(multi4, temp);
	temp.check();
	for (i = -5; i <= 0; i++) {
	    temp.remove(i);
	    temp.check();
	    assert(compare(multi4, temp));
	}
	for (i = 5; i <= 10; i++) {
	    temp.remove(i);
	    temp.check();
	    assert(compare(multi4, temp));
	}
    }

    /* Check large number of entries */
    {
	IMap map;

	map.check();
	for (i = 0; i < 1000; i++) {
	    char* val = new char[20];
	    sprintf(val, "%d", i);
	    map.store(i, val);
	    assert(num_iterations(map) == i+1);
	}
	map.check();

	char* copy = new char[20];
	for (i = 0; i < 1000; i++) {
	    char const* val = map.fetch(i);
	    sprintf(copy, "%d", i);
	    assert(strcmp(copy, val) == 0);
	}
	delete copy;

	for (i = 0; i < 1000; i++) {
	    delete [] (char*)map.fetch(i);
	    map.remove(i);
	    assert(num_iterations(map) == (999-i));
	}
	map.check();
    }

    /* Check prediction */
    {
	IMap map(1000);

	map.check();
	for (i = 0; i < 1000; i++) {
	    char* val = new char[20];
	    sprintf(val, "%d", i);
	    map.store(i, val);
	    assert(num_iterations(map) == i+1);
	}
	map.check();

	char* copy = new char[20];
	for (i = 0; i < 1000; i++) {
	    char const* val = map.fetch(i);
	    sprintf(copy, "%d", i);
	    assert(strcmp(copy, val) == 0);
	}
	delete copy;

	for (i = 0; i < 1000; i++) {
	    delete [] (char*)map.fetch(i);
	    map.remove(i);
	    assert(num_iterations(map) == (999-i));
	}
	map.check();
    }
}

/*
 * Glass box tests.
 */
static void glass_box() {
    // ...
}

int
main() {
    black_box();
    glass_box();
    return 0;
}
