/*
    COLLECTION LIBRARY

    Implementation of the collection library interface.

    Copyright (C) Dmitri Pal <dpal@redhat.com> 2009

    Collection Library is free software: you can redistribute it and/or modify
    it under the terms of the GNU Lesser General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    Collection Library is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU Lesser General Public License for more details.

    You should have received a copy of the GNU Lesser General Public License
    along with Collection Library.  If not, see <http://www.gnu.org/licenses/>.
*/

#include "config.h"
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <ctype.h>
#include <time.h>
#include "trace.h"

/* The collection should use the real structures */
#include "collection_priv.h"
#include "collection.h"

#define INTERRUPT(d) ((d == COL_DSP_LASTDUPNS) || (d == COL_DSP_NDUPNS)) ? 0 : 1

/* Internal constants defined to denote actions that can be performed by find handler */
#define COLLECTION_ACTION_FIND       1
#define COLLECTION_ACTION_DEL        2
#define COLLECTION_ACTION_UPDATE     3
#define COLLECTION_ACTION_GET        4


/* Special internal error code to indicate that collection search was interrupted */
#define EINTR_INTERNAL 10000


/* Magic numbers for hashing */
#if SIZEOF_LONG == 8
    #define FNV1a_prime 1099511628211ul
    #define FNV1a_base 14695981039346656037ul
#elif SIZEOF_LONG_LONG == 8
    #define FNV1a_prime 1099511628211ull
    #define FNV1a_base 14695981039346656037ull
#else
    #error "Platform cannot support 64-bit constant integers"
#endif

/* Struct used for passing parameter for update operation */
struct update_property {
        int type;
        void *data;
        int length;
        int found;
};

/* This struct is used to construct path
 * to an item in the collection (tree)
 */
struct path_data {
    char *name;
    int length;
    struct path_data *previous_path;
};

/* Structure to keep data needed to
 * copy collection
 * while traversing it
 */
struct col_copy {
    int mode;
    struct path_data *current_path;
    char *given_name;
    int given_len;
    col_copy_cb copy_cb;
    void *ext_data;
};

/******************** FUNCTION DECLARATIONS ****************************/

/* Have to declare those due to function cross referencing */
static int col_find_item_and_do(struct collection_item *ci,
                                const char *property_to_find,
                                int type,
                                int mode_flags,
                                col_item_fn item_handler,
                                void *custom_data,
                                int action);

/* Traverse callback for find & delete function */
static int col_act_traverse_handler(struct collection_item *head,
                                    struct collection_item *previous,
                                    struct collection_item *current,
                                    void *passed_traverse_data,
                                    col_item_fn user_item_handler,
                                    void *custom_data,
                                    int *stop);

/* Traverse handler to find parent of the item */
static int col_parent_traverse_handler(struct collection_item *head,
                                       struct collection_item *previous,
                                       struct collection_item *current,
                                       void *traverse_data,
                                       col_item_fn user_item_handler,
                                       void *custom_data,
                                       int *stop);

/* Traverse callback signature */
typedef int (*internal_item_fn)(struct collection_item *head,
                                struct collection_item *previous,
                                struct collection_item *current,
                                void *traverse_data,
                                col_item_fn user_item_handler,
                                void *custom_data,
                                int *stop);
/* Function to walk_items */
static int col_walk_items(struct collection_item *ci,
                          int mode_flags,
                          internal_item_fn traverse_handler,
                          void *traverse_data,
                          col_item_fn user_item_handler,
                          void *custom_data,
                          unsigned *depth);

/* Function to get sub collection */
static int col_get_subcollection(const char *property,
                                  int property_len,
                                  int type,
                                  void *data,
                                  int length,
                                  void *found,
                                  int *dummy);

/* Function to destroy collection */
void col_destroy_collection(struct collection_item *ci);

/******************** SUPPLEMENTARY FUNCTIONS ****************************/
/* BASIC OPERATIONS */

/* Function that checks if property can be added */
static int col_validate_property(const char *property)
{
    TRACE_FLOW_STRING("col_validate_property", "Entry point.");
    /* Only alpha numeric characters are allowed in names of the properties */
    int invalid = 0;
    const char *check;

    check = property;
    while (*check != '\0') {
        /* It turned out that limiting collection charcters is bad */
        if ((*check < ' ') || (*check == '!')) {
            invalid = 1;
            break;
        }
        check++;
    }
    TRACE_FLOW_NUMBER("col_validate_property. Returning ", invalid);
    return invalid;
}


/* Function that cleans the item with callback */
void col_delete_item_with_cb(struct collection_item *item,
                             col_item_cleanup_fn cb,
                             void *custom_data)
{
    struct collection_item *other_collection;

    TRACE_FLOW_STRING("col_delete_item","Entry point.");

    if (item == NULL) {
        TRACE_FLOW_STRING("col_delete_item","Nothing to delete!");
        return;
    }

    /* Handle external or embedded collection */
    if(item->type == COL_TYPE_COLLECTIONREF)  {
        /* Our data is a pointer to a whole external collection so dereference
         * it or delete */
        other_collection = *((struct collection_item **)(item->data));
        col_destroy_collection_with_cb(other_collection, cb, custom_data);
    }

    /* Call the callback */
    if (cb) cb(item->property,
               item->property_len,
               item->type,
               item->data,
               item->length,
               custom_data);

    TRACE_INFO_STRING("Deleting property:", item->property);
    TRACE_INFO_NUMBER("Type:", item->type);

    if (item->property != NULL) free(item->property);
    if (item->data != NULL) free(item->data);

    free(item);

    TRACE_FLOW_STRING("col_delete_item","Exit.");
}

/* Function that cleans the item */
void col_delete_item(struct collection_item *item)
{
    TRACE_FLOW_STRING("col_delete_item","Entry point.");

    col_delete_item_with_cb(item, NULL, NULL);

    TRACE_FLOW_STRING("col_delete_item","Exit.");
}



/* A generic function to allocate a property item */
int col_allocate_item(struct collection_item **ci, const char *property,
                      const void *item_data, int length, int type)
{
    struct collection_item *item = NULL;

    TRACE_FLOW_STRING("col_allocate_item", "Entry point.");
    TRACE_INFO_NUMBER("Will be using type:", type);

    /* Check the length */
    if ((length < 0) || (length >= COL_MAX_DATA) ||
        ((type == COL_TYPE_STRING) && (length == 0))) {
        TRACE_ERROR_STRING("col_allocate_item", "Bad data length.");
        return EMSGSIZE;
    }

    if (col_validate_property(property)) {
        TRACE_ERROR_STRING("Invalid chracters in the property name", property);
        return EINVAL;
    }

    /* Allocate memory for the structure */
    item = (struct collection_item *)malloc(sizeof(struct collection_item));
    if (item == NULL)  {
        TRACE_ERROR_STRING("col_allocate_item", "Malloc failed.");
        return ENOMEM;
    }

    /* After we initialize members we can use delete_item() in case of error */
    item->next = NULL;
    item->property = NULL;
    item->data = NULL;
    TRACE_INFO_NUMBER("About to set type to:", type);
    item->type = type;

    /* Copy property */
    item->property = strdup(property);
    if (item->property == NULL) {
        TRACE_ERROR_STRING("col_allocate_item", "Failed to dup property.");
        col_delete_item(item);
        return ENOMEM;
    }

    item->phash = col_make_hash(property, 0, &(item->property_len));
    TRACE_INFO_NUMBER("Item hash", item->phash);
    TRACE_INFO_NUMBER("Item property length", item->property_len);
    TRACE_INFO_NUMBER("Item property strlen", strlen(item->property));

    /* Deal with data */
    item->data = malloc(length);
    if (length > 0) {
        if (item->data == NULL) {
            TRACE_ERROR_STRING("col_allocate_item", "Failed to dup data.");
            col_delete_item(item);
            return ENOMEM;
        }

        memcpy(item->data, item_data, length);
    }
    item->length = length;

    /* Make sure that data is NULL terminated in case of string */
    if (type == COL_TYPE_STRING) ((char *)(item->data))[length-1] = '\0';

    *ci = item;

    TRACE_INFO_STRING("Item property", item->property);
    TRACE_INFO_NUMBER("Item property type", item->type);
    TRACE_INFO_NUMBER("Item data length", item->length);
    TRACE_FLOW_STRING("col_allocate_item", "Success exit.");
    return EOK;
}

/* Structure used to find things in collection */
struct property_search {
    const char *property;
    uint64_t hash;
    struct collection_item *parent;
    int index;
    int count;
    int found;
    int use_type;
    int type;
    int interrupt;
    int exact;
};

/* Find the parent of the item with given name */
static int col_find_property_sub(struct collection_item *collection,
                                 const char *subcollection,
                                 int interrupt,
                                 const char *refprop,
                                 int idx,
                                 int exact,
                                 int use_type,
                                 int type,
                                 struct collection_item **parent)
{
    struct property_search ps;
    int i = 0;
    unsigned depth = 0;
    struct collection_item *sub = NULL;
    int error = EOK;

    TRACE_FLOW_ENTRY();

    *parent = NULL;

    ps.property = refprop;
    ps.hash = FNV1a_base;
    ps.parent = NULL;
    ps.index = idx;
    ps.count = 0;
    ps.found = 0;
    ps.use_type = use_type;
    ps.type = type;
    ps.interrupt = interrupt;
    ps.exact = exact;

    /* Create hash of the string to search */
    while(refprop[i] != 0) {
        ps.hash = ps.hash ^ toupper(refprop[i]);
        ps.hash *= FNV1a_prime;
        i++;
    }

    /* Add item to collection */
    if (subcollection == NULL) {
        sub = collection;
    }
    else {
        TRACE_INFO_STRING("Subcollection is not null, searching",
                          subcollection);
        error = col_find_item_and_do(collection, subcollection,
                                     COL_TYPE_COLLECTIONREF,
                                     COL_TRAVERSE_DEFAULT,
                                     col_get_subcollection, (void *)(&sub),
                                     COLLECTION_ACTION_FIND);
        if (error) {
            TRACE_ERROR_NUMBER("Search for subcollection returned error:",
                                error);
            /* Not found */
            return 0;
        }

        if (sub == NULL) {
            TRACE_ERROR_STRING("Search for subcollection returned NULL", "");
            /* Not found */
            return 0;
        }

    }

    /* We do not care about error here */
    (void)col_walk_items(sub, COL_TRAVERSE_ONELEVEL,
                         col_parent_traverse_handler,
                         (void *)parent, NULL, (void *)&ps,
                         &depth);

    if (*parent) {
        /* Item is found in the collection */
        TRACE_FLOW_STRING("col_find_property", "Exit - item found");
        return 1;
    }

    /* Item is not found */
    TRACE_FLOW_STRING("col_find_property", "Exit - item NOT found");
    return 0;
}

/* Backward compatible wrapper */
static int col_find_property(struct collection_item *collection,
                             const char *refprop,
                             int idx,
                             int use_type,
                             int type,
                             struct collection_item **parent)
{
    int error = EOK;

    TRACE_FLOW_ENTRY();

    error = col_find_property_sub(collection,
                                  NULL,
                                  1,
                                  refprop,
                                  idx,
                                  0,
                                  use_type,
                                  type,
                                  parent);

    TRACE_FLOW_RETURN(error);
    return error;
}



/* Find a duplicate item */
int col_get_dup_item(struct collection_item *ci,
                     const char *subcollection,
                     const char *property_to_find,
                     int type,
                     int idx,
                     int exact,
                     struct collection_item **item)
{
    int error = EOK;
    struct collection_item *parent = NULL;

    TRACE_FLOW_ENTRY();

    if (!ci) {
        TRACE_ERROR_STRING("Passed in collection is invalid", "");
        return EINVAL;
    }

    if (!item) {
        TRACE_ERROR_STRING("Result storage is invalid", "");
        return EINVAL;
    }

    if (!property_to_find) {
        TRACE_ERROR_STRING("Passed in property to find is NULL", "");
        return EINVAL;
    }

