/*
 * Copyright 2013 University Corporation for Atmospheric Research
 *
 * This file is part of the UDUNITS-2 package.  See the file COPYRIGHT
 * in the top-level source-directory of the package for copying and
 * redistribution conditions.
 */
/*
 * Module for system-to-pointer maps.
 *
 * This module is thread-compatible but not thread-safe.
 */

/*LINTLIBRARY*/

#ifndef	_XOPEN_SOURCE
#   define _XOPEN_SOURCE 500
#endif

#ifdef _MSC_VER
#include "tsearch.h"
#else
#include <search.h>
#endif
#include <stdlib.h>

#include "systemMap.h"
#include "udunits2.h"

struct SystemMap {
    void*	tree;
};

typedef struct {
    const ut_system*	system;
    void*		ptr;
} Entry;


/*
 * Compares two entries according to their unit-system pointers.
 *
 * Arguments:
 *	entry1	Pointer to the first entry.
 *	entry2	Pointer to the second entry.
 * Returns:
 *	-1	The first entry's key is less than the second's.
 *	 0	The first entry's key is equal to the second's.
 *	 1	The first entry's key is greater than the second's.
 */
static int
compareEntries(
    const void*	entry1,
    const void*	entry2)
{
    const ut_system* const	system1 = ((Entry*)entry1)->system;
    const ut_system* const	system2 = ((Entry*)entry2)->system;

    return system1 < system2 ? -1 : system1 == system2 ? 0 : 1;
}


/*
 * Returns a new instance of a system-map.
 *
 * Arguments:
 *	compare	Function for comparing keys.
 * Returns:
 *	NULL	Operating-system failure.  See "errno".
 *	else	Pointer to the new map.
 */
SystemMap*
smNew()
{
    SystemMap*	map = (SystemMap*)malloc(sizeof(SystemMap));

    if (map != NULL)
	map->tree = NULL;

    return map;
}


/*
 * Returns the address of the pointer to which a unit-system maps.
 *
 * Arguments:
 *	map	Pointer to the system-map.
 *	system	Pointer to the unit-system.
 * Returns:
 *	NULL	There is no pointer associated with "system".
 *	else	Address of the pointer to which "system" maps.
 */
void**
smFind(
    const SystemMap* const	map,
    const void* const		system)
{
    Entry	targetEntry;
    Entry**	treeEntry;

    targetEntry.system = system;
    treeEntry = tfind(&targetEntry, (void**)&map->tree, compareEntries);

    return
	treeEntry == NULL
	    ? NULL
	    : &(*treeEntry)->ptr;
}


/*
 * Returns the address of the pointer to which a unit-system maps -- creating a
 * new entry if necessary.  If a new entry is created, then the pointer whose
 * address is returned will be NULL.
 *
 * Arguments:
 *	map	Pointer to the system-map.
 *	system	Pointer to the unit-system.
 * Returns:
 *	NULL	Operating system failure.  See "errno".
 *	else	Address of the pointer to which "system" maps.
 */
void**
smSearch(
    SystemMap* const	map,
    const void*		system)
{
    void**	addr = NULL;		/* failure */
    Entry*	targetEntry = (Entry*)malloc(sizeof(Entry));

    if (targetEntry != NULL) {
	Entry**	treeEntry;

	targetEntry->system = system;
	targetEntry->ptr = NULL;
	treeEntry = tsearch(targetEntry, &map->tree, compareEntries);

	if (treeEntry == NULL) {
	    free(targetEntry);
	}
	else {
	    addr = &(*treeEntry)->ptr;

	    if (targetEntry != *treeEntry)
		free(targetEntry);
	}
    }

    return addr;
}


/*
 * Removes the system-map entry that corresponds to a unit-system.
 *
 * Arguments:
 *	map	Pointer to the map.
 *	system	Pointer to the unit-system.
 */
void
smRemove(
    SystemMap* const	map,
    const void* const	system)
{
    Entry	targetEntry;
    Entry**	treeEntry;

    targetEntry.system = system;
    treeEntry = tfind(&targetEntry, &map->tree, compareEntries);

    if (treeEntry != NULL) {
	Entry*	entry = *treeEntry;

	(void)tdelete(entry, &map->tree, compareEntries);
	free(entry);
    }
}


/*
 * Frees a system-map.  This function should be called when a system-map is no
 * longer needed.
 *
 * Arguments:
 *	map	Pointer to the system-map to be freed or NULL.  Use of "map"
 *		upon return results in undefined behavior.
 */
void
smFree(
    SystemMap* const	map)
{
    if (map != NULL) {
	while (map->tree != NULL) {
	    Entry*	entry = *(Entry**)map->tree;

	    tdelete(entry, &map->tree, compareEntries);
	    free(entry);
	}

	free(map);
    }
}
