#ifndef UNITYLIB_H_LOADED
#define UNITYLIB_H_LOADED 1

/**
 * @file unity.h
 */

#include "known-syntaxes.h"
#include "unit-definitions.h"
#include "function-definitions.h"

/** @struct UnitExpression
 * The parsed version of a unit string.
 */
typedef struct {
    /**
     * The base-10 log of the numerical factor at the head of the string.
     * For some parsers, this is restricted to round powers of ten.
     */
    float log_factor;
    /**
     * The sequence of units which comprise the parsed units specification.
     */
    const struct unit_struct* unit_sequence;

    /* a sorted version of the unit_sequence */
    struct unit_struct** sorted_unit_sequence;
    int nunits;
} UnitExpression;

/** @struct FunctionApplication
 * A unit corresponding to an application of a function to a unit
 * sequence.  In the case of <code>log(V^2/Hz)</code>, for example, the function
 * is ‘log’ and the operand is the parsed units corresponding to
 * ‘V^2/Hz’.
 */
typedef struct {
    /** The definition of the function used.
     * Precisely one of definition and name should be non-null */
    const struct function_definition* definition;
    /** The name of an unrecognised function used.
     * Precisely one of definition and name should be non-null */
    const char* name;
    /** The parsed units to which the function is applied. */
    //const struct unit_struct* operand;
    UnitExpression operand;
} FunctionApplication;

/** @struct SimpleUnit
 * A simple unit, corresponding to, for example, ‘MHz’.
 */
typedef struct {
    /**
     * The base-10 logarithm of the prefix in front of the unit.  For
     * example, this will be ‘-3’ for the ‘m’ (milli-) prefix.
     */
    int prefix_power;           /* -24 to +24 */
    /**
     * True (non-zero) if the prefix is a power of 10;
     * false if it's a power of 2.
     */
    char base10_p;
    /**
     * The base unit of this unit specification, as a known unit.  If
     * this is not a known unit (in the syntax which parsed the
     * associated expression) then this will be NULL.
     */
    const UnitDef* base_unit_def;
    /**
     * The base unit of this unit specification.  This might be ‘m’
     * for metres, ‘Hz’ for Hertz, and so on.
     */
    char* base_unit_string;     /* eg Hz or aangstrom */
} SimpleUnit;

/** An enumeration indicating the two types of unit possible in
 *  {@link unit_struct}
 */
typedef enum {
    /** Simple unit type (eg ‘MHz’) */
    simple_unit_type,
    /** Function-of unit type (eg <code>log(MHz)</code>) */
    function_application_type
} UnitTypes;

/** @struct unit_struct
 * A single parsed unit.  This can be either a simple unit
 * (corresponding to, for example, ‘MHz’) or a function of a sequence
 * of units (corresponding to, for example, <code>log(V^2/mHz)</code>).
 *
 * <p>A unit can be ‘quoted’, indicating that it is to be parsed
 * as an ‘unknown’ unit even if its name matches that of a ‘known’ unit
 * (for example, <code>'B'</code> is a unit of ‘B’, and neither byte
 * nor Bel, and the <code>'furlong'</code> is a ‘furlong’ and not, as
 * it would otherwise be parsed, a femto-urlong).
 * This mechanism is syntactically permitted only in the VOUnits syntax,
 * and is used only for output and validity checks
 * (see {@link unity_check_unit}), and not for processing.
 * All ‘quoted’ units are classed as not ‘recognised’.
 */
struct unit_struct {
    /**
     * The power to which the unit is raised.  The specification
     * <code>mm^(-2)</code> will result in a power of ‘-2’.
     */
    float power;
    /**
     * Indicates which type of unit this is.
     * If this is of type <code>simple_unit_type</code>, then the ‘u’ struct will be valid;
     * if it is of type <code>function_application_type</code>, then ‘f’ will be valid.
     */
    UnitTypes type;
    union {
        /** A contained simple unit */
        SimpleUnit u;
        /** A contained function-of unit */
        FunctionApplication f;
    };
    /** A pointer to the next unit in the sequence */
    struct unit_struct *next;

    /**
     * True (non-zero) if this unit was a ‘quoted’ unit.
     * This only makes sense for VOUnits units, and is used only when
     * writing out units rather than in any processing.
     */
    char is_quoted_unit;
};


/** A clearer name for struct unit_struct */
typedef struct unit_struct Unit;

const UnitExpression* unity_parse_string(const char* unit_string, const UnitySyntax syntax);

/* The function unity_parse_error is declared (and documented) here,
   and defined in lex.lex */
/**
 * Retrieve the most recent lexing or parsing error.  Immediately after
 * the parser has returned with an error (that is function
 * {@link #unity_parse_string} has returned NULL) this function can be called
 * to obtain an explanation.  However, a non-null response from this
 * function does not imply that the most recent call to `unity_parse_string' failed.
 *
 * <p>The returned string should not be freed by the caller.
 * @return a static string containing an explanation of a parse error
 */
const char* unity_parse_error(void);

const char** unity_parser_names(void);

UnitySyntax unity_identify_parser(const char* parser_name);
const char* unity_parser_name(const UnitySyntax parser_id);
void unity_free_expression(const UnitExpression* u);
const char* unity_version_string();
int unity_version_number();

/* Selecting units (convenience functions, since the UnitExpression struct is open */
const Unit* unity_get_unit_by_index(const UnitExpression*, int i);
const UnitDef* unity_get_unitdef_from_unit(const Unit*);
const FunctionDef* unity_get_functiondef_from_unit(const Unit*);

/* Checking units */
/** Flag: check whether the unit is recognised.  See {@link #unity_check_unit} */
#define UNITY_CHECK_RECOGNISED 1
/** Flag: check whether the unit is recommended.  See {@link #unity_check_unit} */
#define UNITY_CHECK_RECOMMENDED 2
/** Flag: check whether the unit is used in a way which satisfies constraints.  See {@link #unity_check_unit} */
#define UNITY_CHECK_CONSTRAINTS 4
/** Flag: perform all checks.
 * Do not rely on the numerical value of this or its related constants,
 * which may change between library versions without notice.
 * See {@link #unity_check_unit}
 */
#define UNITY_CHECK_ALL UNITY_CHECK_RECOGNISED | UNITY_CHECK_RECOMMENDED | UNITY_CHECK_CONSTRAINTS
int unity_check_unit(const Unit* u, const UnitySyntax syntax, const int flags);
int unity_check_expression(const UnitExpression* ue, const UnitySyntax syntax, const int flags);
int unity_equal_unit_p(const Unit*, const Unit*);
int unity_equal_expression_p(const UnitExpression*, const UnitExpression*);

#ifdef UNITY_INTERNAL
/* Internal functions */
Unit* u_new_unit(const char* unitString, const float power, const UnitySyntax syntax);
Unit* u_quoted_unit(const char* pfx, const char* unitString,
                    const float power, UnitySyntax syntax);
Unit* u_power_of_unit(Unit* u0, const float power);
Unit* u_function_application(const char* functionName,
                             const Unit* unit_sequence,
                             const UnitySyntax syntax);
Unit* u_unit_append(Unit* u1, const Unit* u2);
void u_receive_result(float, const Unit* unit_sequence);
int u_prefix_to_power(const char pfx);
Unit* u_divide_units(Unit* num, Unit* den);
Unit* u_unit_reciprocal(Unit* u);
void u_free_unit(const Unit* u);
Unit** u_get_sorted_unit_sequence(UnitExpression*, int*);
#endif

#endif
