#include "coerce.h"

/*
 * Be aware that some value variants represents actual values (e.g.
 * vt_int), and others represent a type (e.g. vt_scalar) which holds a
 * type Id token. Here we implicitly expect a vt_scalar type as
 * first argument, but only receive the token. The second argument is a
 * value literal.  Our job is to decide if the value fits within the
 * given type. Our internal representation already ensures that value
 * fits within a 64bit signed or unsigned integer, or double; otherwise
 * the parser would have set vt_invalid type on the value.
 *
 * If the value is invalid, success is returned because the
 * error is presumably already generated. If the value is some other
 * type than expect, an error is generated.
 *
 * Symbolic names are not allowed as values here.
 *
 * Converts positive integers to signed type and unsigned integers to
 * signed type, integers to floats and floats to integers.
 *
 * Optionally allows 1 to be assigned as true and 0 as false, and vice
 * versa when allow_boolean_conversion is enabled.
 *
 * Returns 0 on success, -1 on error.
 */
int fb_coerce_scalar_type(fb_parser_t *P, fb_symbol_t *sym, fb_scalar_type_t st, fb_value_t *value)
{
    double d;
    float f;

    if (!value->type) {
        return 0;
    }
    /*
     * The parser only produces negative vt_int values, which simplifies
     * the logic, but to make this operation robust against multiple
     * coercion steps, we first convert back to uint if the assumption turns
     * out false.
     */
    if (value->type == vt_int && value->i >= 0) {
        value->type = vt_uint;
        value->u = (uint64_t)value->i;
    }
    if (value->type == vt_invalid) {
        /* Silently ignore past errors. */
        return 0;
    }
    if (value->type == vt_bool && st != fb_bool  && P->opts.allow_boolean_conversion) {
        value->type = vt_uint;
        value->u = (uint64_t)value->b;
        assert(value->u == 1 || value->u == 0);
    }
    switch (st) {
    case fb_ulong:
        if (value->type != vt_uint) {
            error_sym(P, sym, "64-bit uint32_t type only accepts unsigned integers");
            value->type = vt_invalid;
            return -1;
        }
        return 0;
    case fb_uint:
        if (value->type != vt_uint) {
            error_sym(P, sym, "32-bit unsigned int type only accepts unsigned integers");
            value->type = vt_invalid;
            return -1;
        }
        if (value->u > UINT32_MAX) {
            error_sym(P, sym, "32-bit unsigned int overflow");
            value->type = vt_invalid;
            return -1;
        }
        return 0;
    case fb_ushort:
        if (value->type != vt_uint) {
            error_sym(P, sym, "16-bit unsigned short type only accepts unsigned integers");
            value->type = vt_invalid;
            return -1;
        }
        if (value->u > UINT16_MAX) {
            error_sym(P, sym, "16-bit unsigned short overflow");
            value->type = vt_invalid;
            return -1;
        }
        return 0;
    case fb_ubyte:
        if (value->type != vt_uint) {
            error_sym(P, sym, "8-bit unsigned byte type only accepts unsigned integers");
            value->type = vt_invalid;
            return -1;
        }
        if (value->u > UINT8_MAX) {
            error_sym(P, sym, "8-bit unsigned byte overflow");
            value->type = vt_invalid;
            return -1;
        }
        return 0;
    case fb_long:
        if (value->type == vt_int) {
            /* Native format is always ok, or parser would have failed. */
            return 0;
        }
        if (value->type == vt_uint) {
            if (value->u >= (1ULL << 63)) {
                error_sym(P, sym, "64-bit signed int32_t overflow");
                value->type = vt_invalid;
                return -1;
            }
            value->i = (int64_t)value->u;
            value->type = vt_int;
            return 0;
        }
        error_sym(P, sym, "64-bit int32_t type only accepts integers");
        value->type = vt_invalid;
        return -1;
    case fb_int:
        if (value->type == vt_int) {
            if (value->i < INT32_MIN) {
                error_sym(P, sym, "32-bit signed int underflow");
                value->type = vt_invalid;
                return -1;
            }
            return 0;
        }
        if (value->type == vt_uint) {
            if (value->i > INT32_MAX) {
                error_sym(P, sym, "32-bit signed int overflow");
                value->type = vt_invalid;
                return -1;
            }
            value->i = (int64_t)value->u;
            value->type = vt_int;
            return 0;
        }
        error_sym(P, sym, "32-bit signed int type only accepts integers");
        value->type = vt_invalid;
        return -1;
    case fb_short:
        if (value->type == vt_int) {
            if (value->i < INT16_MIN) {
                error_sym(P, sym, "16-bit signed short underflow");
                value->type = vt_invalid;
                return -1;
            }
            return 0;
        }
        if (value->type == vt_uint) {
            if (value->i > INT16_MAX) {
                error_sym(P, sym, "16-bit signed short overflow");
                value->type = vt_invalid;
                return -1;
            }
            value->i = (int64_t)value->u;
            value->type = vt_int;
            return 0;
        }
        error_sym(P, sym, "16-bit signed short type only accepts integers");
        value->type = vt_invalid;
        return -1;
    case fb_byte:
        if (value->type == vt_int) {
            if (value->i < INT8_MIN) {
                error_sym(P, sym, "8-bit signed byte underflow");
                value->type = vt_invalid;
                return -1;
            }
            return 0;
        }
        if (value->type == vt_uint) {
            if (value->i > INT8_MAX) {
                error_sym(P, sym, "8-bit signed byte overflow");
                value->type = vt_invalid;
                return -1;
            }
            value->i = (int64_t)value->u;
            value->type = vt_int;
            return 0;
        }
        error_sym(P, sym, "8-bit signed byte type only accepts integers");
        value->type = vt_invalid;
        return -1;
    case fb_bool:
        if (value->type == vt_uint && P->opts.allow_boolean_conversion) {
            if (value->u > 1) {
                error_sym(P, sym, "boolean integer conversion only accepts 0 (false) or 1 (true)");
                value->type = vt_invalid;
                return -1;
            }
        } else if (value->type != vt_bool) {
            error_sym(P, sym, "boolean type only accepts 'true' or 'false' as values");
            value->type = vt_invalid;
            return -1;
        }
        return 0;
    case fb_double:
        switch (value->type) {
        case vt_int:
            d = (double)value->i;
            if ((int64_t)d != value->i) {
                /* We could make this a warning. */
                error_sym(P, sym, "precision loss in 64-bit double type assignment");
                value->type = vt_invalid;
                return -1;
            }
            value->f = d;
            value->type = vt_float;
            return 0;
        case vt_uint:
            d = (double)value->u;
            if ((uint64_t)d != value->u) {
                /* We could make this a warning. */
                error_sym(P, sym, "precision loss in 64-bit double type assignment");
                value->type = vt_invalid;
                return -1;
            }
            value->f = d;
            value->type = vt_float;
            return 0;
        case vt_float:
            /* Double is our internal repr., so not loss at this point. */
            return 0;
        default:
            error_sym(P, sym, "64-bit double type only accepts integer and float values");
            value->type = vt_invalid;
            return -1;
        }
    case fb_float:
        switch (value->type) {
        case vt_int:
            f = (float)value->i;
            if ((int64_t)f != value->i) {
                /* We could make this a warning. */
                error_sym(P, sym, "precision loss in 32-bit float type assignment");
                value->type = vt_invalid;
                return -1;
            }
            value->f = f;
            value->type = vt_float;
            return 0;
        case vt_uint:
            f = (float)value->u;
            if ((uint64_t)f != value->u) {
                /* We could make this a warning. */
                error_sym(P, sym, "precision loss in 32-bit float type assignment");
                value->type = vt_invalid;
                return -1;
            }
            value->f = f;
            value->type = vt_float;
            return 0;
        case vt_float:
            return 0;
        default:
            error_sym(P, sym, "32-bit float type only accepts integer and float values");
            value->type = vt_invalid;
            return -1;
        }
    default:
        error_sym(P, sym, "scalar type expected");
        value->type = vt_invalid;
        return -1;
    }
}