    /* Find the corresponding duplicate item */
    if (col_find_property_sub(ci,
                              subcollection,
                              0,
                              property_to_find,
                              idx,
                              exact,
                              (type == COL_TYPE_ANY) ? 0 : 1,
                              type,
                              &parent)) {
        *item = parent->next;
        /* function col_get_dup_item with big value of idx should
         * return the last item with the same property name. */
        if (strncmp(property_to_find,
                    parent->next->property,
                    parent->next->property_len) != 0) {
            *item = parent;
        }
    }
    else error = ENOENT;

    TRACE_FLOW_EXIT();
    return error;
}

/* Insert item into the current collection */
int col_insert_item_into_current(struct collection_item *collection,
                                 struct collection_item *item,
                                 int disposition,
                                 const char *refprop,
                                 int idx,
                                 unsigned flags)
{
    struct collection_header *header = NULL;
    struct collection_item *parent = NULL;
    struct collection_item *current = NULL;
    int refindex = 0;

    TRACE_FLOW_STRING("col_insert_item_into_current", "Entry point");

    /* Do best effort on the item */
    if ((!item) || (item->next)) {
        TRACE_ERROR_STRING("Passed in item is invalid", "");
        return EINVAL;
    }

    if (collection == NULL) {
        TRACE_INFO_STRING("col_insert_item_into_current",
                          "Collection accepting is NULL");
        if (item->type == COL_TYPE_COLLECTION) {
            /* This is a special case of self creation */
            TRACE_INFO_STRING("col_insert_item_into_current",
                              "Adding header item to new collection.");
            collection = item;
        }
        else {
            TRACE_ERROR_STRING("Passed in item is invalid", "");
            return EINVAL;
        }
    }
    else {
        /* We can add items only to collections */
        if (collection->type != COL_TYPE_COLLECTION) {
            TRACE_ERROR_STRING("Attempt to add item to non collection.","");
            TRACE_ERROR_STRING("Collection name:", collection->property);
            TRACE_ERROR_NUMBER("Collection type:", collection->type);
            return EINVAL;
        }
    }

    /* After processing flags we can process disposition */

    header = (struct collection_header *)collection->data;

    /* Check flags first */
    switch(flags) {
    case COL_INSERT_NOCHECK:    /* No check - good just fall through */
                                TRACE_INFO_STRING("Insert without check", "");
                                break;
    case COL_INSERT_DUPOVER:    /* Find item and overwrite - ignore disposition */
                                if (col_find_property(collection, item->property, 0, 0, 0, &parent)) {
                                    current = parent->next;
                                    item->next = current->next;
                                    parent->next = item;
                                    if (header->last == current) header->last = item;
                                    col_delete_item(current);
                                    /* Deleted one added another - count stays the same! */
                                    TRACE_FLOW_STRING("col_insert_item_into_current", "Dup overwrite exit");
                                    return EOK;
                                }
                                /* Not found so we fall thorough and add as requested */
                                break;

    case COL_INSERT_DUPOVERT:   /* Find item by name and type and overwrite - ignore disposition */
                                if (col_find_property(collection, item->property, 0, 1, item->type, &parent)) {
                                    current = parent->next;
                                    item->next = current->next;
                                    parent->next = item;
                                    if (header->last == current) header->last = item;
                                    col_delete_item(current);
                                    /* Deleted one added another - count stays the same! */
                                    TRACE_FLOW_STRING("col_insert_item_into_current", "Dup overwrite exit");
                                    return EOK;
                                }
                                /* Not found so we fall thorough and add as requested */
                                break;

    case COL_INSERT_DUPERROR:   if (col_find_property(collection, item->property, 0, 0, 0, &parent)) {
                                    /* Return error */
                                    TRACE_ERROR_NUMBER("Duplicate property", EEXIST);
                                    return EEXIST;
                                }
                                break;

    case COL_INSERT_DUPERRORT:  if (col_find_property(collection, item->property, 0, 1, item->type, &parent)) {
                                    /* Return error */
                                    TRACE_ERROR_NUMBER("Duplicate property of the same type", EEXIST);
                                    return EEXIST;
                                }
                                break;

    case COL_INSERT_DUPMOVE:    /* Find item and delete */
                                if (col_find_property(collection, item->property, 0, 0, 0, &parent)) {
                                    current = parent->next;
                                    parent->next = current->next;
                                    if (header->last == current) header->last = parent;
                                    col_delete_item(current);
                                    header->count--;
                                }
                                /* Now add item according to the disposition */
                                break;

    case COL_INSERT_DUPMOVET:   /* Find item and delete */
                                TRACE_INFO_STRING("Property:", item->property);
                                TRACE_INFO_NUMBER("Type:", item->type);
                                if (col_find_property(collection, item->property, 0, 1, item->type, &parent)) {
                                    TRACE_INFO_LNUMBER("Current:", parent->next);
                                    current = parent->next;
                                    parent->next = current->next;
                                    if (header->last == current) header->last = parent;
                                    col_delete_item(current);
                                    header->count--;
                                }
                                /* Now add item according to the disposition */
                                break;

    default:                    /* The new ones should be added here */
                                TRACE_ERROR_NUMBER("Flag is not implemented", ENOSYS);
                                return ENOSYS;
    }


    switch (disposition) {
    case COL_DSP_END:       /* Link new item to the last item in the list if there any */
                            if (header->count != 0) header->last->next = item;
                            /* Make sure we save a new last element */
                            header->last = item;
                            header->count++;
                            break;

    case COL_DSP_FRONT:     /* Same as above if there is header only */
                            if (header->count == 1) {
                                header->last->next = item;
                                header->last = item;
                            }
                            else {
                                item->next = collection->next;
                                collection->next = item;
                            }
                            header->count++;
                            break;

    case COL_DSP_BEFORE:    /* Check argument */
                            if (!refprop) {
                                TRACE_ERROR_STRING("In this case property is required", "");
                                return EINVAL;
                            }

                            /* We need to find property */
                            if (col_find_property(collection, refprop, 0, 0, 0, &parent)) {
                                item->next = parent->next;
                                parent->next = item;
                                header->count++;
                            }
                            else {
                                TRACE_ERROR_STRING("Property not found", refprop);
                                return ENOENT;
                            }
                            break;

    case COL_DSP_AFTER:     /* Check argument */
                            if (!refprop) {
                                TRACE_ERROR_STRING("In this case property is required", "");
                                return EINVAL;
                            }

                            /* We need to find property */
                            if (col_find_property(collection, refprop, 0, 0, 0, &parent)) {
                                parent = parent->next;
                                if (parent->next) {
                                    /* It is not the last item */
                                    item->next = parent->next;
                                    parent->next = item;
                                }
                                else {
                                    /* It is the last item */
                                    header->last->next = item;
                                    header->last = item;
                                }
                                header->count++;
                            }
                            else {
                                TRACE_ERROR_STRING("Property not found", refprop);
                                return ENOENT;
                            }
                            break;

    case COL_DSP_INDEX:     if(idx == 0) {
                                /* Same is first */
                                if (header->count == 1) {
                                    header->last->next = item;
                                    header->last = item;
                                }
                                else {
                                    item->next = collection->next;
                                    collection->next = item;
                                }
                            }
                            else if(idx >= header->count - 1) {
                                /* In this case add to the end */
                                header->last->next = item;
                                /* Make sure we save a new last element */
                                header->last = item;
                            }
                            else {
                                /* In the middle */
                                parent = collection;
                                /* Move to the right position counting */
                                while (idx > 0) {
                                    idx--;
                                    if (parent->next) parent = parent->next;
                                }
                                item->next = parent->next;
                                parent->next = item;
                            }
                            header->count++;
                            break;

    case COL_DSP_FIRSTDUP:
    case COL_DSP_LASTDUP:
    case COL_DSP_NDUP:
    case COL_DSP_LASTDUPNS:
    case COL_DSP_NDUPNS:

                            if (disposition == COL_DSP_FIRSTDUP) refindex = 0;
                            else if ((disposition == COL_DSP_LASTDUP) ||
                                     (disposition == COL_DSP_LASTDUPNS))
                                        refindex = -1;
                            else refindex = idx;

                            /* We need to find property based on index */
                            if (col_find_property_sub(collection,
                                                      NULL,
                                                      INTERRUPT(disposition),
                                                      item->property,
                                                      refindex,
                                                      0,
                                                      0,
                                                      0,
                                                      &parent)) {
                                item->next = parent->next;
                                parent->next = item;
                                header->count++;
                                if(header->last == parent) header->last = item;
                            }
                            else {
                                TRACE_ERROR_STRING("Property not found", refprop);
                                return ENOENT;
                            }
                            break;

    default:
                            TRACE_ERROR_STRING("Disposition is not implemented", "");
                            return ENOSYS;

    }


    TRACE_INFO_STRING("Collection:", collection->property);
    TRACE_INFO_STRING("Just added item is:", item->property);
    TRACE_INFO_NUMBER("Item type.", item->type);
    TRACE_INFO_NUMBER("Number of items in collection now is.", header->count);

    TRACE_FLOW_STRING("col_insert_item_into_current", "Exit");
    return EOK;
}

/* Extract item from the current collection */
int col_extract_item_from_current(struct collection_item *collection,
                                  int disposition,
                                  const char *refprop,
                                  int idx,
                                  int type,
                                  struct collection_item **ret_ref)
{
    struct collection_header *header = NULL;
    struct collection_item *parent = NULL;
    struct collection_item *current = NULL;
    struct collection_item *found = NULL;
    int refindex = 0;
    int use_type = 0;

    TRACE_FLOW_STRING("col_extract_item_from_current", "Entry point");

    /* Check that collection is not empty */
    if ((collection == NULL) || (collection->type != COL_TYPE_COLLECTION)) {
        TRACE_ERROR_STRING("Collection can't be NULL", "");
        return EINVAL;
    }

    header = (struct collection_header *)collection->data;

    /* Before moving forward we need to check if there is anything to extract */
    if (header->count <= 1) {
        TRACE_ERROR_STRING("Collection is empty.", "Nothing to extract.");
        return ENOENT;
    }

    if (type != 0) use_type = 1;

