/**
 * @file value.h
 * @brief Declares the strongly-typed BASIC value and its operators.
 *
 * Ported from the reference's basicvalue.go. The arithmetic is reproduced
 * exactly, except where a defect has since been fixed on its own merits -- see
 * TODO.md §6 for the catalogue and what became of each.
 *
 * **The left operand decides whether an operation is done in integers or in
 * floats**, and this is deliberate rather than an oversight: every operator
 * branches on `self->valuetype` and converts the right operand to match, so an
 * integer on the left truncates a float on the right. `A# * 0.45` is 0 and
 * `0.45 * A#` is 1.35. It is inherited from the reference
 * (basicvalue.go:190-193), a C128 promotes to float instead, and nothing fails
 * when a program gets it wrong -- it computes something else and carries on.
 * Documented in docs/03-the-language.md and docs/13-differences.md, and pinned
 * in tests/value_arithmetic.c so that changing it has to be a decision.
 *
 * Every binary operator takes a `scratch` value the caller has drawn from an
 * environment's pool and an out-parameter `dest`. Most operators set `*dest` to
 * `scratch` and fill it; akbasic_value_math_plus sets `*dest` to `self` when
 * `self` is mutable, because the reference mutates in place there and
 * CommandNEXT's increment relies on it.
 */

#ifndef _AKBASIC_VALUE_H_
#define _AKBASIC_VALUE_H_

#include <akerror.h>

#include <akbasic/types.h>

/*
 * Backing store for arrays and structures. They draw their storage from here
 * rather than from malloc, and a variable holds a pointer into it. The runtime
 * owns exactly one; it is passed explicitly rather than kept at file scope,
 * because the interpreter must be embeddable and may not own global mutable
 * state.
 *
 * **A scalar is not in here.** It lives in the variable itself
 * (akbasic_Variable::inlinevalue), because scope exit *does* destroy variables
 * -- it returns the slot but not its storage -- and a bump allocator has no way
 * to take a scalar back. See akbasic_variable_init().
 *
 * The allocator is a bump: releasing is not supported. Re-DIMming an array to
 * the same size or smaller reuses its existing slice; growing it takes fresh
 * slots and abandons the old ones. A program that re-DIMs the same array larger
 * in a loop will exhaust the pool and get AKBASIC_ERR_BOUNDS -- bounded and
 * diagnosable, which is the point. A local *array* therefore still costs slots
 * that do not come back, which is the same statement and is deliberate: a
 * pointer into a record outlives the scope that DIMmed it.
 */
typedef struct
{
    int next;
    akbasic_Value values[AKBASIC_MAX_ARRAY_VALUES];
} akbasic_ValuePool;

/** @brief Reset the pool to empty. */
akerr_ErrorContext AKERR_NOIGNORE *akbasic_valuepool_init(akbasic_ValuePool *obj);

/**
 * @brief Take `count` contiguous zeroed values from the pool.
 * @param obj Object to initialize, inspect, or modify.
 * @param count Number of elements required.
 * @param dest Output destination populated by the function.
 * @return `NULL` on success, otherwise an error context owned by the caller.
 * @throws AKBASIC_ERR_BOUNDS When the pool has too few slots left.
 */
akerr_ErrorContext AKERR_NOIGNORE *akbasic_valuepool_take(akbasic_ValuePool *obj, int count, akbasic_Value **dest);

/** @brief Reset a value to a usable empty state. */
akerr_ErrorContext AKERR_NOIGNORE *akbasic_value_init(akbasic_Value *obj);

/** @brief Clear a value to undefined, zero, immutable and unnamed. */
akerr_ErrorContext AKERR_NOIGNORE *akbasic_value_zero(akbasic_Value *obj);

/**
 * @brief Copy the payload of one value onto another.
 *
 * Copies name, type, string, integer, float and boolean payloads. It does *not*
 * copy `mutable_`: the destination keeps its own, matching the reference, where
 * clone() writes every field except that one.
 */
akerr_ErrorContext AKERR_NOIGNORE *akbasic_value_clone(akbasic_Value *self, akbasic_Value *dest);

/** @brief Render a value the way PRINT does. */
akerr_ErrorContext AKERR_NOIGNORE *akbasic_value_to_string(akbasic_Value *self, char *dest, size_t len);

