/*
 * Test open hash set.
 */

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

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

declareOpenHashSet(ISet,int,hash_int,intequal)
implementOpenHashSet(ISet,int,hash_int,intequal)

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

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

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

    /* Copy into destination */
    for (ISet_Elements iter = &m1; iter.ok(); iter.next()) {
	m2.insert(iter.get());
    }
}

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

/*
 * Check that iteration over set yields specified key,value pair
 */
static int iteration_contains(const ISet& m, int key) {
    for (ISet_Elements i = &m; i.ok(); i.next()) {
	if (i.get() == key) {
	    return 1;
	}
    }
    return 0;
}

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

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

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

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

    int i;

    ISet empty;
    empty.check();

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

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

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

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

    /* Check insert */
    {
	ISet single;
	single.check();
	single.insert(1);
	single.check();

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

    /* Check insert */
    {
	ISet single;
	single.check();
	single.insert(1);
	single.check();
	
	assert(single.size() == 1);
	assert(single.contains(1));
    }

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

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

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

    int i;

    ISet single;
    single.check();
    single.insert(2);
    single.check();

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

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

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

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

	assert(temp.size() == 1);
	temp.insert(2);
	temp.check();
	assert(temp.size() == 1);
	assert(temp.contains(2));

	copy(single, temp);
	assert(temp.size() == 1);
	temp.insert(3);
	temp.check();
	assert(temp.size() == 2);
	assert(temp.contains(2));
	assert(temp.contains(3));

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

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

	assert(temp.size() == 1);
	temp.insert(3);
	temp.check();
	assert(temp.size() == 2);
	assert(temp.contains(2));
	assert(temp.contains(3));

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

    /* Check remove */
    {
	ISet 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;
    ISet multi3, multi4, multi5;

    multi3.check();
    multi3.insert(1);
    multi3.check();
    multi3.insert(2);
    multi3.check();
    multi3.insert(3);
    multi3.check();

    multi4.check();
    multi4.insert(1);
    multi4.check();
    multi4.insert(2);
    multi4.check();
    multi4.insert(3);
    multi4.check();
    multi4.insert(4);
    multi4.check();

    multi5.check();
    multi5.insert(1);
    multi5.check();
    multi5.insert(2);
    multi5.check();
    multi5.insert(3);
    multi5.check();
    multi5.insert(4);
    multi5.check();
    multi5.insert(5);
    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 iterator */
    {
	assert(num_iterations(multi3) == 3);
	assert(iteration_contains(multi3, 1));
	assert(iteration_contains(multi3, 2));
	assert(iteration_contains(multi3, 3));

	assert(num_iterations(multi4) == 4);
	assert(iteration_contains(multi4, 1));
	assert(iteration_contains(multi4, 2));
	assert(iteration_contains(multi4, 3));
	assert(iteration_contains(multi4, 4));

	assert(num_iterations(multi5) == 5);
	assert(iteration_contains(multi5, 1));
	assert(iteration_contains(multi5, 2));
	assert(iteration_contains(multi5, 3));
	assert(iteration_contains(multi5, 4));
	assert(iteration_contains(multi5, 5));
    }

    /* Check insert */
    {
	ISet temp;
	temp.check();
	copy(multi3, temp);
	temp.check();

	assert(compare(multi3, temp));

	/* Insert existing element */
	temp.insert(2);
	temp.check();
	assert(temp.size() == multi3.size());
	assert(temp.contains(2));
	temp.remove(2);
	temp.check();
	temp.insert(2);
	temp.check();
	assert(compare(multi3, temp));

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

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

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

	copy(multi3, temp);
	temp.insert(4);
	temp.check();
	temp.insert(5);
	temp.check();
	assert(compare(multi5, temp));
    }

    /* Check remove */
    {
	ISet 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 */
    {
	ISet set;

	set.check();
	for (i = 0; i < 1000; i++) {
	    set.insert(i);
	    assert(num_iterations(set) == i+1);
	}
	set.check();

	for (i = 0; i < 1000; i++) {
	    assert(set.contains(i));
	}

	for (i = 0; i < 1000; i++) {
	    set.remove(i);
	    assert(num_iterations(set) == (999-i));
	}
	set.check();
    }

    /* Check prediction */
    {
	ISet set(1000);

	set.check();
	for (i = 0; i < 1000; i++) {
	    set.insert(i);
	    assert(num_iterations(set) == i+1);
	}
	set.check();

	for (i = 0; i < 1000; i++) {
	    assert(set.contains(i));
	}

	for (i = 0; i < 1000; i++) {
	    set.remove(i);
	    assert(num_iterations(set) == (999-i));
	}
	set.check();
    }
}

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

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