/*
 * Copyright 2020 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.
 */
#ifndef UT_UNITS2_H_INCLUDED
#define UT_UNITS2_H_INCLUDED

#include <stdarg.h>
#include <stddef.h>

#include "harp_udunits2_mangle.h"

#ifdef _MSC_VER
#include <stdlib.h>
#include <stdio.h>
#include <stdarg.h>
#include <io.h>

#define _USE_MATH_DEFINES

/* Define a bunch of variables to use the ISO C++ conformant name instead
   of the POSIX name. This quiets a lot of the warnings thrown by MSVC. */
#define read _read
#define open _open
#define close _close
#define strdup _strdup
#define strcasecmp stricmp
#define stricmp _stricmp
#define isatty _isatty

//We must accommodate the lack of snprintf in versions of MSVC less than 1900.
//udunits_snprintf is defined in udunits_snprintf.c, in lib/.
#define snprintf udunits_snprintf

int udunits_snprintf(
   char* str,
     size_t size,
     const char* format,
     ...);

int udunits_vsnprintf(
char* str,
  size_t size,
  const char* format,
  va_list ap);

#endif

/* If we are working in Visual Studio and have a
   shared library, we will need to do some slight-of-hand
   in order to make it generate a proper export library.
*/

#if defined(DLL_UDUNITS2) /* define when library is a DLL */
#   if defined(DLL_EXPORT) /* defined when building the library */
#     define MSC_EXTRA __declspec(dllexport)
#   else
#     define MSC_EXTRA __declspec(dllimport)
#   endif
#else
# define MSC_EXTRA
#endif /* defined(DLL_UDUNITS2) */

/*
 * Results in
 * udunits2.c.obj : error LNK2019: unresolved external symbol cv_free referenced in function handleRequest
 * udunits2.c.obj : error LNK2019: unresolved external symbol cv_convert_double referenced in function handleRequest
 * udunits2.c.obj : error LNK2019: unresolved external symbol cv_get_expression referenced in function handleRequest
 */
#ifndef EXTERNL
#   define EXTERNL MSC_EXTRA extern
#endif

/*
 * Results in "NMAKE : fatal error U1073: don't know how to make 'lib\udunits2.lib'"
#undef EXTERNL
#define EXTERNL extern
 */

#include "converter.h"

typedef struct ut_system	ut_system;
typedef union ut_unit		ut_unit;

enum utStatus {
    UT_SUCCESS = 0,	/* Success */
    UT_BAD_ARG,	        /* An argument violates the function's contract */
    UT_EXISTS,		/* Unit, prefix, or identifier already exists */
    UT_NO_UNIT,		/* No such unit exists */
    UT_OS,		/* Operating-system error.  See "errno". */
    UT_NOT_SAME_SYSTEM,	/* The units belong to different unit-systems */
    UT_MEANINGLESS,	/* The operation on the unit(s) is meaningless */
    UT_NO_SECOND,	/* The unit-system doesn't have a unit named "second" */
    UT_VISIT_ERROR,	/* An error occurred while visiting a unit */
    UT_CANT_FORMAT,	/* A unit can't be formatted in the desired manner */
    UT_SYNTAX,		/* string unit representation contains syntax error */
    UT_UNKNOWN,		/* string unit representation contains unknown word */
    UT_OPEN_ARG,	/* Can't open argument-specified unit database */
    UT_OPEN_ENV,	/* Can't open environment-specified unit database */
    UT_OPEN_DEFAULT,	/* Can't open installed, default, unit database */
    UT_PARSE		/* Error parsing unit specification */
};
typedef enum utStatus          ut_status;

enum utEncoding {
    UT_ASCII = 0,
    UT_ISO_8859_1 = 1,
    UT_LATIN1 = UT_ISO_8859_1,
    UT_UTF8 = 2
};
typedef enum utEncoding        ut_encoding;

#define UT_NAMES	4
#define UT_DEFINITION	8


/*
 * Data-structure for a visitor to a unit:
 */
typedef struct ut_visitor {
    /*
     * Visits a basic-unit.  A basic-unit is a base unit like "meter" or a non-
     * dimensional but named unit like "radian".
     *
     * Arguments:
     *	unit		Pointer to the basic-unit.
     *	arg		Client pointer passed to ut_accept_visitor().
     * Returns:
     *	UT_SUCCESS	Success.
     *	else		Failure.
     */
    ut_status	(*visit_basic)(const ut_unit* unit, void* arg);

    /*
     * Visits a product-unit.  A product-unit is a product of zero or more
     * basic-units, each raised to a non-zero power.
     *
     * Arguments:
     *	unit		Pointer to the product-unit.
     *	count		The number of basic-units in the product.  May be zero.
     *	basicUnits	Pointer to an array of basic-units in the product.
     *	powers		Pointer to an array of powers to which the respective
     *			basic-units are raised.
     *	arg		Client pointer passed to ut_accept_visitor().
     * Returns:
     *	UT_SUCCESS	Success.
     *	else		Failure.
     */
    ut_status	(*visit_product)(const ut_unit* unit, int count,
	const ut_unit* const* basicUnits, const int* powers, void* arg);

    /*
     * Visits a Galilean-unit.  A Galilean-unit has an underlying unit and a
     * non-unity scale factor or a non-zero offset.
     *
     * Arguments:
     *	unit		Pointer to the Galilean-unit.
     *	scale		The scale factor (e.g., 1000 for a kilometer when the
     *			underlying unit is a meter).
     *	underlyingUnit	Pointer to the underlying unit.
     *	offset		Pointer to the underlying unit.
     *	arg		Client pointer passed to ut_accept_visitor().
     * Returns:
     *	UT_SUCCESS	Success.
     *	else		Failure.
     */
    ut_status	(*visit_galilean)(const ut_unit* unit, double scale,
	const ut_unit* underlyingUnit, double offset, void* arg);

    /*
     * Visits a timestamp-unit.  A timestamp-unit has an underlying unit of time
     * and an encoded time-origin.
     *
     * Arguments:
     *	unit		Pointer to the timestamp-unit.
     *	timeUnit	Pointer to the underlying unit of time.
     *  origin          Encoded origin of the timestamp-unit.
     *	arg		Client pointer passed to ut_accept_visitor().
     * Returns:
     *	UT_SUCCESS	Success.
     *	else		Failure.
     */
    ut_status	(*visit_timestamp)(const ut_unit* unit,
	const ut_unit* timeUnit, double origin, void* arg);

    /*
     * Visits a logarithmic-unit.  A logarithmic-unit has a logarithmic base and
     * a unit that specifies the reference level.
     *
     * Arguments:
     *	unit		Pointer to the logarithmic-unit.
     *  base            The logarithmic base (e.g., 2, M_E, 10).
     *	reference	Pointer to the unit that specifies the reference level.
     *	arg		Client pointer passed to ut_accept_visitor().
     * Returns:
     *	UT_SUCCESS	Success.
     *	else		Failure.
     */
    ut_status	(*visit_logarithmic)(const ut_unit* unit, double base,
	const ut_unit* reference, void* arg);
} ut_visitor;