    switch (disposition) {
    case COL_DSP_END:       /* Extract last item in the list. */
                            parent = collection;
                            current = collection->next;
                            while (current->next != NULL) {
                                parent = current;
                                current = current->next;
                            }
                            *ret_ref = parent->next;
                            parent->next = NULL;
                            /* Special case - one data element */
                            if (header->count == 2) header->last = collection;
                            else header->last = parent;
                            break;

    case COL_DSP_FRONT:     /* Extract first item in the list */
                            *ret_ref = collection->next;
                            collection->next = (*ret_ref)->next;
                            /* Special case - one data element */
                            if (header->count == 2) header->last = collection;
                            break;

    case COL_DSP_BEFORE:    /* Check argument */
                            if (!refprop) {
                                TRACE_ERROR_STRING("In this case property is required", "");
                                return EINVAL;
                            }

                            /* We have to do it in two steps */
                            /* First find the property that is mentioned */
                            if (col_find_property(collection, refprop, 0, use_type, type, &found)) {
                                /* We found the requested property */
                                if (found->next == collection->next) {
                                    /* The referenced property is the first in the list */
                                    TRACE_ERROR_STRING("Nothing to extract. Lists starts with property", refprop);
                                    return ENOENT;
                                }
                                /* Get to the parent of the item that is before the one that is found */
                                parent = collection;
                                current = collection->next;
                                while (current != found) {
                                    parent = current;
                                    current = current->next;
                                }
                                *ret_ref = current;
                                parent->next = current->next;

                            }
                            else {
                                TRACE_ERROR_STRING("Property not found", refprop);
                                return ENOENT;
                            }
                            break;

    case COL_DSP_AFTER:     /* Check argument */
                            if (!refprop) {
                                TRACE_ERROR_STRING("In this case property is required", "");
                                return EINVAL;
                            }

                            /* We need to find property */
                            if (col_find_property(collection, refprop, 0, use_type, type, &parent)) {
                                current = parent->next;
                                if (current->next) {
                                    *ret_ref = current->next;
                                    current->next = (*ret_ref)->next;
                                    /* If we removed the last element adjust header */
                                    if(current->next == NULL) header->last = current;
                                }
                                else {
                                    TRACE_ERROR_STRING("Property is last in the list", refprop);
                                    return ENOENT;
                                }
                            }
                            else {
                                TRACE_ERROR_STRING("Property not found", refprop);
                                return ENOENT;
                            }
                            break;

    case COL_DSP_INDEX:     if (idx == 0) {
                                *ret_ref = collection->next;
                                collection->next = (*ret_ref)->next;
                                /* Special case - one data element */
                                if (header->count == 2) header->last = collection;
                            }
                            /* Index 0 stands for the first data element.
                             * Count includes header element.
                             */
                            else if (idx >= (header->count - 1)) {
                                TRACE_ERROR_STRING("Index is out of boundaries", refprop);
                                return ENOENT;
                            }
                            else {
                                /* Loop till the element with right index */
                                refindex = 0;
                                parent = collection;
                                current = collection->next;
                                while (refindex < idx) {
                                    parent = current;
                                    current = current->next;
                                    refindex++;
                                }
                                *ret_ref = parent->next;
                                parent->next = (*ret_ref)->next;
                                /* If we removed the last element adjust header */
                                if (parent->next == NULL) header->last = parent;
                            }
                            break;

    case COL_DSP_FIRSTDUP:
    case COL_DSP_LASTDUP:
    case COL_DSP_NDUP:
    case COL_DSP_LASTDUPNS:
    case COL_DSP_NDUPNS:

                            if (disposition == COL_DSP_FIRSTDUP) refindex = 0;
                            else if ((disposition == COL_DSP_LASTDUP) ||
                                     (disposition == COL_DSP_LASTDUPNS))
                                        refindex = -2;
                            else refindex = idx;

                            /* We need to find property based on index */
                            if (col_find_property_sub(collection,
                                                      NULL,
                                                      INTERRUPT(disposition),
                                                      refprop,
                                                      refindex,
                                                      1,
                                                      use_type,
                                                      type,
                                                      &parent)) {
                                *ret_ref = parent->next;
                                parent->next = (*ret_ref)->next;
                                /* If we removed the last element adjust header */
                                if(parent->next == NULL) header->last = parent;
                            }
                            else {
                                TRACE_ERROR_STRING("Property not found", refprop);
                                return ENOENT;
                            }
                            break;

    default:
                            TRACE_ERROR_STRING("Disposition is not implemented", "");
                            return ENOSYS;

    }


    /* Clear item and reduce count */
    (*ret_ref)->next = NULL;
    header->count--;

    TRACE_INFO_STRING("Collection:", (*ret_ref)->property);
    TRACE_INFO_NUMBER("Item type.", (*ret_ref)->type);
    TRACE_INFO_NUMBER("Number of items in collection now is.", header->count);

    TRACE_FLOW_STRING("col_extract_item_from_current", "Exit");
    return EOK;
}

/* Extract item from the collection */
int col_extract_item(struct collection_item *collection,
                     const char *subcollection,
                     int disposition,
                     const char *refprop,
                     int idx,
                     int type,
                     struct collection_item **ret_ref)
{
    struct collection_item *col = NULL;
    int error = EOK;

    TRACE_FLOW_STRING("col_extract_item", "Entry point");

    /* Check that collection is not empty */
    if ((collection == NULL) || (collection->type != COL_TYPE_COLLECTION)) {
        TRACE_ERROR_STRING("Collection can't be NULL", "");
        return EINVAL;
    }

    /* Get subcollection if needed */
    if (subcollection == NULL) {
        col = collection;
    }
    else {
        TRACE_INFO_STRING("Subcollection id not null, searching", subcollection);
        error = col_find_item_and_do(collection, subcollection,
                                     COL_TYPE_COLLECTIONREF,
                                     COL_TRAVERSE_DEFAULT,
                                     col_get_subcollection, (void *)(&col),
                                     COLLECTION_ACTION_FIND);
        if (error) {
            TRACE_ERROR_NUMBER("Search for subcollection returned error:", error);
            return error;
        }

        if (col == NULL) {
            TRACE_ERROR_STRING("Search for subcollection returned NULL pointer", "");
            return ENOENT;
        }

    }

    /* Extract from the current collection */
    error = col_extract_item_from_current(col,
                                          disposition,
                                          refprop,
                                          idx,
                                          type,
                                          ret_ref);
    if (error) {
        TRACE_ERROR_NUMBER("Failed to extract item from the current collection", error);
        return error;
    }

    TRACE_FLOW_STRING("col_extract_item", "Exit");
    return EOK;
}


/* Remove item (property) from collection with callback.*/
int col_remove_item_with_cb(struct collection_item *ci,
                            const char *subcollection,
                            int disposition,
                            const char *refprop,
                            int idx,
                            int type,
                            col_item_cleanup_fn cb,
                            void *custom_data)
{
    int error = EOK;
    struct collection_item *ret_ref = NULL;

    TRACE_FLOW_STRING("col_remove_item", "Enter");

    /* Extract from the current collection */
    error = col_extract_item(ci,
                             subcollection,
                             disposition,
                             refprop,
                             idx,
                             type,
                             &ret_ref);
    if (error) {
        TRACE_ERROR_NUMBER("Failed to extract item from the collection", error);
        return error;
    }

    col_delete_item_with_cb(ret_ref, cb, custom_data);

    TRACE_FLOW_STRING("col_remove_item", "Exit");
    return EOK;
}

/* Remove item (property) from collection.*/
int col_remove_item(struct collection_item *ci,
                    const char *subcollection,
                    int disposition,
                    const char *refprop,
                    int idx,
                    int type)
{
    return col_remove_item_with_cb(ci,
                                   subcollection,
                                   disposition,
                                   refprop,
                                   idx,
                                   type,
                                   NULL,
                                   NULL);
}

/* Remove item (property) from current collection.
 * Just a simple wrapper.
 */
int col_remove_item_from_current(struct collection_item *ci,
                                 int disposition,
                                 const char *refprop,
                                 int idx,
                                 int type)
{
    int error = EOK;

    TRACE_FLOW_STRING("col_remove_item_from_current", "Exit");

    /* Remove item from current collection */
    error = col_remove_item(ci,
                            NULL,
                            disposition,
                            refprop,
                            idx,
                            type);

    TRACE_FLOW_NUMBER("col_remove_item_from_current. Exit. Returning", error);
    return error;
}


/* Insert the item into the collection or subcollection */
int col_insert_item(struct collection_item *collection,
                    const char *subcollection,
                    struct collection_item *item,
                    int disposition,
                    const char *refprop,
                    int idx,
                    unsigned flags)
{
    int error;
    struct collection_item *acceptor = NULL;

    TRACE_FLOW_STRING("col_insert_item", "Entry point.");

    /* Do best effort on the item */
    if ((!item) || (item->next)) {
        TRACE_ERROR_STRING("Passed in item is invalid", "");
        return EINVAL;
    }

    /* Check that collection is not empty */
    if ((collection == NULL) && (item->type != COL_TYPE_COLLECTION)) {
        TRACE_ERROR_STRING("Collection can't be NULL", "");
        return EINVAL;
    }

    /* Add item to collection */
    if (subcollection == NULL) {
        acceptor = collection;
    }
    else {
        TRACE_INFO_STRING("Subcollection id not null, searching", subcollection);
        error = col_find_item_and_do(collection, subcollection,
                                     COL_TYPE_COLLECTIONREF,
                                     COL_TRAVERSE_DEFAULT,
                                     col_get_subcollection, (void *)(&acceptor),
                                     COLLECTION_ACTION_FIND);
        if (error) {
            TRACE_ERROR_NUMBER("Search for subcollection returned error:", error);
            return error;
        }

        if (acceptor == NULL) {
            TRACE_ERROR_STRING("Search for subcollection returned NULL pointer", "");
            return ENOENT;
        }

    }

    /* Instert item to the current collection */
    error = col_insert_item_into_current(acceptor,
                                         item,
                                         disposition,
                                         refprop,
                                         idx,
                                         flags);

    if (error) {
        TRACE_ERROR_NUMBER("Failed to insert item into current collection", error);
        return error;
    }

    TRACE_FLOW_STRING("insert_item", "Exit");
    return EOK;
}


/* Insert property with reference.
 * This is internal function so we do not check parameters.
 * See external wrapper below.
 */
static int col_insert_property_with_ref_int(struct collection_item *collection,
                                            const char *subcollection,
                                            int disposition,
                                            const char *refprop,
                                            int idx,
                                            unsigned flags,
                                            const char *property,
                                            int type,
                                            const void *data,
                                            int length,
                                            struct collection_item **ret_ref)
{
    struct collection_item *item = NULL;
    int error;

    TRACE_FLOW_STRING("col_insert_property_with_ref_int", "Entry point.");

    /* Create a new property out of the given parameters */
    error = col_allocate_item(&item, property, data, length, type);
    if (error) {
        TRACE_ERROR_NUMBER("Failed to allocate item", error);
        return error;
    }

    /* Send the property to the insert_item function */
    error = col_insert_item(collection,
                            subcollection,
                            item,
                            disposition,
                            refprop,
                            idx,
                            flags);
    if (error) {
        TRACE_ERROR_NUMBER("Failed to insert item", error);
        col_delete_item(item);
        return error;
    }

    if (ret_ref) *ret_ref = item;

    TRACE_FLOW_STRING("col_insert_property_with_ref_int", "Exit");
    return EOK;
}

/* Special function used to copy item from one
 * collection to another using caller's callback.
 */
static int col_copy_item_with_cb(struct collection_item *collection,
                                 const char *property,
                                 int type,
                                 const void *data,
                                 int length,
                                 col_copy_cb copy_cb,
                                 void *ext_data)
{
    struct collection_item *item = NULL;
    int skip = 0;
    int error = EOK;

    TRACE_FLOW_STRING("col_copy_item_with_cb", "Entry point.");

    /* Create a new property out of the given parameters */
    error = col_allocate_item(&item, property, data, length, type);
    if (error) {
        TRACE_ERROR_NUMBER("Failed to allocate item", error);
        return error;
    }

    /* Call callback if any */
    if (copy_cb) {
        TRACE_INFO_STRING("Calling callback for item:", item->property);
        error = copy_cb(item, ext_data, &skip);
        if (error) {
            TRACE_ERROR_NUMBER("Callback failed", error);
            col_delete_item(item);
            return error;
        }
    }

    /* Are we told to skip this item? */
    if (skip) col_delete_item(item);
    else {
        /* Insted property into the collection */
        error = col_insert_item(collection,
                                NULL,
                                item,
                                COL_DSP_END,
                                NULL,
                                0,
                                0);
        if (error) {
            TRACE_ERROR_NUMBER("Failed to insert item", error);
            col_delete_item(item);
            return error;
        }
    }

    TRACE_FLOW_STRING("col_copy_item_with_cb", "Exit");
    return EOK;
}


/* This is public function so we need to check the validity
 * of the arguments.
 */
int col_insert_property_with_ref(struct collection_item *collection,
                                 const char *subcollection,
                                 int disposition,
                                 const char *refprop,
                                 int idx,
                                 unsigned flags,
                                 const char *property,
                                 int type,
                                 const void *data,
                                 int length,
                                 struct collection_item **ret_ref)
{
    int error;

    TRACE_FLOW_STRING("col_insert_property_with_ref", "Entry point.");

    /* Check that collection is not empty */
    if (collection == NULL) {
        TRACE_ERROR_STRING("Collection cant be NULL", "");
        return EINVAL;
    }

    error = col_insert_property_with_ref_int(collection,
                                             subcollection,
                                             disposition,
                                             refprop,
                                             idx,
                                             flags,
                                             property,
                                             type,
                                             data,
                                             length,
                                             ret_ref);

    TRACE_FLOW_NUMBER("col_insert_property_with_ref_int Returning:", error);
    return error;
}
/* TRAVERSE HANDLERS */