/** @brief Set a value to a BASIC boolean (-1 true, 0 false). */
akerr_ErrorContext AKERR_NOIGNORE *akbasic_value_set_bool(akbasic_Value *obj, bool result);

/** @brief True when the value is a boolean holding AKBASIC_TRUE. */
bool akbasic_value_is_true(akbasic_Value *self);

/**
 * @brief True when the value is nonzero, whatever numeric type it carries.
 *
 * What a condition tests. Commodore BASIC has no boolean type -- a comparison
 * yields -1 or 0, `AND` and `OR` are the bitwise operators, and `IF A THEN` is
 * legal for any numeric A -- so "is this a boolean holding true" is the wrong
 * question for a branch to ask. Strings are never true.
 *
 * @param self Value to test; NULL is not true.
 * @return `true` when the value is a nonzero number or a true truth value.
 */
bool akbasic_value_is_truthy(akbasic_Value *self);

/**
 * @brief Negate a numeric value.
 *
 * Negates both the integer and float payloads, so it is correct whichever one
 * the value is carrying.
 * @param self Operand.
 * @param scratch A value drawn from the caller's pool for the result to land in.
 * @param dest Output destination populated by the function; set to `scratch`.
 * @return `NULL` on success, otherwise an error context owned by the caller.
 * @throws AKBASIC_ERR_TYPE When `self` is a string.
 */
akerr_ErrorContext AKERR_NOIGNORE *akbasic_value_invert(akbasic_Value *self, akbasic_Value *scratch, akbasic_Value **dest);
/**
 * @brief One's complement of an integer value.
 * @param self Operand.
 * @param scratch A value drawn from the caller's pool for the result to land in.
 * @param dest Output destination populated by the function; set to `scratch`.
 * @return `NULL` on success, otherwise an error context owned by the caller.
 * @throws AKBASIC_ERR_TYPE When `self` is not an integer.
 */
akerr_ErrorContext AKERR_NOIGNORE *akbasic_value_bitwise_not(akbasic_Value *self, akbasic_Value *scratch, akbasic_Value **dest);

/**
 * @brief Shift an integer value left.
 *
 * Refuses a count outside 0..63. Go defines the result for any count; in C it
 * would be undefined behaviour, so this is a deliberate deviation.
 * @param self Operand; must be an integer.
 * @param bits Number of bit positions to shift by; must be 0..63.
 * @param scratch A value drawn from the caller's pool for the result to land in.
 * @param dest Output destination populated by the function; set to `scratch`.
 * @return `NULL` on success, otherwise an error context owned by the caller.
 * @throws AKBASIC_ERR_TYPE When `self` is not an integer.
 * @throws AKBASIC_ERR_VALUE When `bits` is outside 0..63.
 */
akerr_ErrorContext AKERR_NOIGNORE *akbasic_value_shift_left(akbasic_Value *self, int64_t bits, akbasic_Value *scratch, akbasic_Value **dest);
/**
 * @brief Shift an integer value right.
 * @param self Operand; must be an integer.
 * @param bits Number of bit positions to shift by; must be 0..63.
 * @param scratch A value drawn from the caller's pool for the result to land in.
 * @param dest Output destination populated by the function; set to `scratch`.
 * @return `NULL` on success, otherwise an error context owned by the caller.
 * @throws AKBASIC_ERR_TYPE When `self` is not an integer.
 * @throws AKBASIC_ERR_VALUE When `bits` is outside 0..63.
 */
akerr_ErrorContext AKERR_NOIGNORE *akbasic_value_shift_right(akbasic_Value *self, int64_t bits, akbasic_Value *scratch, akbasic_Value **dest);

/**
 * @brief Bitwise AND of two integer values.
 * @param self Left operand, and the value whose type selects the operation.
 * @param rval Right operand.
 * @param scratch A value drawn from the caller's pool for the result to land in.
 * @param dest Output destination populated by the function; set to `scratch`.
 * @return `NULL` on success, otherwise an error context owned by the caller.
 * @throws AKBASIC_ERR_TYPE When `self` is not an integer.
 */
