/**
 * @file runtime_format.c
 * @brief The group D verbs: PRINT USING, PUDEF, WIDTH and CHAR.
 *
 * Formatting and text placement. The field rendering itself lives in
 * src/format.c, which is a pure function of a format string and a value and is
 * tested as one; what is here is the verbs that reach it.
 */

#include <inttypes.h>
#include <stdio.h>
#include <string.h>

#include <akerror.h>

#include <akbasic/args.h>
#include <akbasic/error.h>
#include <akbasic/runtime.h>

#include "verbs.h"

/* Most verbs answer "did something happen"; this is that answer. */
#define SUCCEED_TRUE(__obj, __dest)			\
    do {						\
	*(__dest) = &(__obj)->staticTrueValue;		\
    } while ( 0 )

/* ------------------------------------------------------------------ PUDEF -- */

akerr_ErrorContext *akbasic_cmd_pudef(akbasic_Runtime *obj, akbasic_ASTLeaf *expr, akbasic_Value *lval, akbasic_Value *rval, akbasic_Value **dest)
{
    PREPARE_ERROR(errctx);
    akbasic_Value *value = NULL;

    (void)lval; (void)rval;
    FAIL_ZERO_RETURN(errctx, (obj != NULL && dest != NULL), AKERR_NULLPOINTER,
		     "NULL argument in PUDEF");
    FAIL_ZERO_RETURN(errctx, (expr != NULL && expr->right != NULL), AKBASIC_ERR_SYNTAX,
		     "Expected PUDEF \"characters\"");
    PASS(errctx, akbasic_runtime_evaluate(obj, expr->right, &value));
    FAIL_NONZERO_RETURN(errctx, (value->valuetype != AKBASIC_TYPE_STRING), AKBASIC_ERR_TYPE,
			"PUDEF expected a string");
    /*
     * No device needed. The fill characters are the program's state, the same
     * way COLOR's source bindings are, and a runtime with no output device is
     * still entitled to remember what it was told.
     */
    PASS(errctx, akbasic_format_pudef(&obj->format_state, value->stringval));
    SUCCEED_TRUE(obj, dest);
    SUCCEED_RETURN(errctx);
}

/* ------------------------------------------------------------------ WIDTH -- */

akerr_ErrorContext *akbasic_cmd_width(akbasic_Runtime *obj, akbasic_ASTLeaf *expr, akbasic_Value *lval, akbasic_Value *rval, akbasic_Value **dest)
{
    PREPARE_ERROR(errctx);
    double args[1];
    int count = 0;

    (void)lval; (void)rval;
    FAIL_ZERO_RETURN(errctx, (obj != NULL && dest != NULL), AKERR_NULLPOINTER,
		     "NULL argument in WIDTH");
    PASS(errctx, akbasic_args_numbers(obj, expr, "WIDTH", args, 1, &count));
    FAIL_ZERO_RETURN(errctx, (count == 1), AKBASIC_ERR_SYNTAX, "Expected WIDTH (1 or 2)");
    /*
     * BASIC 7.0's WIDTH sets the thickness of the lines the *graphics* verbs
     * draw, not a text column count -- a common confusion, and the reason this
     * verb lives with the drawing state rather than with PRINT.
     */
    FAIL_ZERO_RETURN(errctx, (args[0] == 1.0 || args[0] == 2.0), AKBASIC_ERR_BOUNDS,
		     "WIDTH is 1 or 2, not %d", (int)args[0]);
    obj->gfx.linewidth = (int)args[0];
    SUCCEED_TRUE(obj, dest);
    SUCCEED_RETURN(errctx);
}

/* ------------------------------------------------------------------- CHAR -- */

akerr_ErrorContext *akbasic_cmd_char(akbasic_Runtime *obj, akbasic_ASTLeaf *expr, akbasic_Value *lval, akbasic_Value *rval, akbasic_Value **dest)
{
    PREPARE_ERROR(errctx);
    akbasic_ASTLeaf *arg = NULL;
    akbasic_Value *value = NULL;
    char text[AKBASIC_MAX_STRING_LENGTH];
    double coords[3];
    int count = 0;

    (void)lval; (void)rval;
    FAIL_ZERO_RETURN(errctx, (obj != NULL && dest != NULL), AKERR_NULLPOINTER,
		     "NULL argument in CHAR");
    FAIL_ZERO_RETURN(errctx, (obj->sink != NULL), AKBASIC_ERR_DEVICE,
		     "CHAR needs a text device and this runtime has none");
    FAIL_ZERO_RETURN(errctx, (obj->sink->moveto != NULL), AKBASIC_ERR_DEVICE,
		     "CHAR needs a text device that can position its cursor, and this one cannot");

    /*
     * CHAR colour, column, row, "text". The colour argument is accepted and
     * ignored: this interpreter's text sink draws in one colour, chosen by the
     * host when it built the sink, and a per-call colour would have to become
     * part of the sink interface. Recorded in TODO.md section 5.
     */
    arg = akbasic_leaf_first_argument(expr);
    for ( count = 0; count < 3 && arg != NULL; count++, arg = arg->next ) {
	PASS(errctx, akbasic_runtime_evaluate(obj, arg, &value));
	FAIL_NONZERO_RETURN(errctx, (value->valuetype == AKBASIC_TYPE_STRING), AKBASIC_ERR_TYPE,
			    "CHAR expected a number in argument %d", count + 1);
	coords[count] = (value->valuetype == AKBASIC_TYPE_FLOAT)
	    ? value->floatval : (double)value->intval;
    }
    FAIL_ZERO_RETURN(errctx, (count == 3 && arg != NULL), AKBASIC_ERR_SYNTAX,
		     "Expected CHAR (colour), (column), (row), (string)");

    PASS(errctx, akbasic_runtime_evaluate(obj, arg, &value));
    PASS(errctx, akbasic_value_to_string(value, text, sizeof(text)));

    PASS(errctx, obj->sink->moveto(obj->sink, (int)coords[1], (int)coords[2]));
    PASS(errctx, obj->sink->write(obj->sink, text));
    SUCCEED_TRUE(obj, dest);
    SUCCEED_RETURN(errctx);
}