/* Special handler to just set a flag if the item is found */
static int col_is_in_item_handler(const char *property,
                                  int property_len,
                                  int type,
                                  void *data,
                                  int length,
                                  void *found,
                                  int *dummy)
{
    TRACE_FLOW_STRING("col_is_in_item_handler", "Entry.");
    TRACE_INFO_STRING("Property:", property);
    TRACE_INFO_NUMBER("Property length:", property_len);
    TRACE_INFO_NUMBER("Type:", type);
    TRACE_INFO_NUMBER("Length:", length);

    *((int *)(found)) = COL_MATCH;

    TRACE_FLOW_STRING("col_is_in_item_handler", "Success Exit.");

    return EOK;
}

/* Special handler to retrieve the sub collection */
static int col_get_subcollection(const char *property,
                                 int property_len,
                                 int type,
                                 void *data,
                                 int length,
                                 void *found,
                                 int *dummy)
{
    TRACE_FLOW_STRING("col_get_subcollection", "Entry.");
    TRACE_INFO_STRING("Property:", property);
    TRACE_INFO_NUMBER("Property length:", property_len);
    TRACE_INFO_NUMBER("Type:", type);
    TRACE_INFO_NUMBER("Length:", length);

    *((struct collection_item **)(found)) = *((struct collection_item **)(data));

    TRACE_FLOW_STRING("col_get_subcollection","Success Exit.");

    return EOK;

}



/* CLEANUP */

/* Cleans the collection tree including current item. */
/* The passed in variable should not be used after the call
 * as memory is freed!!! */
static void col_delete_collection(struct collection_item *ci,
                                  col_item_cleanup_fn cb,
                                  void *custom_data)
{
    TRACE_FLOW_STRING("col_delete_collection", "Entry.");

    if (ci == NULL) {
        TRACE_FLOW_STRING("col_delete_collection", "Nothing to do Exit.");
        return;
    }

    TRACE_INFO_STRING("Real work to do", "");
    TRACE_INFO_STRING("Property", ci->property);
    TRACE_INFO_NUMBER("Next item", ci->next);

    col_delete_collection(ci->next, cb, custom_data);

    /* Delete this item */
    col_delete_item_with_cb(ci, cb, custom_data);
    TRACE_FLOW_STRING("col_delete_collection", "Exit.");
}


/* NAME MANAGEMENT - used by search */

/* Internal data structures used for search */


struct find_name {
    const char *name_to_find;
    int name_len_to_find;
    uint64_t hash;
    int type_to_match;
    char *given_name;
    int given_len;
    struct path_data *current_path;
    int action;
};

/* Create a new name */
static int col_create_path_data(struct path_data **name_path,
                                const char *name, int length,
                                const char *property, int property_len,
                                char sep)
{
    int error = EOK;
    struct path_data *new_name_path;

    TRACE_FLOW_STRING("col_create_path_data", "Entry.");

    TRACE_INFO_STRING("Constructing path from name:", name);
    TRACE_INFO_STRING("Constructing path from property:", property);

    /* Allocate structure */
    new_name_path = (struct path_data *)malloc(sizeof(struct path_data));
    if (new_name_path == NULL) {
        TRACE_ERROR_NUMBER("Failed to allocate memory for new path struct.", ENOMEM);
        return ENOMEM;
    }
    new_name_path->name = malloc(length + property_len + 2);
    if (new_name_path->name == NULL) {
        TRACE_ERROR_NUMBER("Failed to allocate memory for new path name.", ENOMEM);
        free(new_name_path);
        return ENOMEM;
    }

    /* Construct the new name */
    new_name_path->length = 0;

    if(length > 0) {
        memcpy(new_name_path->name, name, length);
        new_name_path->length = length;
        new_name_path->name[new_name_path->length] = sep;
        new_name_path->length++;
        new_name_path->name[new_name_path->length] = '\0';
        TRACE_INFO_STRING("Name so far:", new_name_path->name);
        TRACE_INFO_NUMBER("Len so far:", new_name_path->length);
    }
    memcpy(&new_name_path->name[new_name_path->length], property, property_len);
    new_name_path->length += property_len;
    new_name_path->name[new_name_path->length] = '\0';

    /* Link to the chain */
    new_name_path->previous_path = *name_path;
    *name_path = new_name_path;

    TRACE_INFO_STRING("Constructed path", new_name_path->name);


    TRACE_FLOW_NUMBER("col_create_path_data. Returning:", error);
    return error;
}

/* Matching item name and type */
static int col_match_item(struct collection_item *current,
                          struct find_name *traverse_data)
{

    const char *find_str;
    const char *start;
    const char *data_str;

    TRACE_FLOW_STRING("col_match_item", "Entry");

    if (traverse_data->type_to_match & current->type) {

        /* Check if there is any value to match */
        if ((traverse_data->name_to_find == NULL) ||
            (*(traverse_data->name_to_find) == '\0')) {
            TRACE_INFO_STRING("col_match_item",
                              "Returning MATCH because there is no search criteria!");
            return COL_MATCH;
        }

        /* Check the hashes - if they do not match return */
        if (traverse_data->hash != current->phash) {
            TRACE_INFO_STRING("col_match_item","Returning NO match!");
            return COL_NOMATCH;
        }

        /* We will do the actual string comparison only if the hashes matched */

        /* Start comparing the two strings from the end */
        find_str = traverse_data->name_to_find + traverse_data->name_len_to_find;
        start = current->property;
        data_str = start + current->property_len;

        TRACE_INFO_STRING("Searching for:", traverse_data->name_to_find);
        TRACE_INFO_STRING("Item name:", current->property);
        TRACE_INFO_STRING("Current path:", traverse_data->current_path->name);
        TRACE_INFO_NUMBER("Searching:", toupper(*find_str));
        TRACE_INFO_NUMBER("Have:", toupper(*data_str));

        /* We start pointing to 0 so the loop will be executed at least once */
        while (toupper(*data_str) == toupper(*find_str)) {

            TRACE_INFO_STRING("Loop iteration:","");

            if (data_str == start) {
                if (find_str > traverse_data->name_to_find) {
                    if (*(find_str-1) == '!') {
                        /* We matched the property but the search string is
                         * longer so we need to continue matching */
                        TRACE_INFO_STRING("col_match_item",
                                          "Need to continue matching");
                        start = traverse_data->current_path->name;
                        data_str = &start[traverse_data->current_path->length - 1];
                        find_str -= 2;
                        continue;
                    }
                    else {
                        TRACE_INFO_STRING("col_match_item","Returning NO match!");
                        return COL_NOMATCH;
                    }
                }
                else {
                    TRACE_INFO_STRING("col_match_item","Returning MATCH!");
                    return COL_MATCH;
                }
            }
            else if ((find_str == traverse_data->name_to_find) &&
                     (*(data_str-1) == '!')) return COL_MATCH;

            data_str--;
            find_str--;
            TRACE_INFO_NUMBER("Searching:", toupper(*find_str));
            TRACE_INFO_NUMBER("Have:", toupper(*data_str));

        }
    }

    TRACE_FLOW_STRING("col_match_item","Returning NO match!");
    return COL_NOMATCH;

}

/* Function to delete the data that contains search path */
static void col_delete_path_data(struct path_data *path)
{
    TRACE_FLOW_STRING("col_delete_path_data","Entry.");

    if (path != NULL) {
        TRACE_INFO_STRING("col_delete_path_data", "Item to delete exits.");
        if (path->previous_path != NULL) {
            TRACE_INFO_STRING("col_delete_path_data",
                              "But previous item to delete exits to. Nesting.");
            col_delete_path_data(path->previous_path);
        }
        if (path->name != NULL) {
            TRACE_INFO_STRING("col_delete_path_data Deleting path:", path->name);
            free(path->name);
        }
        TRACE_INFO_STRING("col_delete_path_data", "Deleting path element");
        free(path);
    }
    TRACE_FLOW_STRING("col_delete_path_data", "Exit");
}


/* MAIN TRAVERSAL FUNCTION */

/* Internal function to walk collection */
/* For each item walked it will call traverse handler.
   Traverse handler accepts: current item,
   user provided item handler and user provided custom data. */
/* See below different traverse handlers for different cases */
static int col_walk_items(struct collection_item *ci,
                          int mode_flags,
                          internal_item_fn traverse_handler,
                          void *traverse_data,
                          col_item_fn user_item_handler,
                          void *custom_data,
                          unsigned *depth)
{
    struct collection_item *current;
    struct collection_item *parent = NULL;
    struct collection_item *sub;
    int stop = 0;
    int error = EOK;

    TRACE_FLOW_STRING("col_walk_items", "Entry.");
    TRACE_INFO_NUMBER("Mode flags:", mode_flags);

    /* Increase depth */
    /* NOTE: The depth is increased at the entry to the function.
     * and decreased right before the exit so it is safe to decrease it.
     */
    (*depth)++;

    current = ci;

    while (current) {

        TRACE_INFO_STRING("Processing item:", current->property);
        TRACE_INFO_NUMBER("Item type:", current->type);

        if (current->type == COL_TYPE_COLLECTIONREF) {

            TRACE_INFO_STRING("Subcollection:", current->property);

            if ((mode_flags & COL_TRAVERSE_IGNORE) == 0) {

                TRACE_INFO_STRING("Subcollection is not ignored.", "");
                /* We are not ignoring sub collections */

                if ((mode_flags & COL_TRAVERSE_FLAT) == 0) {

                    TRACE_INFO_STRING("Subcollection is not flattened.", "");
                    /* We are not flattening sub collections.
                     * The flattening means that we are not going
                     * to return reference and headers for sub collections.
                     * We will also not do special end collection
                     * invocation for sub collections.
                     */
                    error = traverse_handler(ci, parent, current, traverse_data,
                                             user_item_handler, custom_data, &stop);
                    if (stop != 0) {
                        TRACE_INFO_STRING("Traverse handler returned STOP.", "");
                        error = EINTR_INTERNAL;
                    }
                    /* Check what error we got */
                    if (error == EINTR_INTERNAL) {
                        TRACE_FLOW_NUMBER("Internal error - means we are stopping.", error);
                        (*depth)--;
                        return error;
                    }
                    else if (error) {
                        TRACE_ERROR_NUMBER("Traverse handler returned error.", error);
                        (*depth)--;
                        return error;
                    }
                }

                if ((mode_flags & COL_TRAVERSE_ONELEVEL) == 0) {
                    TRACE_INFO_STRING("Before diving into sub collection","");
                    sub = *((struct collection_item **)(current->data));
                    TRACE_INFO_STRING("Sub collection name", sub->property);
                    TRACE_INFO_NUMBER("Header type", sub->type);
                    /* We need to go into sub collections */
                    error = col_walk_items(sub, mode_flags,
                                           traverse_handler, traverse_data,
                                           user_item_handler, custom_data,
                                           depth);
                    TRACE_INFO_STRING("Returned from sub collection processing", "");
                    TRACE_INFO_STRING("Done processing item:", current->property);
                    TRACE_INFO_NUMBER("Done processing item type:", current->type);

                }
            }
        }
        else {
            /* Check if it is a header and we are not on the root level.
             * If we are flattening collection we need to skip headers
             * for sub collections.
             */

            /* Call handler if:
             * a) It is not collection header
             * OR
             * b) It is header we are flattening but we are on top level
             * OR
             * c) It is header and we are not flattening.
             */
            if ((current->type != COL_TYPE_COLLECTION) ||
                (((mode_flags & COL_TRAVERSE_FLAT) != 0) && (*depth == 1)) ||
                ((mode_flags & COL_TRAVERSE_FLAT) == 0)) {
                /* Call handler then move on */
                error = traverse_handler(ci, parent, current,
                                         traverse_data, user_item_handler,
                                         custom_data, &stop);

            }
        }
        /* If we are stopped - return EINTR_INTERNAL */
        if (stop != 0) {
            TRACE_INFO_STRING("Traverse handler returned STOP.", "");
            error = EINTR_INTERNAL;
        }
        /* Check what error we got */
        if (error == EINTR_INTERNAL) {
            TRACE_FLOW_NUMBER("Internal error - means we are stopping.", error);
            (*depth)--;
            return error;
        }
        else if (error) {
            TRACE_ERROR_NUMBER("Traverse handler returned error.", error);
            (*depth)--;
            return error;
        }

        TRACE_INFO_NUMBER("Next element", current->next);

        parent = current;
        current = current->next;
    }

    TRACE_INFO_STRING("Out of loop", "");

    /* Check if we need to have a special
     * call at the end of the collection.
     */
    if ((mode_flags & COL_TRAVERSE_END) != 0) {

        /* Do this dummy invocation only:
         * a) If we are flattening and on the root level
         * b) We are not flattening
         */
        if ((((mode_flags & COL_TRAVERSE_FLAT) != 0) && (*depth == 1)) ||
            ((mode_flags & COL_TRAVERSE_FLAT) == 0)) {

            TRACE_INFO_STRING("About to do the special end collection invocation of handler", "");
            error = traverse_handler(ci, parent, current,
                                     traverse_data, user_item_handler,
                                     custom_data, &stop);
        }
    }

    TRACE_FLOW_NUMBER("col_walk_items. Returns: ", error);
    (*depth)--;
    return error;
}