akerr_ErrorContext AKERR_NOIGNORE *akbasic_value_bitwise_and(akbasic_Value *self, akbasic_Value *rval, akbasic_Value *scratch, akbasic_Value **dest);
/**
 * @brief Bitwise OR of two integer values.
 * @param self Left operand, and the value whose type selects the operation.
 * @param rval Right operand.
 * @param scratch A value drawn from the caller's pool for the result to land in.
 * @param dest Output destination populated by the function; set to `scratch`.
 * @return `NULL` on success, otherwise an error context owned by the caller.
 * @throws AKBASIC_ERR_TYPE When `self` is not an integer.
 */
akerr_ErrorContext AKERR_NOIGNORE *akbasic_value_bitwise_or(akbasic_Value *self, akbasic_Value *rval, akbasic_Value *scratch, akbasic_Value **dest);
/**
 * @brief Bitwise exclusive OR of two integer values.
 * @param self Left operand, and the value whose type selects the operation.
 * @param rval Right operand.
 * @param scratch A value drawn from the caller's pool for the result to land in.
 * @param dest Output destination populated by the function; set to `scratch`.
 * @return `NULL` on success, otherwise an error context owned by the caller.
 * @throws AKBASIC_ERR_TYPE When either operand is not an integer.
 */
akerr_ErrorContext AKERR_NOIGNORE *akbasic_value_bitwise_xor(akbasic_Value *self, akbasic_Value *rval, akbasic_Value *scratch, akbasic_Value **dest);

/**
 * @brief Add two values, or concatenate when the left operand is a string.
 *
 * The one operator that does not always clone: when `self` is mutable the result
 * is written into `self` and `*dest` is set to it rather than to `scratch`.
 * CommandNEXT's loop increment depends on that, so it is not an oversight --
 * see TODO.md section 6 item 4.
 * @param self Left operand, and the value whose type selects the operation.
 * @param rval Right operand.
 * @param scratch A value drawn from the caller's pool for the result to land in.
 * @param dest Output destination populated by the function; set to `scratch`.
 * @return `NULL` on success, otherwise an error context owned by the caller.
 * @throws AKBASIC_ERR_TYPE When the operand types have no defined addition.
 * @throws AKBASIC_ERR_VALUE When a concatenated string exceeds the length limit.
 */
akerr_ErrorContext AKERR_NOIGNORE *akbasic_value_math_plus(akbasic_Value *self, akbasic_Value *rval, akbasic_Value *scratch, akbasic_Value **dest);
/**
 * @brief Subtract one numeric value from another.
 * @param self Left operand, and the value whose type selects the operation.
 * @param rval Right operand.
 * @param scratch A value drawn from the caller's pool for the result to land in.
 * @param dest Output destination populated by the function; set to `scratch`.
 * @return `NULL` on success, otherwise an error context owned by the caller.
 * @throws AKBASIC_ERR_TYPE When either operand is a string.
 */
akerr_ErrorContext AKERR_NOIGNORE *akbasic_value_math_minus(akbasic_Value *self, akbasic_Value *rval, akbasic_Value *scratch, akbasic_Value **dest);
/**
 * @brief Multiply two numeric values, or repeat a string.
 *
 * A string left operand is repeated `rval->intval` times, which is what makes
 * `" " * 5` work.
 * @param self Left operand, and the value whose type selects the operation.
 * @param rval Right operand.
 * @param scratch A value drawn from the caller's pool for the result to land in.
 * @param dest Output destination populated by the function; set to `scratch`.
 * @return `NULL` on success, otherwise an error context owned by the caller.
 * @throws AKBASIC_ERR_TYPE When both operands are strings.
 * @throws AKBASIC_ERR_VALUE When the multiplier is negative or the result would overflow the length limit.
 */
akerr_ErrorContext AKERR_NOIGNORE *akbasic_value_math_multiply(akbasic_Value *self, akbasic_Value *rval, akbasic_Value *scratch, akbasic_Value **dest);
/**
 * @brief Divide one numeric value by another.
 * @param self Left operand, and the value whose type selects the operation.
 * @param rval Right operand.
 * @param scratch A value drawn from the caller's pool for the result to land in.
 * @param dest Output destination populated by the function; set to `scratch`.
 * @return `NULL` on success, otherwise an error context owned by the caller.
 * @throws AKBASIC_ERR_TYPE When either operand is a string.
 * @throws AKBASIC_ERR_VALUE On integer division by zero.
 */