typedef int (*ut_error_message_handler)(const char* fmt, va_list args);


#ifdef __cplusplus
EXTERNL "C" {
#endif


/******************************************************************************
 * Unit System:
 ******************************************************************************/


/**
 * Returns the pathname of the XML database.
 *
 * @param path      The pathname of the XML file or NULL.
 * @param status    Status. One of UT_OPEN_ARG, UT_OPEN_ENV, or UT_OPEN_DEFAULT.
 * @return          If "path" is not NULL, then it is returned; otherwise, the
 *                  pathname specified by the environment variable
 *                  UDUNITS2_XML_PATH is returned if set; otherwise, the
 *                  compile-time pathname of the installed, default, unit
 *                  database is returned.
 */
EXTERNL const char*
ut_get_path_xml(
	const char*	path,
	ut_status*  status);

/*
 * Returns the unit-system corresponding to an XML file.  This is the usual way
 * that a client will obtain a unit-system.
 *
 * Arguments:
 *	path	The pathname of the XML file or NULL.  If NULL, then the
 *		pathname specified by the environment variable UDUNITS2_XML_PATH
 *		is used if set; otherwise, the compile-time pathname of the
 *		installed, default, unit database is used.
 * Returns:
 *	NULL	Failure.  "ut_get_status()" will be
 *		    UT_OPEN_ARG		"path" is non-NULL but file couldn't be
 *					opened.  See "errno" for reason.
 *		    UT_OPEN_ENV		"path" is NULL and environment variable
 *					UDUNITS2_XML_PATH is set but file
 *					couldn't be opened.  See "errno" for
 *					reason.
 *		    UT_OPEN_DEFAULT	"path" is NULL, environment variable
 *					UDUNITS2_XML_PATH is unset, and the
 *					installed, default, unit database
 *					couldn't be opened.  See "errno" for
 *					reason.
 *		    UT_PARSE		Couldn't parse unit database.
 *		    UT_OS		Operating-system error.  See "errno".
 *	else	Pointer to the unit-system defined by "path".
 */
EXTERNL ut_system*
ut_read_xml(
    const char*	path);


/*
 * Returns a new unit-system.  On success, the unit-system will only contain
 * the dimensionless unit one.  See "ut_get_dimensionless_unit_one()".
 *
 * Returns:
 *	NULL	Failure.  "ut_get_status()" will be:
 *		    UT_OS	Operating-system error.  See "errno".
 *	else	Pointer to a new unit system.
 */
EXTERNL ut_system*
ut_new_system(void);


/*
 * Frees a unit-system.  All unit-to-identifier and identifier-to-unit mappings
 * will be removed.
 *
 * Arguments:
 *	system		Pointer to the unit-system to be freed.  Use of "system"
 *			upon return results in undefined behavior.
 */
EXTERNL void
ut_free_system(
    ut_system*	system);


/*
 * Returns the unit-system to which a unit belongs.
 *
 * Arguments:
 *	unit	Pointer to the unit in question.
 * Returns:
 *	NULL	Failure.  "ut_get_status()" will be
 *		    UT_BAD_ARG	"unit" is NULL.
 *	else	Pointer to the unit-system to which "unit" belongs.
 */
EXTERNL ut_system*
ut_get_system(
    const ut_unit* const	unit);


/*
 * Returns the dimensionless-unit one of a unit-system.
 *
 * Arguments:
 *	system	Pointer to the unit-system for which the dimensionless-unit one
 *		will be returned.
 * Returns:
 *	NULL	Failure.  "ut_get_status()" will be:
 *		    UT_BAD_ARG	"system" is NULL.
 *	else	Pointer to the dimensionless-unit one associated with "system".
 *		While not necessary, the pointer may be passed to ut_free()
 *		when the unit is no longer needed by the client.
 */
EXTERNL ut_unit*
ut_get_dimensionless_unit_one(
    const ut_system* const	system);


/*
 * Returns the unit with a given name from a unit-system.  Name comparisons
 * are case-insensitive.
 *
 * Arguments:
 *	system	Pointer to the unit-system.
 *	name	Pointer to the name of the unit to be returned.
 * Returns:
 *	NULL	Failure.  "ut_get_status()" will be
 *		    UT_SUCCESS		"name" doesn't map to a unit of
 *					"system".
 *		    UT_BAD_ARG		"system" or "name" is NULL.
 *	else	Pointer to the unit of the unit-system with the given name.
 *		The pointer should be passed to ut_free() when the unit is
 *		no longer needed.
 */
EXTERNL ut_unit*
ut_get_unit_by_name(
    const ut_system* const	system,
    const char* const		name);


/*
 * Returns the unit with a given symbol from a unit-system.  Symbol
 * comparisons are case-sensitive.
 *
 * Arguments:
 *	system		Pointer to the unit-system.
 *	symbol		Pointer to the symbol associated with the unit to be
 *			returned.
 * Returns:
 *	NULL	Failure.  "ut_get_status()" will be
 *		    UT_SUCCESS		"symbol" doesn't map to a unit of
 *					"system".
 *		    UT_BAD_ARG		"system" or "symbol" is NULL.
 *	else	Pointer to the unit in the unit-system with the given symbol.
 *		The pointer should be passed to ut_free() when the unit is no
 *		longer needed.
 */
EXTERNL ut_unit*
ut_get_unit_by_symbol(
    const ut_system* const	system,
    const char* const		symbol);


/*
 * Sets the "second" unit of a unit-system.  This function must be called before
 * the first call to "ut_offset_by_time()". ut_read_xml() calls this function if the
 * resulting unit-system contains a unit named "second".
 *
 * Arguments:
 *	second		Pointer to the "second" unit.
 * Returns:
 *	UT_BAD_ARG	"second" is NULL.
 *	UT_EXISTS	The second unit of the unit-system to which "second"
 *			belongs is set to a different unit.
 *	UT_SUCCESS	Success.
 */
EXTERNL ut_status
ut_set_second(
    const ut_unit* const	second);


/******************************************************************************
 * Defining Unit Prefixes:
 ******************************************************************************/


/*
 * Adds a name-prefix to a unit-system.  A name-prefix is something like "mega"
 * or "milli".  Comparisons between name-prefixes are case-insensitive.
 *
 * Arguments:
 *	system		Pointer to the unit-system.
 *	name		Pointer to the name-prefix (e.g., "mega").  May be freed
 *			upon return.
 *	value		The value of the prefix (e.g., 1e6).
 * Returns:
 *	UT_SUCCESS	Success.
 *	UT_BAD_ARG	"system" or "name" is NULL, or "value" is 0.
 *	UT_EXISTS	"name" already maps to a different value.
 *	UT_OS		Operating-system failure.  See "errno".
 */
EXTERNL ut_status
ut_add_name_prefix(
    ut_system* const	system,
    const char* const	name,
    const double	value);


/*
 * Adds a symbol-prefix to a unit-system.  A symbol-prefix is something like
 * "M" or "y".  Comparisons between symbol-prefixes are case-sensitive.
 *
 * Arguments:
 *	system		Pointer to the unit-system.
 *	symbol		Pointer to the symbol-prefix (e.g., "M").  May be freed
 *			upon return.
 *	value		The value of the prefix (e.g., 1e6).
 * Returns:
 *	UT_SUCCESS	Success.
 *	UT_BADSYSTEM	"system" or "symbol" is NULL.
 *	UT_BAD_ARG	"value" is 0.
 *	UT_EXISTS	"symbol" already maps to a different value.
 *	UT_OS		Operating-system failure.  See "errno".
 */
EXTERNL ut_status
ut_add_symbol_prefix(
    ut_system* const	system,
    const char* const	symbol,
    const double	value);


/******************************************************************************
 * Defining and Deleting Units:
 ******************************************************************************/


/*
 * Adds a base-unit to a unit-system.  Clients that use ut_read_xml() should not
 * normally need to call this function.
 *
 * Arguments:
 *	system	Pointer to the unit-system to which to add the new base-unit.
 * Returns:
 *	NULL	Failure.  "ut_get_status()" will be
 *		    UT_BAD_ARG		"system" or "name" is NULL.
 *		    UT_OS		Operating-system error.  See "errno".
 *	else	Pointer to the new base-unit.  The pointer should be passed to
 *		ut_free() when the unit is no longer needed by the client (the
 *		unit will remain in the unit-system).
 */
EXTERNL ut_unit*
ut_new_base_unit(
    ut_system* const	system);


/*
 * Adds a dimensionless-unit to a unit-system.  In the SI system of units, the
 * derived-unit radian is a dimensionless-unit.  Clients that use ut_read_xml()
 * should not normally need to call this function.
 *
 * Arguments:
 *	system	Pointer to the unit-system to which to add the new
 *		dimensionless-unit.
 * Returns:
 *	NULL	Failure.  "ut_get_status()" will be
 *		    UT_BAD_ARG		"system" is NULL.
 *		    UT_OS		Operating-system error.  See "errno".
 *	else	Pointer to the new dimensionless-unit.  The pointer should be
 *		passed to ut_free() when the unit is no longer needed by the
 *		client (the unit will remain in the unit-system).
 */
EXTERNL ut_unit*
ut_new_dimensionless_unit(
    ut_system* const	system);


/*
 * Returns a clone of a unit.
 *
 * Arguments:
 *	unit	Pointer to the unit to be cloned.
 * Returns:
 *	NULL	Failure.  ut_get_status() will be
 *		    UT_OS	Operating-system failure.  See "errno".
 *		    UT_BAD_ARG	"unit" is NULL.
 *	else	Pointer to the clone of "unit".  The pointer should be
 *		passed to ut_free() when the unit is no longer needed by the
 *		client.
 */
EXTERNL ut_unit*
ut_clone(
    const ut_unit* const unit);


/*
 * Frees resources associated with a unit.  This function should be invoked on
 * all units that are no longer needed by the client.  Use of the unit upon
 * return from this function will result in undefined behavior.
 *
 * Arguments:
 *	unit	Pointer to the unit to have its resources freed or NULL.
 */
EXTERNL void
ut_free(
    ut_unit* const	unit);


/******************************************************************************
 * Mapping between Units and Names:
 ******************************************************************************/


/*
 * Returns the name in a given encoding to which a unit maps.
 *
 * Arguments:
 *	unit		Pointer to the unit whose name should be returned.
 *	encoding	The desired encoding of the name.
 * Returns:
 *	NULL		Failure.  "ut_get_status()" will be
 *			    UT_BAD_ARG		"unit" is NULL.
 *			    UT_SUCCESS		"unit" doesn't map to a name in
 *						in the given encoding.
 *	else		Pointer to the name in the given encoding to which
 *			"unit" maps.
 */
EXTERNL const char*
ut_get_name(
    const ut_unit* const	unit,
    const ut_encoding		encoding);


/*
 * Adds a mapping from a name to a unit.
 *
 * Arguments:
 *	name		Pointer to the name to be mapped to "unit".  May be
 *			freed upon return.
 *      encoding        The character encoding of "name".
 *	unit		Pointer to the unit to be mapped-to by "name".  May be
 *			freed upon return.
 * Returns:
 *	UT_BAD_ARG	"name" or "unit" is NULL.
 *	UT_OS		Operating-system error.  See "errno".
 *	UT_EXISTS	"name" already maps to a different unit.
 *	UT_SUCCESS	Success.
 */
EXTERNL ut_status
ut_map_name_to_unit(
    const char* const		name,
    const ut_encoding		encoding,
    const ut_unit* const	unit);


/*
 * Removes a mapping from a name to a unit.  After this function,
 * ut_get_unit_by_name(system,name) will no longer return a unit.
 *
 * Arguments:
 *	system		The unit-system to which the unit belongs.
 *	name		The name of the unit.
 *      encoding        The character encoding of "name".
 * Returns:
 *	UT_SUCCESS	Success.
 *	UT_BAD_ARG	"system" or "name" is NULL.
 */
EXTERNL ut_status
ut_unmap_name_to_unit(
    ut_system*		system,
    const char* const	name,
    const ut_encoding   encoding);


/*
 * Adds a mapping from a unit to a name.
 *
 * Arguments:
 *	unit		Pointer to the unit to be mapped to "name".  May be
 *			freed upon return.
 *	name		Pointer to the name to be mapped-to by "unit".  May be
 *			freed upon return.
 *	encoding	The encoding of "name".
 * Returns:
 *	UT_SUCCESS	Success.
 *	UT_BAD_ARG	"unit" or "name" is NULL, or "name" is not in the
 *                      specified encoding.
 *	UT_OS		Operating-system error.  See "errno".
 *	UT_EXISTS	"unit" already maps to a name.
 */
EXTERNL ut_status
ut_map_unit_to_name(
    const ut_unit* const	unit,
    const char* const		name,
    ut_encoding			encoding);


/*
 * Removes a mapping from a unit to a name.
 *
 * Arguments:
 *	unit		Pointer to the unit.  May be freed upon return.
 *	encoding	The encoding to be removed.  No other encodings will be
 *			removed.
 * Returns:
 *	UT_BAD_ARG	"unit" is NULL.
 *	UT_SUCCESS	Success.
 */
EXTERNL ut_status
ut_unmap_unit_to_name(
    const ut_unit* const	unit,
    ut_encoding			encoding);


/******************************************************************************
 * Mapping between Units and Symbols:
 ******************************************************************************/


/*
 * Returns the symbol in a given encoding to which a unit maps.
 *
 * Arguments:
 *	unit		Pointer to the unit whose symbol should be returned.
 *	encoding	The desired encoding of the symbol.
 * Returns:
 *	NULL		Failure.  "ut_get_status()" will be
 *			    UT_BAD_ARG		"unit" is NULL.
 *			    UT_SUCCESS		"unit" doesn't map to a symbol
 *						in the given encoding.
 *	else		Pointer to the symbol in the given encoding to which
 *			"unit" maps.
 */
EXTERNL const char*
ut_get_symbol(
    const ut_unit* const	unit,
    const ut_encoding	encoding);


/*
 * Adds a mapping from a symbol to a unit.
 *
 * Arguments:
 *	symbol		Pointer to the symbol to be mapped to "unit".  May be
 *			freed upon return.
 *      ut_encoding     The character encoding of "symbol".
 *	unit		Pointer to the unit to be mapped-to by "symbol".  May
 *			be freed upon return.
 * Returns:
 *	UT_BAD_ARG	"symbol" or "unit" is NULL.
 *	UT_OS		Operating-system error.  See "errno".
 *	UT_EXISTS	"symbol" already maps to a different unit.
 *	UT_SUCCESS	Success.
 */
EXTERNL ut_status
ut_map_symbol_to_unit(
    const char* const		symbol,
    const ut_encoding		encoding,
    const ut_unit* const	unit);


/*
 * Removes a mapping from a symbol to a unit.  After this function,
 * ut_get_unit_by_symbol(system,symbol) will no longer return a unit.
 *
 * Arguments:
 *	system		The unit-system to which the unit belongs.
 *	symbol		The symbol of the unit.
 *      encoding        The character encoding of "symbol".
 * Returns:
 *	UT_SUCCESS	Success.
 *	UT_BAD_ARG	"system" or "symbol" is NULL.
 */
EXTERNL ut_status
ut_unmap_symbol_to_unit(
    ut_system*		system,
    const char* const	symbol,
    const ut_encoding   encoding);


/*
 * Adds a mapping from a unit to a symbol.
 *
 * Arguments:
 *	unit		Pointer to the unit to be mapped to "symbol".  May be
 *			freed upon return.
 *	symbol		Pointer to the symbol to be mapped-to by "unit".  May
 *			be freed upon return.
 *	encoding	The encoding of "symbol".
 * Returns:
 *	UT_SUCCESS	Success.
 *	UT_BAD_ARG	"unit" or "symbol" is NULL.
 *	UT_OS		Operating-system error.  See "errno".
 *	UT_EXISTS	"unit" already maps to a symbol.
 */
EXTERNL ut_status
ut_map_unit_to_symbol(
    const ut_unit*		unit,
    const char* const		symbol,
    ut_encoding			encoding);


/*
 * Removes a mapping from a unit to a symbol.
 *
 * Arguments:
 *	unit		Pointer to the unit to be unmapped to a symbol.  May be
 *			freed upon return.
 *	encoding	The encoding to be removed.  The mappings for "unit" in
 *			other encodings will not be removed.
 * Returns:
 *	UT_SUCCESS	Success.
 *	UT_BAD_ARG	"unit" is NULL.
 */
EXTERNL ut_status
ut_unmap_unit_to_symbol(
    const ut_unit* const	unit,
    ut_encoding			encoding);


/******************************************************************************
 * Getting Information about a Unit:
 ******************************************************************************/


/*
 * Indicates if a given unit is dimensionless or not.  Note that logarithmic
 * units are dimensionless by definition.
 *
 * Arguments:
 *	unit	Pointer to the unit in question.
 * Returns:
 *	0	"unit" is dimensionfull or an error occurred.  "ut_get_status()"
 *		 will be
 *		    UT_BAD_ARG		"unit" is NULL.
 *		    UT_SUCCESS		"unit" is dimensionfull.
 *	else	"unit" is dimensionless.
 */
EXTERNL int
ut_is_dimensionless(
    const ut_unit* const	unit);


/*
 * Indicates if two units belong to the same unit-system.
 *
 * Arguments:
 *	unit1		Pointer to a unit.
 *	unit2		Pointer to another unit.
 * Returns:
 *	0		Failure or the units belong to different unit-systems.
 *			"ut_get_status()" will be
 *	    		    UT_BAD_ARG		"unit1" or "unit2" is NULL.
 *	    		    UT_SUCCESS		The units belong to different
 *						unit-systems.
 *	else		The units belong to the same unit-system.
 */
EXTERNL int
ut_same_system(
    const ut_unit* const	unit1,
    const ut_unit* const	unit2);


/*
 * Compares two units.  Returns a value less than, equal to, or greater than
 * zero as the first unit is considered less than, equal to, or greater than
 * the second unit, respectively.  Units from different unit-systems never
 * compare equal.
 *
 * Arguments:
 *	unit1		Pointer to a unit or NULL.
 *	unit2		Pointer to another unit or NULL.
 * Returns:
 *	<0	The first unit is less than the second unit.
 *	 0	The first and second units are equal or both units are NULL.
 *	>0	The first unit is greater than the second unit.
 */
EXTERNL int
ut_compare(
    const ut_unit* const	unit1,
    const ut_unit* const	unit2);


/*
 * Indicates if numeric values in one unit are convertible to numeric values in
 * another unit via "ut_get_converter()".  In making this determination,
 * dimensionless units are ignored.
 *
 * Arguments:
 *	unit1		Pointer to a unit.
 *	unit2		Pointer to another unit.
 * Returns:
 *	0		Failure.  "ut_get_status()" will be
 *	    		    UT_BAD_ARG		"unit1" or "unit2" is NULL.
 *			    UT_NOT_SAME_SYSTEM	"unit1" and "unit2" belong to
 *						different unit-sytems.
 *			    UT_SUCCESS		Conversion between the units is
 *						not possible (e.g., "unit1" is
 *						"meter" and "unit2" is
 *						"kilogram").
 *	else	Numeric values can be converted between the units.
 */
EXTERNL int
ut_are_convertible(
    const ut_unit* const	unit1,
    const ut_unit* const	unit2);


/*
 * Returns a converter of numeric values in one unit to numeric values in
 * another unit.  The returned converter should be passed to cv_free() when it is
 * no longer needed by the client.
 *
 * NOTE:  Leap seconds are not taken into account when converting between
 * timestamp units.
 *
 * Arguments:
 *	from		Pointer to the unit from which to convert values.
 *	to		Pointer to the unit to which to convert values.
 * Returns:
 *	NULL		Failure.  "ut_get_status()" will be:
 *			    UT_BAD_ARG		"from" or "to" is NULL.
 *			    UT_NOT_SAME_SYSTEM	"from" and "to" belong to
 *						different unit-systems.
 *			    UT_MEANINGLESS	Conversion between the units is
 *						not possible.  See
 *						"ut_are_convertible()".
 *	else		Pointer to the appropriate converter.  The pointer
 *			should be passed to cv_free() when no longer needed by
 *			the client.
 */
EXTERNL cv_converter*
ut_get_converter(
    ut_unit* const	from,
    ut_unit* const	to);


/******************************************************************************
 * Arithmetic Unit Manipulation:
 ******************************************************************************/


/*
 * Returns a unit equivalent to another unit scaled by a numeric factor,
 * e.g.,
 *	const ut_unit*	meter = ...
 *	const ut_unit*	kilometer = ut_scale(1000, meter);
 *
 * Arguments:
 *	factor		The numeric scale factor.
 *	unit		Pointer to the unit to be scaled.
 * Returns:
 *	NULL		Failure.  "ut_get_status()" will be
 *			    UT_BAD_ARG  	"factor" is 0 or "unit" is NULL.
 *			    UT_OS		Operating-system error.  See
 *						"errno".
 *	else		Pointer to the resulting unit.  The pointer should be
 *			passed to ut_free() when the unit is no longer needed by
 *			the client.
 */
EXTERNL ut_unit*
ut_scale(
    const double		factor,
    const ut_unit* const	unit);


/*
 * Returns a unit equivalent to another unit offset by a numeric amount,
 * e.g.,
 *	const ut_unit*	kelvin = ...
 *	const ut_unit*	celsius = ut_offset(kelvin, 273.15);
 *
 * Arguments:
 *	unit		Pointer to the unit to be offset.
 *	offset		The numeric offset.
 * Returns:
 *	NULL		Failure.  "ut_get_status()" will be
 *			    UT_BAD_ARG		"unit" is NULL.
 *			    UT_OS		Operating-system error.  See
 *						"errno".
 *	else		Pointer to the resulting unit.  The pointer should be
 *			passed to ut_free() when the unit is no longer needed by
 *			the client.
 */
EXTERNL ut_unit*
ut_offset(
    const ut_unit* const	unit,
    const double	offset);


/*
 * Returns a unit equivalent to another unit relative to a particular time.
 * e.g.,
 *	const ut_unit*	second = ...
 *	const ut_unit*	secondsSinceTheEpoch =
 *              ut_offset_by_time(second, ut_encode_time(1970, 1, 1, 0, 0, 0.0));
 *
 * Arguments:
 *	unit	Pointer to the time-unit to be made relative to a time-origin.
 *	origin	The origin as returned by ut_encode_time().
 * Returns:
 *	NULL	Failure.  "ut_get_status()" will be
 *		    UT_BAD_ARG		"unit" is NULL.
 *		    UT_OS		Operating-system error.  See "errno".
 *		    UT_MEANINGLESS	Creation of a timestamp unit based on
 *					"unit" is not meaningful.
 *		    UT_NO_SECOND	The associated unit-system doesn't
 *					contain a "second" unit.  See
 *					ut_set_second().
 *	else	Pointer to the resulting unit.  The pointer should be passed
 *		to ut_free() when the unit is no longer needed by the client.
 */
EXTERNL ut_unit*
ut_offset_by_time(
    const ut_unit* const	unit,
    const double	origin);


/*
 * Returns the result of multiplying one unit by another unit.
 *
 * Arguments:
 *	unit1	Pointer to a unit.
 *	unit2	Pointer to another unit.
 * Returns:
 *	NULL	Failure.  "ut_get_status()" will be:
 *		    UT_BAD_ARG		"unit1" or "unit2" is NULL.
 *		    UT_NOT_SAME_SYSTEM	"unit1" and "unit2" belong to
 *					different unit-systems.
 *		    UT_OS		Operating-system error. See "errno".
 *	else	Pointer to the resulting unit.  The pointer should be passed
 *		to ut_free() when the unit is no longer needed by the client.
 */
EXTERNL ut_unit*
ut_multiply(
    const ut_unit* const	unit1,
    const ut_unit* const	unit2);


/*
 * Returns the inverse (i.e., reciprocal) of a unit.  This convenience function
 * is equal to "ut_raise(unit, -1)".
 *
 * Arguments:
 *	unit	Pointer to the unit.
 * Returns:
 *	NULL	Failure.  "ut_get_status()" will be:
 *		    UT_BAD_ARG		"unit" is NULL.
 *		    UT_OS		Operating-system error. See "errno".
 *	else	Pointer to the resulting unit.  The pointer should be passed to
 *		ut_free() when the unit is no longer needed by the client.
 */
EXTERNL ut_unit*
ut_invert(
    const ut_unit* const	unit);


/*
 * Returns the result of dividing one unit by another unit.  This convenience
 * function is equivalent to the following sequence:
 *     {
 *         ut_unit* inverse = ut_invert(denom);
 *         ut_multiply(numer, inverse);
 *         ut_free(inverse);
 *     }
 *
 * Arguments:
 *	numer	Pointer to the numerator (top, dividend) unit.
 *	denom	Pointer to the denominator (bottom, divisor) unit.
 * Returns:
 *	NULL	Failure.  "ut_get_status()" will be:
 *		    UT_BAD_ARG		"numer" or "denom" is NULL.
 *		    UT_NOT_SAME_SYSTEM	"unit1" and "unit2" belong to
 *					different unit-systems.
 *		    UT_OS		Operating-system error. See "errno".
 *	else	Pointer to the resulting unit.  The pointer should be passed to
 *		ut_free() when the unit is no longer needed by the client.
 */
EXTERNL ut_unit*
ut_divide(
    const ut_unit* const	numer,
    const ut_unit* const	denom);


/*
 * Returns the result of raising a unit to a power.
 *
 * Arguments:
 *	unit	Pointer to the unit.
 *	power	The power by which to raise "unit".  Must be greater than or
 *		equal to -255 and less than or equal to 255.
 * Returns:
 *	NULL	Failure.  "ut_get_status()" will be:
 *		    UT_BAD_ARG		"unit" is NULL or "power" is invalid.
 *		    UT_OS		Operating-system error. See "errno".
 *	else	Pointer to the resulting unit.  The pointer should be passed to
 *		ut_free() when the unit is no longer needed by the client.
 */
EXTERNL ut_unit*
ut_raise(
    const ut_unit* const	unit,
    const int			power);


/*
 * Returns the result of taking the root of a unit.
 *
 * Arguments:
 *	unit	Pointer to the unit.
 *	root	The root to take of "unit".  Must be greater than or
 *		equal to 1 and less than or equal to 255.
 * Returns:
 *	NULL	Failure.  "ut_get_status()" will be:
 *		    UT_BAD_ARG		"unit" is NULL, or "root" is invalid.
 *		                        In particular, all powers of base units
 *		                        in "unit" must be integral multiples of
 *		                        "root".
 *		    UT_OS		Operating-system error. See "errno".
 *	else	Pointer to the resulting unit.  The pointer should be passed to
 *		ut_free() when the unit is no longer needed by the client.
 */
EXTERNL ut_unit*
ut_root(
    const ut_unit* const	unit,
    const int			root);


/*
 * Returns the logarithmic unit corresponding to a logarithmic base and a
 * reference level.  For example, the following creates a decibel unit with a
 * one milliwatt reference level:
 *
 *     const ut_unit* watt = ...;
 *     const ut_unit* milliWatt = ut_scale(0.001, watt);
 *
 *     if (milliWatt != NULL) {
 *         const ut_unit* bel_1_mW = ut_log(10.0, milliWatt);
 *
 *         if (bel_1_mW != NULL) {
 *             const ut_unit* decibel_1_mW = ut_scale(0.1, bel_1_mW);
 *
 *             if (decibel_1_mW != NULL) {
 *                 ...
 *                 ut_free(decibel_1_mW);
 *             }			// "decibel_1_mW" allocated
 *
 *             ut_free(bel_1_mW);
 *         }				// "bel_1_mW" allocated
 *
 *         ut_free(milliWatt);
 *     }				// "milliWatt" allocated
 *
 * Arguments:
 *	base		The logarithmic base (e.g., 2, M_E, 10).  Must be
 *                      greater than one.  "M_E" is defined in <math.h>.
 *	reference	Pointer to the reference value as a unit.
 * Returns:
 *	NULL		Failure.  "ut_get_status()" will be:
 *			    UT_BAD_ARG	        "base" is invalid or "reference"
 *                                              is NULL.
 *			    UT_OS		Operating-system error. See
 *						"errno".
 *	else		Pointer to the resulting unit.  The pointer should be
 *			passed to ut_free() when the unit is no longer needed by
 *			the client.
 */
EXTERNL ut_unit*
ut_log(
    const double		base,
    const ut_unit* const	reference);


/******************************************************************************
 * Parsing and Formatting Units:
 ******************************************************************************/


/*
 * Returns the binary representation of a unit corresponding to a string
 * representation.
 *
 * Arguments:
 *	system		Pointer to the unit-system in which the parsing will
 *			occur.
 *	string		The string to be parsed (e.g., "millimeters").  There
 *			should be no leading or trailing whitespace in the
 *			string.  See ut_trim().
 *	encoding	The encoding of "string".
 * Returns:
 *	NULL		Failure.  "ut_get_status()" will be one of
 *			    UT_BAD_ARG		"system" or "string" is NULL.
 *			    UT_SYNTAX		"string" contained a syntax
 *						error.
 *			    UT_UNKNOWN		"string" contained an unknown
 *						identifier.
 *			    UT_OS		Operating-system failure.  See
 *						"errno".
 *	else		Pointer to the unit corresponding to "string".
 */
EXTERNL ut_unit*
ut_parse(
    const ut_system* const	system,
    const char* const		string,
    ut_encoding		        encoding);


/*
 * Removes leading and trailing whitespace from a string.
 *
 * Arguments:
 *	string		NUL-terminated string.  Will be modified if it contains
 *                      whitespace..
 *	encoding	The character-encoding of "string".
 * Returns:
 *      "string", with all leading and trailing whitespace removed.
 */
EXTERNL char*
ut_trim(
    char* const	        string,
    const ut_encoding	encoding);


/*
 * Formats a unit.
 *
 * Arguments:
 *	unit		Pointer to the unit to be formatted.
 *	buf		Pointer to the buffer into which to format "unit".
 *	size		Size of the buffer in bytes.
 *	opts		Formatting options: bitwise-OR of zero or more of the
 *			following:
 *			    UT_NAMES		Use unit names instead of
 *						symbols
 *                          UT_DEFINITION       The formatted string should be
 *                                              the definition of "unit" in
 *                                              terms of basic-units instead of
 *						stopping any expansion at the
 *						highest level possible.
 *			    UT_ASCII		The string should be formatted
 *						using the ASCII character set
 *						(default).
 *			    UT_LATIN1		The string should be formatted
 *						using the ISO Latin-1 (alias
 *						ISO-8859-1) character set.
 *			    UT_UTF8		The string should be formatted
 *						using the UTF-8 character set.
 *			UT_LATIN1 and UT_UTF8 are mutually exclusive: they may
 *			not both be specified.
 * Returns:
 *	-1		Failure:  "ut_get_status()" will be
 *			    UT_BAD_ARG		"unit" or "buf" is NULL, or both
 *                                              UT_LATIN1 and UT_UTF8 specified.
 *			    UT_CANT_FORMAT	"unit" can't be formatted in
 *						the desired manner.
 *      else		Success.  Number of characters printed in "buf".  If
 *			the number is equal to the size of the buffer, then the
 *			buffer is too small to have a terminating NUL character.
 */
EXTERNL int
ut_format(
    const ut_unit* const	unit,
    char*		buf,
    size_t		size,
    unsigned		opts);


/*
 * Accepts a visitor to a unit.
 *
 * Arguments:
 *	unit		Pointer to the unit to accept the visitor.
 *	visitor		Pointer to the visitor of "unit".
 *	arg		An arbitrary pointer that will be passed to "visitor".
 * Returns:
 *	UT_BAD_ARG	"unit" or "visitor" is NULL.
 *	UT_VISIT_ERROR	A error occurred in "visitor" while visiting "unit".
 *	UT_SUCCESS	Success.
 */
EXTERNL ut_status
ut_accept_visitor(
    const ut_unit* const		unit,
    const ut_visitor* const	visitor,
    void* const			arg);


/******************************************************************************
 * Time Handling:
 ******************************************************************************/


/*
 * Encodes a date as a double-precision value.
 *
 * Arguments:
 *	year		The year.
 *	month		The month.
 *	day		The day (1 = the first of the month).
 * Returns:
 *	The date encoded as a scalar value.
 */
EXTERNL double
ut_encode_date(
    int		year,
    int		month,
    int		day);


/*
 * Encodes a time as a double-precision value. If an input value isn't within
 * its allowed range, then zero is returned and `ut_get_status()` will return
 * `UT_BAD_ARG`.
 *
 * @param[in] hours    The number of hours (0 = midnight). `abs(hours)` must be
 *                     less than 24.
 * @param[in] minutes  The number of minutes. `abs(minutes)` must be less than
 *                     60.
 * @param[in] seconds  The number of seconds. `fabs(seconds)` must be less than
 *                     or equal to 62.
 * @return             The clock-time encoded as a scalar value.
 */
EXTERNL double
ut_encode_clock(
    int    hours,
    int    minutes,
    double seconds);


/*
 * Encodes a time as a double-precision value.  The convenience function is
 * equivalent to "ut_encode_date(year,month,day) +
 * ut_encode_clock(hour,minute,second)"
 *
 * Arguments:
 *	year	The year.
 *	month	The month.
 *	day	The day.
 *	hour	The hour.
 *	minute	The minute.
 *	second	The second.
 * Returns:
 *	The time encoded as a scalar value.
 */
EXTERNL double
ut_encode_time(
    const int		year,
    const int		month,
    const int		day,
    const int		hour,
    const int		minute,
    const double	second);


/*
 * Decodes a time from a double-precision value.
 *
 * Arguments:
 *      value           The value to be decoded.
 *      year            Pointer to the variable to be set to the year.
 *      month           Pointer to the variable to be set to the month.
 *      day             Pointer to the variable to be set to the day.
 *      hour            Pointer to the variable to be set to the hour.
 *      minute          Pointer to the variable to be set to the minute.
 *      second          Pointer to the variable to be set to the second.
 *      resolution      Pointer to the variable to be set to the resolution
 *                      of the decoded time in seconds.
 */
EXTERNL void
ut_decode_time(
    double	value,
    int		*year,
    int		*month,
    int		*day,
    int		*hour,
    int		*minute,
    double	*second,
    double	*resolution);


/******************************************************************************
 * Error Handling:
 ******************************************************************************/


/*
 * Returns the status of the last operation by the units module.  This function
 * will not change the status.
 */
EXTERNL ut_status
ut_get_status(void);


/*
 * Sets the status of the units module.  This function would not normally be
 * called by the user unless they were doing their own parsing or formatting.
 *
 * Arguments:
 *	status	The status of the units module.
 */
EXTERNL void
ut_set_status(
    ut_status	status);


/*
 * Handles an error-message.
 *
 * Arguments:
 *	fmt	The format for the error-message.
 *	...	The arguments for "fmt".
 * Returns:
 *	<0	An output error was encountered.
 *	else	The number of bytes of "fmt" and "arg" written excluding any
 *		terminating NUL.
 */
EXTERNL int
ut_handle_error_message(
    const char* const	fmt,
    ...);


/*
 * Returns the previously-installed error-message handler and optionally
 * installs a new handler.  The initial handler is "ut_write_to_stderr()".
 *
 * Arguments:
 *      handler		NULL or pointer to the error-message handler.  If NULL,
 *			then the handler is not changed.  The
 *			currently-installed handler can be obtained this way.
 * Returns:
 *	Pointer to the previously-installed error-message handler.
 */
EXTERNL ut_error_message_handler
ut_set_error_message_handler(
    ut_error_message_handler	handler);


/*
 * Writes an error-message to the standard-error stream when received and
 * appends a newline.  This is the initial error-message handler.
 *
 * Arguments:
 *	fmt	The format for the error-message.
 *	args	The arguments of "fmt".
 * Returns:
 *	<0	A output error was encountered.  See "errno".
 *	else	The number of bytes of "fmt" and "arg" written excluding any
 *		terminating NUL.
 */
EXTERNL int
ut_write_to_stderr(
    const char* const	fmt,
    va_list		args);


/*
 * Does nothing with an error-message.
 *
 * Arguments:
 *	fmt	The format for the error-message.
 *	args	The arguments of "fmt".
 * Returns:
 *	0	Always.
 */
EXTERNL int
ut_ignore(
    const char* const	fmt,
    va_list		args);


#ifdef __cplusplus
}
#endif

#endif