/* ACTION */

/* Find an item by property name and perform an action on it. */
/* No pattern matching supported in the first implementation. */
/* To refer to child properties use notatation like this: */
/* parent!child!subchild!subsubchild etc.  */
static int col_find_item_and_do(struct collection_item *ci,
                                const char *property_to_find,
                                int type,
                                int mode_flags,
                                col_item_fn item_handler,
                                void *custom_data,
                                int action)
{

    int error = EOK;
    struct find_name *traverse_data = NULL;
    unsigned depth = 0;
    int count = 0;
    const char *last_part;
    char *sep;

    TRACE_FLOW_STRING("col_find_item_and_do", "Entry.");

    /* Item handler is always required */
    if ((item_handler == NULL) &&
        (action == COLLECTION_ACTION_FIND)) {
        TRACE_ERROR_NUMBER("No item handler - returning error!", EINVAL);
        return EINVAL;
    }

    /* Collection is requered */
    if (ci == NULL) {
        TRACE_ERROR_NUMBER("No collection to search!", EINVAL);
        return EINVAL;
    }

    /* Make sure that there is anything to search */
    type &= COL_TYPE_ANY;
    if ((type == 0) &&
        ((property_to_find == NULL) ||
         ((property_to_find != NULL) && (*property_to_find == '\0')))) {
        TRACE_ERROR_NUMBER("No item search criteria specified - returning error!", ENOENT);
        return ENOENT;
    }

    /* Prepare data for traversal */
    traverse_data = (struct find_name *)malloc(sizeof(struct find_name));
    if (traverse_data == NULL) {
        TRACE_ERROR_NUMBER("Failed to allocate traverse data memory - returning error!", ENOMEM);
        return ENOMEM;
    }

    TRACE_INFO_STRING("col_find_item_and_do", "Filling in traverse data.");

    traverse_data->name_to_find = property_to_find;

    if (property_to_find != NULL) {

        traverse_data->name_len_to_find = strlen(property_to_find);

        /* Check if the search string ends with "!" - this is illegal */
        if (traverse_data->name_to_find[traverse_data->name_len_to_find - 1] == '!') {
            TRACE_ERROR_NUMBER("Search string is invalid.", EINVAL);
            free(traverse_data);
            return EINVAL;
        }

        /* Find last ! if any */
        sep = strrchr(traverse_data->name_to_find, '!');
        if (sep != NULL) {
            sep++;
            last_part = sep;
        }
        else last_part = traverse_data->name_to_find;

        TRACE_INFO_STRING("Last item", last_part);

        /* Create hash of the last part */
        traverse_data->hash = FNV1a_base;

        /* Create hash of the string to search */
        while(last_part[count] != 0) {
            traverse_data->hash = traverse_data->hash ^ toupper(last_part[count]);
            traverse_data->hash *= FNV1a_prime;
            count++;
        }
    }
    else {
        /* We a looking for a first element of a given type */
        TRACE_INFO_STRING("No search string", "");
        traverse_data->name_len_to_find = 0;
    }


    traverse_data->type_to_match = type;
    traverse_data->given_name = NULL;
    traverse_data->given_len = 0;
    traverse_data->current_path = NULL;
    traverse_data->action = action;

    mode_flags |= COL_TRAVERSE_END;

    TRACE_INFO_STRING("col_find_item_and_do", "About to walk the tree.");
    TRACE_INFO_NUMBER("Traverse flags", mode_flags);

    error = col_walk_items(ci, mode_flags, col_act_traverse_handler,
                           (void *)traverse_data, item_handler, custom_data,
                           &depth);

    if (traverse_data->current_path != NULL) {
        TRACE_INFO_STRING("find_item_and_do",
                          "Path was not cleared - deleting");
        col_delete_path_data(traverse_data->current_path);
    }

    free(traverse_data);

    if (error && (error != EINTR_INTERNAL)) {
        TRACE_ERROR_NUMBER("Walk items returned error. Returning: ", error);
        return error;
    }
    else {
        TRACE_FLOW_STRING("Walk items returned SUCCESS.", "");
        return EOK;
    }
}

/* Function to replace data in the item */
static int col_update_current_item(struct collection_item *current,
                                   struct update_property *update_data)
{
    TRACE_FLOW_STRING("col_update_current_item", "Entry");

    /* If type is different or same but it is string or binary we need to
     * replace the storage */
    if ((current->type != update_data->type) ||
        ((current->type == update_data->type) &&
        ((current->type == COL_TYPE_STRING) ||
         (current->type == COL_TYPE_BINARY)))) {
        TRACE_INFO_STRING("Replacing item data buffer", "");
        free(current->data);
        current->data = malloc(update_data->length);
        if (current->data == NULL) {
            TRACE_ERROR_STRING("Failed to allocate memory", "");
            current->length = 0;
            return ENOMEM;
        }
        current->length = update_data->length;
    }

    TRACE_INFO_STRING("Overwriting item data", "");
    memcpy(current->data, update_data->data, current->length);
    current->type = update_data->type;

    if (current->type == COL_TYPE_STRING)
        ((char *)(current->data))[current->length-1] = '\0';

    TRACE_FLOW_STRING("update_current_item", "Exit");
    return EOK;
}

/* TRAVERSE CALLBACKS */

/* Traverse handler for simple traverse function */
/* Handler must be able to deal with NULL current item */
static int col_simple_traverse_handler(struct collection_item *head,
                                       struct collection_item *previous,
                                       struct collection_item *current,
                                       void *traverse_data,
                                       col_item_fn user_item_handler,
                                       void *custom_data,
                                       int *stop)
{
    int error = EOK;
    struct collection_item end_item;
    char zero = '\0';

    TRACE_FLOW_STRING("col_simple_traverse_handler", "Entry.");

    if (current == NULL) {
        memset((void *)&end_item, 0, sizeof(struct collection_item));
        end_item.type = COL_TYPE_END;
        end_item.property = &zero;
        current = &end_item;
    }

    error = user_item_handler(current->property,
                              current->property_len,
                              current->type,
                              current->data,
                              current->length,
                              custom_data,
                              stop);

    TRACE_FLOW_NUMBER("col_simple_traverse_handler. Returning:", error);
    return error;
}

/* Traverse handler for to find parent */
static int col_parent_traverse_handler(struct collection_item *head,
                                       struct collection_item *previous,
                                       struct collection_item *current,
                                       void *traverse_data,
                                       col_item_fn user_item_handler,
                                       void *custom_data,
                                       int *stop)
{
    struct property_search *to_find;
    int done = 0;
    int match = 0;

    TRACE_FLOW_STRING("col_parent_traverse_handler", "Entry.");

    to_find = (struct property_search *)custom_data;

    TRACE_INFO_NUMBER("Looking for HASH:", (unsigned)(to_find->hash));
    TRACE_INFO_NUMBER("Current HASH:", (unsigned)(current->phash));

    /* Check hashes first */
    if(to_find->hash == current->phash) {

        /* Check type if we are asked to use type */
        if ((to_find->use_type) && (!(to_find->type & current->type))) {
            TRACE_FLOW_STRING("parent_traverse_handler. Returning:","Exit. Hash is Ok, type is not");
            return EOK;
        }

        /* Validate property. Make sure we include terminating 0 in the comparison */
        if (strncasecmp(current->property, to_find->property, current->property_len + 1) == 0) {

            match = 1;
            to_find->found = 1;

            /* Do the right thing based on index */
            /* If index is 0 we are looking for the first value in the list of duplicate properties */
            if (to_find->index == 0) done = 1;
            /* If index is non zero we are looking for N-th instance of the dup property */
            else if (to_find->index > 0) {
                if (to_find->count == to_find->index) done = 1;
                else {
                    /* Record found instance and move on */
                    to_find->parent = previous;
                    (to_find->count)++;
                }
            }
            /* If we are looking for last instance just record it */
            else to_find->parent = previous;
        }
    }

    if (done) {
        *stop = 1;
        *((struct collection_item **)traverse_data) = previous;
    }
    else {
        if (to_find->interrupt) {
            /* As soon as we found first non matching one but there was a match
             * return the parent of the last found item.
             */
            if (((!match) || (current->next == NULL)) &&
                 (to_find->index != 0) && (to_find->found)) {
                *stop = 1;
                if (to_find->index == -2)
                    *((struct collection_item **)traverse_data) =
                                                 to_find->parent;
                else
                    if (to_find->exact) {
                        /* This means that we need to match the exact
                         * index but we did not */
                        to_find->parent = NULL;
                        to_find->found = 0;
                    }
                    else
                        *((struct collection_item **)traverse_data) =
                                                 to_find->parent->next;
            }
        }
        else if ((current->next == NULL) && (to_find->found)) {
                *stop = 1;
                if (to_find->index == -2)
                    *((struct collection_item **)traverse_data) =
                                                 to_find->parent;
                else
                    if (to_find->exact) {
                        /* This means that we need to match the exact
                         * index but we did not */
                        to_find->parent = NULL;
                        to_find->found = 0;
                    }
                    else
                        *((struct collection_item **)traverse_data) =
                                                 to_find->parent->next;
        }
    }


    TRACE_FLOW_STRING("col_parent_traverse_handler. Returning:","Exit");
    return EOK;
}


