/**
 * @file format.h
 * @brief `PRINT USING` field formatting, and the `PUDEF` characters it fills with.
 *
 * Its own translation unit rather than a helper inside the PRINT handler,
 * because the interesting part is a pure function of a format string and a
 * value: it can be tested exhaustively without a runtime, a sink or a program,
 * and field formatting has far more edge cases than a verb usually does.
 */

#ifndef _AKBASIC_FORMAT_H_
#define _AKBASIC_FORMAT_H_

#include <akerror.h>

#include <akbasic/types.h>
#include <akbasic/value.h>

/** @brief How many characters `PUDEF` can redefine. */
#define AKBASIC_PUDEF_CHARS	4

/** @brief `PUDEF` position 1: what a leading blank in a numeric field is filled with. */
#define AKBASIC_PUDEF_BLANK	0
/** @brief `PUDEF` position 2: the thousands separator. */
#define AKBASIC_PUDEF_COMMA	1
/** @brief `PUDEF` position 3: the decimal point. */
#define AKBASIC_PUDEF_POINT	2
/** @brief `PUDEF` position 4: the currency sign. */
#define AKBASIC_PUDEF_DOLLAR	3

/**
 * @brief The four characters `PUDEF` redefines.
 *
 * Lives on the runtime beside the other verb-group state: it is the program's,
 * not the sink's, and a host that swaps one output device for another does not
 * expect the program's punctuation to change.
 */
typedef struct
{
    char chars[AKBASIC_PUDEF_CHARS];
} akbasic_FormatState;

/**
 * @brief Reset the `PUDEF` characters to their defaults: space, comma, point, dollar.
 * @param obj Object to initialize, inspect, or modify.
 * @return `NULL` on success, otherwise an error context owned by the caller.
 * @throws AKERR_NULLPOINTER When `obj` is NULL.
 */
akerr_ErrorContext AKERR_NOIGNORE *akbasic_format_state_init(akbasic_FormatState *obj);

/**
 * @brief Redefine the fill characters, up to four of them.
 *
 * Positions past the end of @p chars keep whatever they had, so `PUDEF "*"`
 * changes only the leading-blank character. That is BASIC 7.0's rule.
 *
 * @param obj Object to initialize, inspect, or modify.
 * @param chars Replacement characters, in PUDEF order; at most four are read.
 * @return `NULL` on success, otherwise an error context owned by the caller.
 * @throws AKERR_NULLPOINTER When either argument is NULL.
 */
akerr_ErrorContext AKERR_NOIGNORE *akbasic_format_pudef(akbasic_FormatState *obj, const char *chars);

/**
 * @brief Render one value into one `PRINT USING` field.
 *
 * @p format is the whole format string; the *first* field in it is the one used,
 * and any literal text around that field is copied through. That is how BASIC
 * 7.0 works -- `PRINT USING "TOTAL: ###.##"; X` prints the label as well.
 *
 * Numeric fields are built from `#` digit positions, an optional `.` and
 * embedded `,` separators, with an optional leading or trailing `$`, `+` or `-`.
 * String fields are `=` to centre and `>` to right-justify, and a bare run of
 * `#` left-justifies.
 *
 * **A value too wide for its field fills the field with `*`**, which is BASIC
 * 7.0's answer and is deliberately loud: silently printing more digits than the
 * field asked for would misalign every column after it.
 *
 * @param obj The PUDEF characters to fill with.
 * @param format The format string.
 * @param value The value to render.
 * @param dest Output destination populated by the function.
 * @param len Capacity of @p dest, terminator included.
 * @return `NULL` on success, otherwise an error context owned by the caller.
 * @throws AKERR_NULLPOINTER When any argument is NULL.
 * @throws AKBASIC_ERR_BOUNDS When the result does not fit @p dest.
 * @throws AKBASIC_ERR_SYNTAX When the format string contains no field at all.
 */
akerr_ErrorContext AKERR_NOIGNORE *akbasic_format_using(akbasic_FormatState *obj, const char *format, akbasic_Value *value, char *dest, size_t len);

#endif // _AKBASIC_FORMAT_H_
