using PhpefRuntimeCore;

static double parse_microtime_string(string value) throws Error {
    string[] parts = value.split(" ");
    if (parts.length != 2) {
        throw new FileError.FAILED("microtime string must have two fields: %s".printf(value));
    }

    double fractional = double.parse(parts[0]);
    double seconds = double.parse(parts[1]);
    if (fractional < 0.0 || fractional >= 1.0 || seconds <= 0.0) {
        throw new FileError.FAILED("microtime string fields out of range: %s".printf(value));
    }
    return seconds + fractional;
}

static bool near(double left, double right, double tolerance_seconds = 5.0) {
    double delta = left - right;
    if (delta < 0.0) {
        delta = -delta;
    }
    return delta <= tolerance_seconds;
}

static int compare_float_case(string label, Zval? arg) {
    Zval native_result = arg == null
        ? PhpefRuntimeNativeStandard.phpfunc_microtime()
        : PhpefRuntimeNativeStandard.phpfunc_microtime(arg);
    Zval embed_result = arg == null
        ? PhpefRuntimeEmbed.phpfunc_microtime()
        : PhpefRuntimeEmbed.phpfunc_microtime(arg);

    if (native_result.kind != ValueKind.FLOAT) {
        stderr.printf("%s native result is not FLOAT: %s\n", label, native_result.to_php_string());
        return 1;
    }
    if (embed_result.kind != ValueKind.FLOAT) {
        stderr.printf("%s embed result is not FLOAT: %s\n", label, embed_result.to_php_string());
        return 1;
    }
    if (native_result.as_float() <= 0.0 || embed_result.as_float() <= 0.0 || !near(native_result.as_float(), embed_result.as_float())) {
        stderr.printf("%s float mismatch: native=%f embed=%f\n", label, native_result.as_float(), embed_result.as_float());
        return 1;
    }
    return 0;
}

static int compare_string_case(string label, Zval? arg) {
    Zval native_result = arg == null
        ? PhpefRuntimeNativeStandard.phpfunc_microtime()
        : PhpefRuntimeNativeStandard.phpfunc_microtime(arg);
    Zval embed_result = arg == null
        ? PhpefRuntimeEmbed.phpfunc_microtime()
        : PhpefRuntimeEmbed.phpfunc_microtime(arg);

    if (native_result.kind != ValueKind.STRING) {
        stderr.printf("%s native result is not STRING: %s\n", label, native_result.to_php_string());
        return 1;
    }
    if (embed_result.kind != ValueKind.STRING) {
        stderr.printf("%s embed result is not STRING: %s\n", label, embed_result.to_php_string());
        return 1;
    }

    try {
        double native_time = parse_microtime_string(native_result.as_string() ?? "");
        double embed_time = parse_microtime_string(embed_result.as_string() ?? "");
        if (!near(native_time, embed_time)) {
            stderr.printf("%s string time mismatch: native=%s embed=%s\n", label, native_result.as_string(), embed_result.as_string());
            return 1;
        }
    } catch (Error e) {
        stderr.printf("%s parse failed: %s\n", label, e.message);
        return 1;
    }

    return 0;
}

public static int main(string[] args) {
    PhpefRuntimeEmbed.engine_start(args);

    int rc = 0;
    rc |= compare_string_case("microtime()", null);
    rc |= compare_string_case("microtime(false)", Zval.from_bool(false));
    rc |= compare_string_case("microtime(null)", Zval.from_null());
    rc |= compare_string_case("microtime(0)", Zval.from_int(0));
    rc |= compare_string_case("microtime('0')", Zval.from_string("0"));

    rc |= compare_float_case("microtime(true)", Zval.from_bool(true));
    rc |= compare_float_case("microtime(1)", Zval.from_int(1));
    rc |= compare_float_case("microtime('1')", Zval.from_string("1"));

    try {
        string typed_default = PhpefRuntimeNativeStandard.phpfunc_microtime_typed_string();
        Zval embed_default = PhpefRuntimeEmbed.phpfunc_microtime();
        if (embed_default.kind != ValueKind.STRING || !near(parse_microtime_string(typed_default), parse_microtime_string(embed_default.as_string() ?? ""))) {
            stderr.printf("microtime() typed string mismatch: native=%s embed=%s\n", typed_default, embed_default.to_php_string());
            rc = 1;
        }

        string typed_false = PhpefRuntimeNativeStandard.phpfunc_microtime_typed_string_arg1_bool_false();
        Zval embed_false = PhpefRuntimeEmbed.phpfunc_microtime(Zval.from_bool(false));
        if (embed_false.kind != ValueKind.STRING || !near(parse_microtime_string(typed_false), parse_microtime_string(embed_false.as_string() ?? ""))) {
            stderr.printf("microtime(false) typed string mismatch: native=%s embed=%s\n", typed_false, embed_false.to_php_string());
            rc = 1;
        }
    } catch (Error e) {
        stderr.printf("typed string microtime parse failed: %s\n", e.message);
        rc = 1;
    }

    double typed_true = PhpefRuntimeNativeStandard.phpfunc_microtime_typed_double_arg1_bool_true();
    Zval embed_true = PhpefRuntimeEmbed.phpfunc_microtime(Zval.from_bool(true));
    if (embed_true.kind != ValueKind.FLOAT || typed_true <= 0.0 || !near(typed_true, embed_true.as_float())) {
        stderr.printf("microtime(true) typed double mismatch: native=%f embed=%s\n", typed_true, embed_true.to_php_string());
        rc = 1;
    }

    PhpefRuntimeEmbed.engine_stop();
    return rc;
}