/* Traverse callback for find & delete function */
static int col_act_traverse_handler(struct collection_item *head,
                                    struct collection_item *previous,
                                    struct collection_item *current,
                                    void *passed_traverse_data,
                                    col_item_fn user_item_handler,
                                    void *custom_data,
                                    int *stop)
{
    int error = EOK;
    struct find_name *traverse_data = NULL;
    char *name;
    int length;
    struct path_data *temp;
    struct collection_header *header;
    char *property;
    int property_len;
    struct update_property *update_data;

    TRACE_FLOW_STRING("col_act_traverse_handler", "Entry.");

    traverse_data = (struct find_name *)passed_traverse_data;

    /* We can be called when current points to NULL */
    if (current == NULL) {
        TRACE_INFO_STRING("col_act_traverse_handler",
                          "Special call at the end of the collection.");
        temp = traverse_data->current_path;
        traverse_data->current_path = temp->previous_path;
        temp->previous_path = NULL;
        col_delete_path_data(temp);
        traverse_data->given_name = NULL;
        traverse_data->given_len = 0;
        TRACE_FLOW_NUMBER("Handling end of collection - removed path. Returning:", error);
        return error;
    }

    /* Create new path at the beginning of a new sub collection */
    if (current->type == COL_TYPE_COLLECTION) {

        TRACE_INFO_STRING("col_act_traverse_handler",
                          "Processing collection handle.");

        /* Create new path */
        if (traverse_data->current_path != NULL) {
            TRACE_INFO_STRING("Already have part of the path", "");
            name = traverse_data->current_path->name;
            length = traverse_data->current_path->length;
            TRACE_INFO_STRING("Path:", name);
            TRACE_INFO_NUMBER("Path len:", length);
        }
        else {
            name = NULL;
            length = 0;
        }

        if (traverse_data->given_name != NULL) {
            property = traverse_data->given_name;
            property_len = traverse_data->given_len;
        }
        else {
            property = current->property;
            property_len = current->property_len;
        }

        TRACE_INFO_STRING("col_act_traverse_handler", "About to create path data.");

        error = col_create_path_data(&(traverse_data->current_path),
                                     name, length,
                                     property, property_len, '!');

        TRACE_INFO_NUMBER("col_create_path_data returned:", error);
        return error;
    }

    /* Handle the collection pointers */
    if (current->type == COL_TYPE_COLLECTIONREF) {
        traverse_data->given_name = current->property;
        traverse_data->given_len = current->property_len;
        TRACE_INFO_STRING("Saved given name:", traverse_data->given_name);
    }

    TRACE_INFO_STRING("Processing item with property:", current->property);

    /* Do here what we do with items */
    if (col_match_item(current, traverse_data)) {
        TRACE_INFO_STRING("Matched item:", current->property);
        switch (traverse_data->action) {
        case COLLECTION_ACTION_FIND:
            TRACE_INFO_STRING("It is a find action - calling handler.", "");
            if (user_item_handler != NULL) {
                /* Call user handler */
                error = user_item_handler(current->property,
                                          current->property_len,
                                          current->type,
                                          current->data,
                                          current->length,
                                          custom_data,
                                          stop);

                TRACE_INFO_NUMBER("Handler returned:", error);
                TRACE_INFO_NUMBER("Handler set STOP to:", *stop);

            }
            break;

        case COLLECTION_ACTION_GET:
            TRACE_INFO_STRING("It is a get action.", "");
            if (custom_data != NULL)
                *((struct collection_item **)(custom_data)) = current;
            break;

        case COLLECTION_ACTION_DEL:
            TRACE_INFO_STRING("It is a delete action.", "");
            /* Make sure we tell the caller we found a match */
            if (custom_data != NULL)
                *(int *)custom_data = COL_MATCH;

            /* Adjust header of the collection */
            header = (struct collection_header *)head->data;
            header->count--;
            if (current->next == NULL)
                header->last = previous;

            /* Unlink and delete iteam */
            /* Previous can't be NULL here becuase we never delete
             * header elements */
            previous->next = current->next;
            col_delete_item(current);
            TRACE_INFO_STRING("Did the delete of the item.", "");
            break;

        case COLLECTION_ACTION_UPDATE:
            TRACE_INFO_STRING("It is an update action.", "");
            if((current->type == COL_TYPE_COLLECTION) ||
               (current->type == COL_TYPE_COLLECTIONREF)) {
                TRACE_ERROR_STRING("Can't update collections it is an error for now", "");
                return EINVAL;
            }

            /* Make sure we tell the caller we found a match */
            if (custom_data != NULL) {
                update_data = (struct update_property *)custom_data;
                update_data->found = COL_MATCH;
                error = col_update_current_item(current, update_data);
            }
            else {
                TRACE_ERROR_STRING("Error - update data is required", "");
                return EINVAL;
            }

            TRACE_INFO_STRING("Did the delete of the item.", "");
            break;
        default:
            break;
        }
        /* Force interrupt if we found */
        *stop = 1;
    }

    TRACE_FLOW_NUMBER("col_act_traverse_handler returning", error);
    return error;
}


/* Traverse handler for copy function */
static int col_copy_traverse_handler(struct collection_item *head,
                                     struct collection_item *previous,
                                     struct collection_item *current,
                                     void *passed_traverse_data,
                                     col_item_fn user_item_handler,
                                     void *custom_data,
                                     int *stop)
{
    int error = EOK;
    struct collection_item *parent;
    struct collection_item *other = NULL;
    struct col_copy *traverse_data;
    struct path_data *temp;
    char *name;
    int length;
    char *property = NULL;
    int property_len;
    struct collection_header *header;
    char *offset;

    TRACE_FLOW_STRING("col_copy_traverse_handler", "Entry.");

    parent = (struct collection_item *)custom_data;
    traverse_data = (struct col_copy *)passed_traverse_data;

    /* We can be called when current points to NULL */
    /* This will happen only in the FLATDOT case */
    if (current == NULL) {
        TRACE_INFO_STRING("col_copy_traverse_handler",
                          "Special call at the end of the collection.");
        temp = traverse_data->current_path;
        traverse_data->current_path = temp->previous_path;
        temp->previous_path = NULL;
        col_delete_path_data(temp);
        traverse_data->given_name = NULL;
        traverse_data->given_len = 0;
        TRACE_FLOW_NUMBER("Handling end of collection - removed path. Returning:", error);
        return error;
    }

    /* Create new path at the beginning of a new sub collection */
    if (current->type == COL_TYPE_COLLECTION) {

        TRACE_INFO_STRING("col_copy_traverse_handler",
                          "Processing collection handle.");
        if (traverse_data->mode == COL_COPY_FLATDOT) {
            /* Create new path */
            if (traverse_data->current_path != NULL) {
                TRACE_INFO_STRING("Already have part of the path", "");
                name = traverse_data->current_path->name;
                length = traverse_data->current_path->length;
                TRACE_INFO_STRING("Path:", name);
                TRACE_INFO_NUMBER("Path len:", length);
                if (traverse_data->given_name != NULL) {
                    property = traverse_data->given_name;
                    property_len = traverse_data->given_len;
                }
                else {
                    property = current->property;
                    property_len = current->property_len;
                }
            }
            else {
                /* Do not create prefix for top collection
                 * if there is no given name.
                 */
                name = NULL;
                length = 0;
                if (traverse_data->given_name != NULL) {
                    property = traverse_data->given_name;
                    property_len = traverse_data->given_len;
                }
                else {
                    property = NULL;
                    property_len = 0;
                }
            }

            TRACE_INFO_STRING("col_copy_traverse_handler", "About to create path data.");

            error = col_create_path_data(&(traverse_data->current_path),
                                         name, length,
                                         property, property_len, '.');

            TRACE_FLOW_NUMBER("col_copy_traverse_handler processed header:", error);
            return error;
        }
        else {
            TRACE_FLOW_NUMBER("col_copy_traverse_handler skipping the header:", error);
            return error;
        }
    }


    /* Check if this is a special case of sub collection */
    if (current->type == COL_TYPE_COLLECTIONREF) {

        TRACE_INFO_STRING("Found a subcollection we need to copy. Name:",
                          current->property);

        /* Based on the mode we need to do different things */
        switch (traverse_data->mode) {
        case COL_COPY_NORMAL:

            error = col_copy_collection_with_cb(&other,
                                        *((struct collection_item **)(current->data)),
                                        current->property,
                                        COL_COPY_NORMAL,
                                        traverse_data->copy_cb,
                                        traverse_data->ext_data);
            if (error) {
                TRACE_ERROR_NUMBER("Copy subcollection returned error:", error);
                return error;
            }

            /* Add new item to a collection
             * all references are now sub collections */
            error = col_insert_property_with_ref_int(parent,
                                                     NULL,
                                                     COL_DSP_END,
                                                     NULL,
                                                     0,
                                                     0,
                                                     current->property,
                                                     COL_TYPE_COLLECTIONREF,
                                                     (void *)(&other),
                                                     sizeof(struct collection_item **),
                                                     NULL);

            TRACE_FLOW_NUMBER("col_copy_traverse_handler returning in NORMAL mode:", error);
            return error;

       case COL_COPY_KEEPREF:

            /* Just increase reference count of the referenced collection */
			other = *((struct collection_item **)(current->data));
            header = (struct collection_header *)(other->data);
            header->reference_count++;

            /* Add new item to a collection
             * all references are now sub collections */
            error = col_insert_property_with_ref_int(parent,
                                                     NULL,
                                                     COL_DSP_END,
                                                     NULL,
                                                     0,
                                                     0,
                                                     current->property,
                                                     COL_TYPE_COLLECTIONREF,
                                                     (void *)(&other),
                                                     sizeof(struct collection_item **),
                                                     NULL);
            TRACE_FLOW_NUMBER("col_copy_traverse_handler returning in KEEPREF mode:", error);
            return error;

        case COL_COPY_TOP:
            /* Told to ignore sub collections */
            TRACE_FLOW_NUMBER("col_copy_traverse_handler returning in TOP mode:", error);
            return error;

        case COL_COPY_FLATDOT:

            traverse_data->given_name = current->property;
            traverse_data->given_len = current->property_len;
            TRACE_INFO_STRING("Saved given name:", traverse_data->given_name);
            TRACE_FLOW_NUMBER("col_copy_traverse_handler returning in FLATDOT mode:", error);
            return error;

        /* NOTE: The mode COL_COPY_FLAT is not in the list of cases becuase
         * in this flat mode we traverse collection using COL_TRAVERSE_FLAT flag
         * thus we should not be called on referenced collections at all
         * by the col_walk_items() function.
         */

        default:
            TRACE_ERROR_NUMBER("col_copy_traverse_handler bad mode error:", EINVAL);
            return EINVAL;
        }
    }
    else {

        if (traverse_data->mode == COL_COPY_FLATDOT) {
            /* Since this code can't use asprintf have to do it hard way */
            property = malloc(traverse_data->current_path->length +
                              current->property_len + 2);
            if (property == NULL) {
                TRACE_ERROR_NUMBER("Failed to allocate memory for a new name:", error);
                return error;
            }
            /* Add first part and dot only if we have prefix */
            offset = property;
            if (traverse_data->current_path->length) {
                memcpy(offset, traverse_data->current_path->name,
                       traverse_data->current_path->length);
                offset[traverse_data->current_path->length] = '.';
                offset += traverse_data->current_path->length + 1;
            }
            memcpy(offset, current->property, current->property_len);
            offset[current->property_len] = '\0';
        }
        else property = current->property;

        TRACE_INFO_STRING("Using property:", property);

        error = col_copy_item_with_cb(parent,
                                      property,
                                      current->type,
                                      current->data,
                                      current->length,
                                      traverse_data->copy_cb,
                                      traverse_data->ext_data);

        /* Free property if we allocated it */
        if (traverse_data->mode == COL_COPY_FLATDOT) free(property);

        if (error) {
            TRACE_ERROR_NUMBER("Failed to copy property:", error);
            return error;
        }
    }

    TRACE_FLOW_NUMBER("col_copy_traverse_handler returning", error);
    return error;
}




/********************* MAIN INTERFACE FUNCTIONS *****************************/


/* CREATE */

/* Function that creates an named collection of a given class*/
int col_create_collection(struct collection_item **ci, const char *name,
                          unsigned cclass)
{
    struct collection_item *handle = NULL;
    struct collection_header header;
    int error = EOK;

    TRACE_FLOW_STRING("col_create_collection", "Entry.");

    /* Prepare header */
    header.last = NULL;
    header.reference_count = 1;
    header.count = 0;
    header.cclass = cclass;

    /* Create a collection type property */
    error = col_insert_property_with_ref_int(NULL,
                                             NULL,
                                             COL_DSP_END,
                                             NULL,
                                             0,
                                             0,
                                             name,
                                             COL_TYPE_COLLECTION,
                                             &header,
                                             sizeof(header),
                                             &handle);


    if (error) return error;

    *ci = handle;

    TRACE_FLOW_STRING("col_create_collection", "Success Exit.");
    return EOK;
}


/* DESTROY */

/* Function that destroys a collection */
void col_destroy_collection_with_cb(struct collection_item *ci,
                                    col_item_cleanup_fn cb,
                                    void *custom_data)
{
    struct collection_header *header;

    TRACE_FLOW_STRING("col_destroy_collection_with_cb", "Entry.");

    /* Do not try to delete NULL */
    if (ci == NULL) return;

    /* You can delete only whole collection not a part of it */
    if (ci->type != COL_TYPE_COLLECTION) {
        TRACE_ERROR_STRING("Attempt to delete a non collection - BAD!", "");
        TRACE_ERROR_NUMBER("Actual type is:", ci->type);
        return;
    }

    TRACE_INFO_STRING("Name:", ci->property);

    /* Collection can be referenced by other collection */
    header = (struct collection_header *)(ci->data);
    TRACE_INFO_NUMBER("Reference count:", header->reference_count);
    if (header->reference_count > 1) {
        TRACE_INFO_STRING("Dereferencing a referenced collection.", "");
        header->reference_count--;
        TRACE_INFO_NUMBER("Number after dereferencing.",
                          header->reference_count);
    }
    else {
        col_delete_collection(ci, cb, custom_data);
    }

    TRACE_FLOW_STRING("col_destroy_collection_with_cb", "Exit.");
}