akerr_ErrorContext AKERR_NOIGNORE *akbasic_value_math_divide(akbasic_Value *self, akbasic_Value *rval, akbasic_Value *scratch, akbasic_Value **dest);

/**
 * @brief Compare two values for less-than, yielding a BASIC boolean.
 * @param self Left operand, and the value whose type selects the operation.
 * @param rval Right operand.
 * @param scratch A value drawn from the caller's pool for the result to land in.
 * @param dest Output destination populated by the function; set to `scratch`.
 * @return `NULL` on success, otherwise an error context owned by the caller.
 * @throws AKERR_NULLPOINTER When any argument is NULL.
 */
akerr_ErrorContext AKERR_NOIGNORE *akbasic_value_less_than(akbasic_Value *self, akbasic_Value *rval, akbasic_Value *scratch, akbasic_Value **dest);
/**
 * @brief Compare two values for less-than-or-equal, yielding a BASIC boolean.
 * @param self Left operand, and the value whose type selects the operation.
 * @param rval Right operand.
 * @param scratch A value drawn from the caller's pool for the result to land in.
 * @param dest Output destination populated by the function; set to `scratch`.
 * @return `NULL` on success, otherwise an error context owned by the caller.
 * @throws AKERR_NULLPOINTER When any argument is NULL.
 */
akerr_ErrorContext AKERR_NOIGNORE *akbasic_value_less_than_equal(akbasic_Value *self, akbasic_Value *rval, akbasic_Value *scratch, akbasic_Value **dest);
/**
 * @brief Compare two values for greater-than, yielding a BASIC boolean.
 * @param self Left operand, and the value whose type selects the operation.
 * @param rval Right operand.
 * @param scratch A value drawn from the caller's pool for the result to land in.
 * @param dest Output destination populated by the function; set to `scratch`.
 * @return `NULL` on success, otherwise an error context owned by the caller.
 * @throws AKERR_NULLPOINTER When any argument is NULL.
 */
akerr_ErrorContext AKERR_NOIGNORE *akbasic_value_greater_than(akbasic_Value *self, akbasic_Value *rval, akbasic_Value *scratch, akbasic_Value **dest);
/**
 * @brief Compare two values for greater-than-or-equal, yielding a BASIC boolean.
 * @param self Left operand, and the value whose type selects the operation.
 * @param rval Right operand.
 * @param scratch A value drawn from the caller's pool for the result to land in.
 * @param dest Output destination populated by the function; set to `scratch`.
 * @return `NULL` on success, otherwise an error context owned by the caller.
 * @throws AKERR_NULLPOINTER When any argument is NULL.
 */
akerr_ErrorContext AKERR_NOIGNORE *akbasic_value_greater_than_equal(akbasic_Value *self, akbasic_Value *rval, akbasic_Value *scratch, akbasic_Value **dest);
/**
 * @brief Compare two values for equality, yielding a BASIC boolean.
 * @param self Left operand, and the value whose type selects the operation.
 * @param rval Right operand.
 * @param scratch A value drawn from the caller's pool for the result to land in.
 * @param dest Output destination populated by the function; set to `scratch`.
 * @return `NULL` on success, otherwise an error context owned by the caller.
 * @throws AKERR_NULLPOINTER When any argument is NULL.
 */
akerr_ErrorContext AKERR_NOIGNORE *akbasic_value_is_equal(akbasic_Value *self, akbasic_Value *rval, akbasic_Value *scratch, akbasic_Value **dest);
/**
 * @brief Compare two values for inequality, yielding a BASIC boolean.
 * @param self Left operand, and the value whose type selects the operation.
 * @param rval Right operand.
 * @param scratch A value drawn from the caller's pool for the result to land in.
 * @param dest Output destination populated by the function; set to `scratch`.
 * @return `NULL` on success, otherwise an error context owned by the caller.
 * @throws AKERR_NULLPOINTER When any argument is NULL.
 */
akerr_ErrorContext AKERR_NOIGNORE *akbasic_value_is_not_equal(akbasic_Value *self, akbasic_Value *rval, akbasic_Value *scratch, akbasic_Value **dest);

#endif // _AKBASIC_VALUE_H_
