/*
 * Copyright (c) 2011      Oracle and/or its affiliates.  All rights reserved.
 * Copyright (c) 2014 Cisco Systems, Inc.  All rights reserved.
 * $COPYRIGHT$
 *
 * Additional copyrights may follow
 *
 * $HEADER$
 */

#include "opal_config.h"
#include <assert.h>

#include "support.h"
#include "opal/class/opal_tree.h"
#include "opal/runtime/opal.h"
#include "opal/constants.h"

#include <math.h>
#include <string.h>

/*
 * Data type used for testing
 */
typedef struct test_data {
    /* tree data structure */
    opal_tree_item_t tree_element;
    /* test data */
    size_t data;
} test_data_t;

OBJ_CLASS_INSTANCE(test_data_t,
                   opal_tree_item_t,
                   NULL, NULL);

static void check_descendants(opal_tree_item_t* item, unsigned *data,
                              unsigned level, int *err_order,
                              int *err_ancestor);
static int test_comp(opal_tree_item_t *item, void *key);
static int test_serialize(opal_tree_item_t *item, opal_buffer_t *buffer);
static int test_deserialize(opal_buffer_t *serial_data,
			    opal_tree_item_t **item);
static void *test_get_key(opal_tree_item_t *item);

int main(int argc, char **argv)
{
    /* local variables */
    opal_tree_t tree, x;
    opal_buffer_t *serial_tree;
    size_t i, j, tree_size, size_levels, size_elements, total_elements;
    int err_order, err_ancestor, rc;
    unsigned key;
    test_data_t *elements;
    opal_tree_item_t *item, *rm_item;

    rc = opal_init_util(&argc, &argv);
    test_verify_int(OPAL_SUCCESS, rc);
    if (OPAL_SUCCESS != rc) {
        test_finalize();
        exit(1);
    }

    test_init("opal_tree_t");

    /* initialize tree */

    OBJ_CONSTRUCT(&tree, opal_tree_t);
    opal_tree_init(&tree, test_comp, test_serialize, test_deserialize, test_get_key);
    OBJ_CONSTRUCT(&x, opal_tree_t);
    opal_tree_init(&x, test_comp, test_serialize, test_deserialize, test_get_key);

    /* check length of tree */
    tree_size=opal_tree_get_size(&tree);
    if( 0 == tree_size ) {
        test_success();
    } else {
        test_failure(" opal_tree_get_size");
    }

    /* check for empty */
    if (opal_tree_is_empty(&tree)) {
        test_success();
    } else {
        test_failure(" opal_tree_is_empty(empty tree)");
    }

    /* create test elements */
    size_levels = 4;
    size_elements=4;
    total_elements = size_elements * size_levels;
    elements=(test_data_t *)malloc(sizeof(test_data_t)*total_elements);
    assert(elements);
    for(i=0 ; i < total_elements; i++) {
        OBJ_CONSTRUCT(elements + i, test_data_t);
        (elements+i)->data=i;
    }

    /* check get_root */
    item = opal_tree_get_root(&tree);


    /* populate a 4 level tree (this is weighted to the left side) */
    for (i = 0; i < size_levels; i++) {
        for(j=0 ; j < size_elements ; j++) {
            opal_tree_add_child(item,(opal_tree_item_t *)(elements+
                                                          (i*size_elements)+
                                                          j));
        }
        item = opal_tree_get_first_child(item);
    }

    /* checking for tree size */
    tree_size=opal_tree_get_size(&tree);
    if( tree_size == total_elements ) {
        test_success();
    } else {
        test_failure(" count off for populating 4 level tree");
    }

    /* checking for empty on non-empty tree */
    if (!opal_tree_is_empty(&tree)) {
        test_success();
    } else {
        test_failure(" opal_tree_is_empty(non-empty tree)");
    }

    /* check that we have correct tree ordering */
    err_order = 0;
    err_ancestor = 0;
    if (!opal_tree_is_empty(&tree)) {
        item = opal_tree_get_root(&tree);
        i = 0;
        check_descendants(item, (unsigned *)&i, 0, &err_order, &err_ancestor);
    }

    if (!err_order) {
        test_success();
    } else {
        test_failure(" order values incorrect");
    }
    if (!err_ancestor) {
        test_success();
    } else {
        test_failure(" invalid ancestor count");
    }

    /* test matching code */
    /* check for invalid matching */
    key = 444;
    item = opal_tree_find_with(opal_tree_get_root(&tree), (void*)&key);
    if (NULL == item) {
        test_success();
    } else {
        test_failure(" failed invalid matching item test");
    }

    /* check matching, note nest tests because they rely on previous tests */
    /* check for valid matching descendants */
    key = 4;
    item = opal_tree_find_with(opal_tree_get_root(&tree), (void*)&key);
    if (NULL != item && ((test_data_t*)item)->data == key) {
        test_success();
        /* check for valid matching siblings */
        key = 7;
        item = opal_tree_find_with(item, (void*)&key);
        if (NULL != item && ((test_data_t*)item)->data == key) {
            test_success();
            /* check for valid matching ancestors */
            key = 2;
            item = opal_tree_find_with(item, (void*)&key);
            if (NULL != item && ((test_data_t*)item)->data == key) {
                test_success();
            } else {
                test_failure(" failed valid matching ancestors test");
            }
        } else {
            test_failure(" failed valid matching siblings test");
        }
    } else {
        test_failure(" failed valid matching descendants test");
    }

    /* check subtree removal */
    /* find the first key = 3 item and remove it */
    key = 8;
    tree_size=opal_tree_get_size(&tree);
    item = opal_tree_find_with(opal_tree_get_root(&tree), (void*)&key);
    rm_item = opal_tree_remove_subtree(item);
    if (NULL == rm_item) {
        test_failure(" rm_item should not be NULL");
    }
    /* validate the tree count adjusted */
    if (5 != (tree_size - opal_tree_get_size(&tree))) {
	test_failure(" failed subtree removal tree size test");
    } else {
	/* validate cannot find children in tree */
	key = 13;
	if (NULL !=
	    opal_tree_find_with(opal_tree_get_root(&tree), (void*)&key)) {
	    test_failure(" failed subtree removal item children removed test");
	} else {
	    /* validate cannot find the item */
	    key = 8;
	    if (NULL !=
		opal_tree_find_with(opal_tree_get_root(&tree), (void*)&key)) {
		test_failure(" failed subtree removal item removed test");
	    } else {
		test_success();
	    }
	}
    }

    /* check serialization-deserialization */
    /* serialize tree */
    serial_tree = OBJ_NEW(opal_buffer_t);

    if (OPAL_SUCCESS == opal_tree_serialize(opal_tree_get_root(&tree),
					    serial_tree)) {
        opal_tree_t tmp_tree;
        opal_buffer_t *serial2_tree;

        /* create new tree */
        OBJ_CONSTRUCT(&tmp_tree, opal_tree_t);
        opal_tree_init(&tmp_tree, test_comp, test_serialize,
                       test_deserialize, test_get_key);

        /* deserialize tree */
        opal_tree_deserialize(serial_tree, &(tmp_tree.opal_tree_sentinel));
        /* serialize tmp tree */
	serial2_tree = OBJ_NEW(opal_buffer_t);
        if (OPAL_SUCCESS == opal_tree_serialize(opal_tree_get_root(&tmp_tree),
						serial2_tree)) {
	    void *payload1, *payload2;
	    int32_t size1, size2;

	    /* compare new with original serialization */
        serial_tree->unpack_ptr = serial_tree->base_ptr;
        serial2_tree->unpack_ptr = serial2_tree->unpack_ptr;
	    opal_dss.unload(serial_tree, &payload1, &size1);
	    opal_dss.unload(serial2_tree, &payload2, &size2);
	    if (size1 == size2) {
		if (0 == memcmp(payload1, payload2, size1)) {
		    test_success();
		} else {
		    test_failure(" failed tree deserialization data compare");
		}
	    } else {
		test_failure(" failed tree deserialization size compare");
	    }
	} else {
	    test_failure(" failed tree second pass serialization");
	}
    } else {
        test_failure(" failed tree serialization");
    }

    if (NULL != elements) free(elements);

    opal_finalize_util ();

    return test_finalize();
}