/* Function that destroys a collection */
void col_destroy_collection(struct collection_item *ci)
{
    TRACE_FLOW_STRING("col_destroy_collection", "Entry.");

    col_destroy_collection_with_cb(ci, NULL, NULL);

    TRACE_FLOW_STRING("col_destroy_collection", "Exit.");
}

/* COPY */

/* Wrapper around a more advanced function */
int col_copy_collection(struct collection_item **collection_copy,
                        struct collection_item *collection_to_copy,
                        const char *name_to_use,
                        int copy_mode)
{
    int error = EOK;
    TRACE_FLOW_STRING("col_copy_collection", "Entry.");

    error = col_copy_collection_with_cb(collection_copy,
                                        collection_to_copy,
                                        name_to_use,
                                        copy_mode,
                                        NULL,
                                        NULL);

    TRACE_FLOW_NUMBER("col_copy_collection. Exit. Returning", error);
    return error;
}

/* Create a deep copy of the current collection. */
/* Referenced collections of the donor are copied as sub collections. */
int col_copy_collection_with_cb(struct collection_item **collection_copy,
                                struct collection_item *collection_to_copy,
                                const char *name_to_use,
                                int copy_mode,
                                col_copy_cb copy_cb,
                                void *ext_data)
{
    int error = EOK;
    struct collection_item *new_collection = NULL;
    const char *name;
    struct collection_header *header;
    unsigned depth = 0;
    struct col_copy traverse_data;
    int flags;

    TRACE_FLOW_STRING("col_copy_collection_with_cb", "Entry.");

    /* Collection is required */
    if (collection_to_copy == NULL) {
        TRACE_ERROR_NUMBER("No collection to search!", EINVAL);
        return EINVAL;
    }

    /* Storage is required too */
    if (collection_copy == NULL) {
        TRACE_ERROR_NUMBER("No memory provided to receive collection copy!", EINVAL);
        return EINVAL;
    }

    /* NOTE: Refine this check if adding a new copy mode */
    if ((copy_mode < 0) || (copy_mode > COL_COPY_TOP)) {
        TRACE_ERROR_NUMBER("Invalid copy mode:", copy_mode);
        return EINVAL;
    }

    /* Determine what name to use */
    if (name_to_use != NULL)
        name = name_to_use;
    else
        name = collection_to_copy->property;

    header = (struct collection_header *)collection_to_copy->data;

    /* Create a new collection */
    error = col_create_collection(&new_collection, name, header->cclass);
    if (error) {
        TRACE_ERROR_NUMBER("col_create_collection failed returning", error);
        return error;
    }

    traverse_data.mode = copy_mode;
    traverse_data.current_path = NULL;
    traverse_data.given_name = NULL;
    traverse_data.given_len = 0;
    traverse_data.copy_cb = copy_cb;
    traverse_data.ext_data = ext_data;

    if (copy_mode == COL_COPY_FLATDOT) flags = COL_TRAVERSE_DEFAULT | COL_TRAVERSE_END;
    else if (copy_mode == COL_COPY_FLAT) flags = COL_TRAVERSE_FLAT;
    else flags = COL_TRAVERSE_ONELEVEL;

    error = col_walk_items(collection_to_copy, flags,
                           col_copy_traverse_handler, (void *)(&traverse_data),
                           NULL, new_collection, &depth);

    if (!error) *collection_copy = new_collection;
    else col_destroy_collection(new_collection);

    TRACE_FLOW_NUMBER("col_copy_collection_with_cb returning", error);
    return error;

}


/* EXTRACTION */

/* Extract collection */
int col_get_collection_reference(struct collection_item *ci,
                                 struct collection_item **acceptor,
                                 const char *collection_to_find)
{
    struct collection_header *header;
    struct collection_item *subcollection = NULL;
    int error = EOK;

    TRACE_FLOW_STRING("col_get_collection_reference", "Entry.");

    if ((ci == NULL) ||
        (ci->type != COL_TYPE_COLLECTION) ||
        (acceptor == NULL)) {
        TRACE_ERROR_NUMBER("Invalid parameter - returning error",EINVAL);
        return EINVAL;
    }

    if (collection_to_find) {
        /* Find a sub collection */
        TRACE_INFO_STRING("We are given subcollection name - search it:",
                          collection_to_find);
        error = col_find_item_and_do(ci, collection_to_find,
                                     COL_TYPE_COLLECTIONREF,
                                     COL_TRAVERSE_DEFAULT,
                                     col_get_subcollection,
                                     (void *)(&subcollection),
                                     COLLECTION_ACTION_FIND);
        if (error) {
            TRACE_ERROR_NUMBER("Search failed returning error", error);
            return error;
        }

        if (subcollection == NULL) {
            TRACE_ERROR_STRING("Search for subcollection returned NULL pointer", "");
            return ENOENT;
        }
    }
    else {
        /* Create reference to the same collection */
        TRACE_INFO_STRING("Creating reference to the top level collection.", "");
        subcollection = ci;
    }

    header = (struct collection_header *)subcollection->data;
    TRACE_INFO_NUMBER("Count:", header->count);
    TRACE_INFO_NUMBER("Ref count:", header->reference_count);
    header->reference_count++;
    TRACE_INFO_NUMBER("Ref count after increment:", header->reference_count);
    *acceptor = subcollection;

    TRACE_FLOW_STRING("col_get_collection_reference", "Success Exit.");
    return EOK;
}

/* Get collection - if current item is a reference get a real collection from it. */
int col_get_reference_from_item(struct collection_item *ci,
                                struct collection_item **acceptor)
{
    struct collection_header *header;
    struct collection_item *subcollection = NULL;

    TRACE_FLOW_STRING("get_reference_from_item", "Entry.");

    if ((ci == NULL) ||
        (ci->type != COL_TYPE_COLLECTIONREF) ||
        (acceptor == NULL)) {
        TRACE_ERROR_NUMBER("Invalid parameter - returning error",EINVAL);
        return EINVAL;
    }

    subcollection = *((struct collection_item **)ci->data);

    header = (struct collection_header *)subcollection->data;
    TRACE_INFO_NUMBER("Count:", header->count);
    TRACE_INFO_NUMBER("Ref count:", header->reference_count);
    header->reference_count++;
    TRACE_INFO_NUMBER("Ref count after increment:", header->reference_count);
    *acceptor = subcollection;

    TRACE_FLOW_STRING("col_get_reference_from_item", "Success Exit.");
    return EOK;
}

/* ADDITION */

/* Add collection to collection */
int col_add_collection_to_collection(struct collection_item *ci,
                                     const char *sub_collection_name,
                                     const char *as_property,
                                     struct collection_item *collection_to_add,
                                     int mode)
{
    struct collection_item *acceptor = NULL;
    const char *name_to_use;
    struct collection_header *header;
    struct collection_item *collection_copy;
    int error = EOK;
    struct col_copy traverse_data;
    unsigned depth = 0;


    TRACE_FLOW_STRING("col_add_collection_to_collection", "Entry.");

    if ((ci == NULL) ||
        (ci->type != COL_TYPE_COLLECTION) ||
        (collection_to_add == NULL) ||
        (collection_to_add->type != COL_TYPE_COLLECTION)) {
        /* Need to debug here */
        TRACE_ERROR_NUMBER("Missing parameter - returning error", EINVAL);
        return EINVAL;
    }

    if (sub_collection_name != NULL) {
        /* Find a sub collection */
        TRACE_INFO_STRING("We are given subcollection name - search it:",
                          sub_collection_name);
        error = col_find_item_and_do(ci, sub_collection_name,
                                     COL_TYPE_COLLECTIONREF,
                                     COL_TRAVERSE_DEFAULT,
                                     col_get_subcollection,
                                     (void *)(&acceptor),
                                     COLLECTION_ACTION_FIND);
        if (error) {
            TRACE_ERROR_NUMBER("Search failed returning error", error);
            return error;
        }

        if (acceptor == NULL) {
            TRACE_ERROR_STRING("Search for subcollection returned NULL pointer", "");
            return ENOENT;
        }

    }
    else {
        acceptor = ci;
    }

    if (as_property != NULL)
        name_to_use = as_property;
    else
        name_to_use = collection_to_add->property;


    TRACE_INFO_STRING("Going to use name:", name_to_use);


    switch (mode) {
    case COL_ADD_MODE_REFERENCE:
        TRACE_INFO_STRING("We are adding a reference.", "");
        TRACE_INFO_NUMBER("Type of the header element:",
                          collection_to_add->type);
        TRACE_INFO_STRING("Header name we are adding.",
                          collection_to_add->property);
        /* Create a pointer to external collection */
        /* For future thread safety: Transaction start -> */
        error = col_insert_property_with_ref_int(acceptor,
                                                 NULL,
                                                 COL_DSP_END,
                                                 NULL,
                                                 0,
                                                 0,
                                                 name_to_use,
                                                 COL_TYPE_COLLECTIONREF,
                                                 (void *)(&collection_to_add),
                                                 sizeof(struct collection_item **),
                                                 NULL);

        TRACE_INFO_NUMBER("Type of the header element after adding property:",
                          collection_to_add->type);
        TRACE_INFO_STRING("Header name we just added.",
                          collection_to_add->property);
        if (error) {
            TRACE_ERROR_NUMBER("Adding property failed with error:", error);
            return error;
        }
        header = (struct collection_header *)collection_to_add->data;
        TRACE_INFO_NUMBER("Count:", header->count);
        TRACE_INFO_NUMBER("Ref count:", header->reference_count);
        header->reference_count++;
        TRACE_INFO_NUMBER("Ref count after increment:",
                          header->reference_count);
        /* -> Transaction end */
        break;

    case COL_ADD_MODE_EMBED:
        TRACE_INFO_STRING("We are embedding the collection.", "");
        /* First check if the passed in collection is referenced more than once */
        TRACE_INFO_NUMBER("Type of the header element we are adding:",
                          collection_to_add->type);
        TRACE_INFO_STRING("Header name we are adding.",
                          collection_to_add->property);
        TRACE_INFO_NUMBER("Type of the header element we are adding to:",
                          acceptor->type);
        TRACE_INFO_STRING("Header name we are adding to.",
                          acceptor->property);

        error = col_insert_property_with_ref_int(acceptor,
                                                 NULL,
                                                 COL_DSP_END,
                                                 NULL,
                                                 0,
                                                 0,
                                                 name_to_use,
                                                 COL_TYPE_COLLECTIONREF,
                                                 (void *)(&collection_to_add),
                                                 sizeof(struct collection_item **),
                                                 NULL);


        TRACE_INFO_NUMBER("Adding property returned:", error);
        break;

    case COL_ADD_MODE_CLONE:
        TRACE_INFO_STRING("We are cloning the collection.", "");
        TRACE_INFO_STRING("Name we will use.", name_to_use);

        /* For future thread safety: Transaction start -> */
        error = col_copy_collection(&collection_copy,
                                    collection_to_add, name_to_use,
                                    COL_COPY_NORMAL);
        if (error) return error;

        TRACE_INFO_STRING("We have a collection copy.", collection_copy->property);
        TRACE_INFO_NUMBER("Collection type.", collection_copy->type);
        TRACE_INFO_STRING("Acceptor collection.", acceptor->property);
        TRACE_INFO_NUMBER("Acceptor collection type.", acceptor->type);

        error = col_insert_property_with_ref_int(acceptor,
                                                 NULL,
                                                 COL_DSP_END,
                                                 NULL,
                                                 0,
                                                 0,
                                                 name_to_use,
                                                 COL_TYPE_COLLECTIONREF,
                                                 (void *)(&collection_copy),
                                                 sizeof(struct collection_item **),
                                                 NULL);

        /* -> Transaction end */
        TRACE_INFO_NUMBER("Adding property returned:", error);
        break;

    case COL_ADD_MODE_FLAT:
        TRACE_INFO_STRING("We are flattening the collection.", "");

        traverse_data.mode = COL_COPY_FLAT;
        traverse_data.current_path = NULL;
        traverse_data.copy_cb = NULL;
        traverse_data.ext_data = NULL;

        if ((as_property) && (*as_property)) {
            /* The normal assignement generates a warning
             * becuase I am assigning const to a non const.
             * I can't make the structure member to be const
             * since it changes but it changes
             * to point to different stings at different time
             * This is just an initial sting it will use.
             * The logic does not change the content of the string.
             * To overcome the issue I use memcpy();
             */
            memcpy(&(traverse_data.given_name),
                   &(as_property), sizeof(char *));
            traverse_data.given_len = strlen(as_property);
        }
        else {
            traverse_data.given_name = NULL;
            traverse_data.given_len = 0;
        }

        error = col_walk_items(collection_to_add, COL_TRAVERSE_FLAT,
                               col_copy_traverse_handler, (void *)(&traverse_data),
                               NULL, acceptor, &depth);

        TRACE_INFO_NUMBER("Copy collection flat returned:", error);
        break;

    case COL_ADD_MODE_FLATDOT:
        TRACE_INFO_STRING("We are flattening the collection with dots.", "");

        traverse_data.mode = COL_COPY_FLATDOT;
        traverse_data.current_path = NULL;
        traverse_data.copy_cb = NULL;
        traverse_data.ext_data = NULL;

        if ((as_property) && (*as_property)) {
            /* The normal assignement generates a warning
             * becuase I am assigning const to a non const.
             * I can't make the structure member to be const
             * since it changes but it changes
             * to point to different stings at different time
             * This is just an initial sting it will use.
             * The logic does not change the content of the string.
             * To overcome the issue I use memcpy();
             */
            memcpy(&(traverse_data.given_name),
                   &(as_property), sizeof(char *));
            traverse_data.given_len = strlen(as_property);
        }
        else {
            traverse_data.given_name = NULL;
            traverse_data.given_len = 0;
        }

        error = col_walk_items(collection_to_add, COL_TRAVERSE_DEFAULT | COL_TRAVERSE_END,
                               col_copy_traverse_handler, (void *)(&traverse_data),
                               NULL, acceptor, &depth);

        TRACE_INFO_NUMBER("Copy collection flatdot returned:", error);
        break;

    default:
        error = EINVAL;
    }

    TRACE_FLOW_NUMBER("col_add_collection_to_collection returning:", error);
    return error;
}

/* TRAVERSING */

/* Function to traverse the entire collection including optionally
 * sub collections */
int col_traverse_collection(struct collection_item *ci,
                            int mode_flags,
                            col_item_fn item_handler,
                            void *custom_data)
{

    int error = EOK;
    unsigned depth = 0;

    TRACE_FLOW_STRING("col_traverse_collection", "Entry.");

    if (ci == NULL) {
        TRACE_ERROR_NUMBER("No collection to traverse!", EINVAL);
        return EINVAL;
    }

    error = col_walk_items(ci, mode_flags, col_simple_traverse_handler,
                           NULL, item_handler, custom_data, &depth);

    if ((error != 0) && (error != EINTR_INTERNAL)) {
        TRACE_ERROR_NUMBER("Error walking tree", error);
        return error;
    }

    TRACE_FLOW_STRING("col_traverse_collection", "Success exit.");
    return EOK;
}

/* CHECK */

/* Convenience function to check if specific property is in the collection */
int col_is_item_in_collection(struct collection_item *ci,
                              const char *property_to_find,
                              int type,
                              int mode_flags,
                              int *found)
{
    int error;

    TRACE_FLOW_STRING("col_is_item_in_collection","Entry.");

    *found = COL_NOMATCH;
    error = col_find_item_and_do(ci, property_to_find,
                                 type, mode_flags,
                                 col_is_in_item_handler,
                                 (void *)found,
                                 COLLECTION_ACTION_FIND);

    TRACE_FLOW_NUMBER("col_is_item_in_collection returning", error);
    return error;
}

/* SEARCH */
/* Search function. Looks up an item in the collection based on the property.
   Essentually it is a traverse function with spacial traversing logic.
 */
int col_get_item_and_do(struct collection_item *ci,
                        const char *property_to_find,
                        int type,
                        int mode_flags,
                        col_item_fn item_handler,
                        void *custom_data)
{
    int error = EOK;

    TRACE_FLOW_STRING("col_get_item_and_do","Entry.");

    error = col_find_item_and_do(ci, property_to_find,
                                 type, mode_flags,
                                 item_handler,
                                 custom_data,
                                 COLLECTION_ACTION_FIND);

    TRACE_FLOW_NUMBER("col_get_item_and_do returning", error);
    return error;
}


/* Get raw item */
int col_get_item(struct collection_item *ci,
                 const char *property_to_find,
                 int type,
                 int mode_flags,
                 struct collection_item **item)
{

    int error = EOK;

    TRACE_FLOW_STRING("col_get_item", "Entry.");

    error = col_find_item_and_do(ci, property_to_find,
                                 type, mode_flags,
                                 NULL, (void *)item,
                                 COLLECTION_ACTION_GET);

    TRACE_FLOW_NUMBER("col_get_item returning", error);
    return error;
}

/* DELETE */
/* Delete property from the collection */
int col_delete_property(struct collection_item *ci,
                        const char *property_to_find,
                        int type,
                        int mode_flags)
{
    int error = EOK;
    int found;

    TRACE_FLOW_STRING("col_delete_property", "Entry.");
    found = COL_NOMATCH;

    error = col_find_item_and_do(ci, property_to_find,
                                 type, mode_flags,
                                 NULL, (void *)(&found),
                                 COLLECTION_ACTION_DEL);

    if ((error == EOK) && (found == COL_NOMATCH))
        error = ENOENT;

    TRACE_FLOW_NUMBER("col_delete_property returning", error);
    return error;
}

/* UPDATE */
/* Update property in the collection */
int col_update_property(struct collection_item *ci,
                        const char *property_to_find,
                        int type,
                        void *new_data,
                        int length,
                        int mode_flags)
{
    int error = EOK;
    struct update_property update_data;

    TRACE_FLOW_STRING("col_update_property", "Entry.");
    update_data.type = type;
    update_data.data = new_data;
    update_data.length = length;
    update_data.found = COL_NOMATCH;

    error = col_find_item_and_do(ci, property_to_find,
                                 type, mode_flags,
                                 NULL, (void *)(&update_data),
                                 COLLECTION_ACTION_UPDATE);

    if ((error == EOK) && (update_data.found == COL_NOMATCH))
        error = ENOENT;

    TRACE_FLOW_NUMBER("col_update_property returning", error);
    return error;
}


/* Function to modify the item */
int col_modify_item(struct collection_item *item,
                    const char *property,
                    int type,
                    const void *data,
                    int length)
{
    TRACE_FLOW_STRING("col_modify_item", "Entry");

    /* Allow renameing only */
    if ((item == NULL) ||
        ((item->type == COL_TYPE_COLLECTION) && (length != 0)) ||
        ((item->type == COL_TYPE_COLLECTIONREF) && (length != 0))) {
        TRACE_ERROR_NUMBER("Invalid argument or invalid argument type", EINVAL);
        return EINVAL;
    }

    if (property != NULL) {
        if (col_validate_property(property)) {
            TRACE_ERROR_STRING("Invalid chracters in the property name", property);
            return EINVAL;
        }
        free(item->property);
        item->property = strdup(property);
        if (item->property == NULL) {
            TRACE_ERROR_STRING("Failed to allocate memory", "");
            return ENOMEM;
        }

        /* Update property length and hash if we rename the property */
        item->phash = col_make_hash(property, 0, &(item->property_len));
        TRACE_INFO_NUMBER("Item hash", item->phash);
        TRACE_INFO_NUMBER("Item property length", item->property_len);
        TRACE_INFO_NUMBER("Item property strlen", strlen(item->property));

    }

    /* We need to change data ? */
    if(length) {

        /* If type is different or same but it is string or binary we need to
         * replace the storage */
        if ((item->type != type) ||
            ((item->type == type) &&
            ((item->type == COL_TYPE_STRING) || (item->type == COL_TYPE_BINARY)))) {
            TRACE_INFO_STRING("Replacing item data buffer", "");
            free(item->data);
            item->data = malloc(length);
            if (item->data == NULL) {
                TRACE_ERROR_STRING("Failed to allocate memory", "");
                item->length = 0;
                return ENOMEM;
            }
            item->length = length;
        }

        TRACE_INFO_STRING("Overwriting item data", "");
        memcpy(item->data, data, item->length);
        item->type = type;

        if (item->type == COL_TYPE_STRING)
            ((char *)(item->data))[item->length - 1] = '\0';
    }

    TRACE_FLOW_STRING("col_modify_item", "Exit");
    return EOK;
}


/* Set collection class */
int col_set_collection_class(struct collection_item *item,
                             unsigned cclass)
{
    struct collection_header *header;

    TRACE_FLOW_STRING("col_set_collection_class", "Entry");

    if (item->type != COL_TYPE_COLLECTION) {
        TRACE_INFO_NUMBER("Not a collectin object. Type is", item->type);
        return EINVAL;
    }

    header = (struct collection_header *)item->data;
    header->cclass = cclass;
    TRACE_FLOW_STRING("col_set_collection_class", "Exit");
    return EOK;
}

/* Get collection class */
int col_get_collection_class(struct collection_item *item,
                             unsigned *cclass)
{
    struct collection_header *header;

    TRACE_FLOW_STRING("col_get_collection_class", "Entry");

    if (item->type != COL_TYPE_COLLECTION) {
        TRACE_ERROR_NUMBER("Not a collection object. Type is", item->type);
        return EINVAL;
    }

    header = (struct collection_header *)item->data;
    *cclass  = header->cclass;
    TRACE_FLOW_STRING("col_get_collection_class", "Exit");
    return EOK;
}

/* Get collection count */
int col_get_collection_count(struct collection_item *item,
                             unsigned *count)
{
    struct collection_header *header;

    TRACE_FLOW_STRING("col_get_collection_count", "Entry");

    if (item->type != COL_TYPE_COLLECTION) {
        TRACE_ERROR_NUMBER("Not a collectin object. Type is", item->type);
        return EINVAL;
    }

    header = (struct collection_header *)item->data;
    *count  = header->count;
    TRACE_FLOW_STRING("col_get_collection_count", "Exit");
    return EOK;

}

/* Convinience function to check if the collection is of the specific class */
/* In case of internal error assumes that collection is not of the right class */
int col_is_of_class(struct collection_item *item, unsigned cclass)
{
    int error = EOK;
    unsigned ret_class = 0;

    TRACE_FLOW_STRING("col_is_of_class invoked", "");

    error = col_get_collection_class(item, &ret_class);
    if (error || (ret_class != cclass))
        return 0;
    else
        return 1;
}

/* Get propery */
const char *col_get_item_property(struct collection_item *ci,
                                  int *property_len)
{
    if (property_len != NULL) *property_len = ci->property_len;
    return ci->property;
}

/* Get type */
int col_get_item_type(struct collection_item *ci)
{
    return ci->type;
}

/* Get length */
int col_get_item_length(struct collection_item *ci)
{
    return ci->length;
}

/* Get data */
void *col_get_item_data(struct collection_item *ci)
{
    return ci->data;
}

/* Get hash */
uint64_t col_get_item_hash(struct collection_item *ci)
{
    return ci->phash;
}

/* Calculates hash of the string using internal hashing
 * algorithm. Populates "length" with length
 * of the string not counting 0.
 * Length argument can be NULL.
 */
uint64_t col_make_hash(const char *string, int sub_len, int *length)
{
    uint64_t hash = 0;
    int str_len = 0;

    TRACE_FLOW_STRING("col_make_hash called for string:", string);

    if (string) {
        hash = FNV1a_base;
        while (string[str_len] != 0) {

            /* Check if we need to stop */
            if ((sub_len > 0) && (str_len == sub_len)) break;

            hash = hash ^ toupper(string[str_len]);
            hash *= FNV1a_prime;
            str_len++;
        }
    }

    if (length) *length = str_len;

    TRACE_FLOW_NUMBER("col_make_hash returning hash:", hash);

    return hash;
}