/*
 * check all the descendants from our level and below for correct data and
 * level.  Note this will traverse the tree in a weird fashion where you
 * go across all siblings and then start searching down the last siblings
 * children.  As the current tests are set up if one populated more than just
 * the left sided children things will probably fail.
 */
static void check_descendants(opal_tree_item_t* item,
                              unsigned *data,
                              unsigned level,
                              int *err_order, int *err_ancestor)
{
    test_data_t *ele;

    /* loop over all siblings and then down first child  */
    while (item) {
        /* check item for correctness */
        ele = (test_data_t *)item;
        if (ele->data != *data) {
            (*err_order)++;
        }
        if (item->opal_tree_num_ancestors != level) {
            (*err_ancestor)++;
        }
        (*data)++;
        check_descendants(opal_tree_get_next_sibling(item), data, level,
                          err_order, err_ancestor);
        item = opal_tree_get_first_child(item);
        level++;
    }
    return;
}

static int test_comp(opal_tree_item_t *item, void *key)
{
    if (((test_data_t *)item)->data > *((unsigned *) key)) {
        return(1);
    }
    if (((test_data_t *)item)->data < *((unsigned *) key)) {
        return(-1);
    }
    return(0);
}

static int test_serialize(opal_tree_item_t *item, opal_buffer_t *buffer)
{
    test_data_t *ele = (test_data_t *)item;

    return(opal_dss.pack(buffer, &ele->data, 1, OPAL_INT32));
}

static int test_deserialize(opal_buffer_t *serial_data, opal_tree_item_t **item)
{
    int rc = OPAL_SUCCESS, idx = 1;
    test_data_t *ele;

    ele = (test_data_t *)malloc(sizeof(test_data_t));
    OBJ_CONSTRUCT(ele, test_data_t);
    if (OPAL_SUCCESS == (rc = opal_dss.unpack(serial_data, &ele->data, &idx,
					      OPAL_INT32))) {
	*item = (opal_tree_item_t*)ele;
    } else {
	*item = NULL;
    }
    return(rc);
}

static void *test_get_key(opal_tree_item_t *item)
{
    return (void*) (((test_data_t *)item)->data);
}
