#include <test.h>

#include <cmockery.h>
#include <conversion.h>

static void test_string_is_boolean(void)
{
    // This test should be updated if someone changes CF_BOOL:
    assert_string_equal(CF_BOOL, "true,false,yes,no,on,off");

    // Accepted boolean values:
    assert_true(StringIsBoolean("true"));
    assert_true(StringIsBoolean("false"));
    assert_true(StringIsBoolean("yes"));
    assert_true(StringIsBoolean("no"));
    assert_true(StringIsBoolean("on"));
    assert_true(StringIsBoolean("off"));

    // Anything else is not boolean:
    assert_false(StringIsBoolean("boolean"));
    assert_false(StringIsBoolean("bool"));
    assert_false(StringIsBoolean(""));
    assert_false(StringIsBoolean(" "));
    assert_false(StringIsBoolean(","));
    assert_false(StringIsBoolean(",,"));
    assert_false(StringIsBoolean("()"));
    assert_false(StringIsBoolean("$(foo)"));
    assert_false(StringIsBoolean("$(true)"));
    assert_false(StringIsBoolean("y"));
    assert_false(StringIsBoolean("n"));
    assert_false(StringIsBoolean("tru"));
    assert_false(StringIsBoolean("fals"));
    assert_false(StringIsBoolean("true,"));
    assert_false(StringIsBoolean(",false"));
    assert_false(StringIsBoolean("o,n"));
    assert_false(StringIsBoolean(" on "));
    assert_false(StringIsBoolean("onoff"));
    assert_false(StringIsBoolean("onon"));
}

static void test_boolean_from_string(void)
{
    // This test should be updated if someone changes CF_BOOL:
    assert_string_equal(CF_BOOL, "true,false,yes,no,on,off");

    // Expected true values:
    assert_true(BooleanFromString("true"));
    assert_true(BooleanFromString("yes"));
    assert_true(BooleanFromString("on"));

    // Expected false values:
    assert_false(BooleanFromString("false"));
    assert_false(BooleanFromString("no"));
    assert_false(BooleanFromString("off"));

    // Edge cases, default to true:
    assert_true(BooleanFromString("boolean"));
    assert_true(BooleanFromString("bool"));
    assert_true(BooleanFromString(""));
    assert_true(BooleanFromString(" "));
    assert_true(BooleanFromString(","));
    assert_true(BooleanFromString(",,"));
    assert_true(BooleanFromString("()"));
    assert_true(BooleanFromString("$(foo)"));
    assert_true(BooleanFromString("$(true)"));
    assert_true(BooleanFromString("y"));
    assert_true(BooleanFromString("n"));
    assert_true(BooleanFromString("tru"));
    assert_true(BooleanFromString("fals"));
    assert_true(BooleanFromString("true,"));
    assert_true(BooleanFromString(",false"));
    assert_true(BooleanFromString("o,n"));
    assert_true(BooleanFromString(" on "));
    assert_true(BooleanFromString("onoff"));
    assert_true(BooleanFromString("onon"));
}

static void test_int_from_string(void)
{
    assert_int_equal(IntFromString("0"), 0);
    assert_int_equal(IntFromString("1"), 1);
    assert_int_equal(IntFromString(" 1"), 1);
    assert_int_equal(IntFromString("-1"), (long) -1);
    assert_int_equal(IntFromString("-1k"), (long) -1000);
    assert_int_equal(IntFromString("-123"), (long) -123);
    assert_int_equal(IntFromString("12  "), 12);
    assert_int_equal(IntFromString("12k  "), 12000);
    assert_int_equal(IntFromString("\t1m  "), 1000000);

    /* ==== SPECIAL CASES ==== */
    assert_int_equal(IntFromString("inf"), CF_INFINITY);
    assert_int_equal(IntFromString("now"), CFSTARTTIME);
    assert_int_equal(IntFromString("2k"), 2000);
    assert_int_equal(IntFromString("3K"), 3072);
    assert_int_equal(IntFromString("4m"), 4000000);
    assert_int_equal(IntFromString("1M"), 1024 * 1024);
    /* Percentages are stored as negatives TODO fix. */
    assert_int_equal(IntFromString("10%"), (long) -10);
    /* Unknown quantifiers are just being ignored. */
    assert_int_equal(IntFromString("13o"), 13);

    /* ==== CONTROLLED FAILURES ==== */
    assert_int_equal(IntFromString(NULL), CF_NOINT);
    assert_int_equal(IntFromString(""), CF_NOINT);
    assert_int_equal(IntFromString("  "), CF_NOINT);
    assert_int_equal(IntFromString("$(blah)"), CF_NOINT);
    assert_int_equal(IntFromString("123.45"), CF_NOINT);
    assert_int_equal(IntFromString("120%"), CF_NOINT);
    assert_int_equal(IntFromString("-1%"), CF_NOINT);
    assert_int_equal(IntFromString("14 o"), CF_NOINT);
    assert_int_equal(IntFromString("2ko"), CF_NOINT);
    assert_int_equal(IntFromString("3K o"), CF_NOINT);
    /* The quantifier is not expanded yet. */
    assert_int_equal(IntFromString("99$(blah)"), CF_NOINT);
}

static void test_double_from_string(void)
{
    double val;

    /* ===== TESTING SUCCESS ===== */

    assert_true(DoubleFromString("1.2k", &val));
    assert_double_close(1200.0, val);

    assert_true(DoubleFromString("1m", &val));
    assert_double_close(1000000.0, val);

    assert_true(DoubleFromString("1K", &val));
    assert_double_close(1024.0, val);

    /* Previously reserved as NO_DOUBLE define. */
    assert_true(DoubleFromString("-123.45", &val));
    assert_double_close(-123.45, val);

    assert_true(DoubleFromString("0.1", &val));
    assert_double_close(0.1, val);

    /* leading space is OK. */
    assert_true(DoubleFromString(" 0.2", &val));
    assert_double_close(0.2, val);
    assert_true(DoubleFromString(" 0.2k", &val));
    assert_double_close(200., val);

    assert_true(DoubleFromString("0.1%", &val));
    /* Currently percentages are stored as negatives; TODO FIX! */
    assert_double_close(-0.1, val);

    /* Space quantifier ignored. */
    assert_true(DoubleFromString("1233 ", &val));
    assert_double_close(1233, val);

    assert_true(DoubleFromString("1112    ", &val));
    assert_double_close(1112, val);

    /* Invalid quantifier, ignored for backwards compatibility. */
    assert_true(DoubleFromString("11.1o", &val));
    assert_double_close(11.1, val);

    /* ===== ERROR RETURN ===== */

    /* Verify that parameter is not modified. */
    double old_val = val;

    assert_false(DoubleFromString("", &val));
    assert_false(DoubleFromString(" ", &val));
    assert_false(DoubleFromString("  ", &val));
    assert_false(DoubleFromString("abc", &val));
    assert_false(DoubleFromString("G1", &val));

    /* Anomalous remainders. */
    assert_false(DoubleFromString("123adf", &val));
    assert_false(DoubleFromString("123 adf", &val));
    assert_false(DoubleFromString("123   adf", &val));
    assert_false(DoubleFromString("123   adf", &val));

    assert_false(DoubleFromString("123$(remainder)", &val));


    assert_true(val == old_val);
}

static void test_integer_range_from_string(void)
{
    long min, max;

    assert_true(IntegerRangeFromString("0,0", &min, &max));
    assert_int_equal(min, 0L);
    assert_int_equal(max, 0L);

    assert_true(IntegerRangeFromString("-20,19", &min, &max));
    assert_int_equal(min, -20L);
    assert_int_equal(max, 19L);

    assert_true(IntegerRangeFromString("1,inf", &min, &max));
    assert_int_equal(min, 1L);
    assert_int_equal(max, CF_INFINITY);

    /* An absent attribute is reported as CF_NOINT. */
    assert_true(IntegerRangeFromString(NULL, &min, &max));
    assert_int_equal(min, CF_NOINT);
    assert_int_equal(max, CF_NOINT);
}

/**
 * TimeRangeFromString() exists because time_t is wider than long on some
 * platforms (notably Windows), where writing a bound through a long * fills
 * only its low half and leaves the rest untouched. These cases check that
 * bounds reach a time_t intact, at full width and with the correct sign.
 */
static void test_time_range_from_string(void)
{
    /* Pre-fill with a pattern that is wrong in both halves, so that a bound
     * written only 4 bytes wide fails these assertions. */
    const time_t poison = (time_t) 0x5A5A5A5A5A5A5A5A;
    time_t min = poison, max = poison;

    /* A bound that fits in 32 bits. */
    assert_true(TimeRangeFromString("0,283824000", &min, &max));
    assert_int_equal(min, 0);
    assert_int_equal(max, 283824000);

    /* A bound beyond INT32_MAX, which cannot survive a round trip through a
     * 32-bit long. Policy reaches these values easily: this one is what
     * irange(0, accumulated(99,0,0,0,0,0)) expands to, i.e. 99 years. */
    min = poison;
    max = poison;
    assert_true(TimeRangeFromString("0,3122064000", &min, &max));
    assert_int_equal(min, 0);
    assert_int_equal(max, (time_t) 3122064000);

    min = poison;
    max = poison;
    assert_true(TimeRangeFromString("-3122064000,-1", &min, &max));
    assert_int_equal(min, (time_t) -3122064000);
    assert_int_equal(max, (time_t) -1);

    min = poison;
    max = poison;
    assert_true(TimeRangeFromString("0,inf", &min, &max));
    assert_int_equal(min, 0);
    assert_int_equal(max, CF_INFINITY);

    /* An absent attribute is reported as CF_NOINT */
    min = poison;
    max = poison;
    assert_true(TimeRangeFromString(NULL, &min, &max));
    assert_int_equal(min, CF_NOINT);
    assert_int_equal(max, CF_NOINT);
}

static void test_CommandArg0_bound(void)
{
    char dst[128];
    size_t zret;

    zret = CommandArg0_bound(dst, "", sizeof(dst));
    assert_string_equal(dst, "");
    assert_int_equal(zret, 0);
    zret = CommandArg0_bound(dst, " ", sizeof(dst));
    assert_string_equal(dst, "");
    assert_int_equal(zret, 0);
    zret = CommandArg0_bound(dst, " blah", sizeof(dst));
    assert_string_equal(dst, "");
    assert_int_equal(zret, 0);
    zret = CommandArg0_bound(dst, "blah", sizeof(dst));
    assert_string_equal(dst, "blah");
    assert_int_equal(zret, 4);
    zret = CommandArg0_bound(dst, "blah blue", sizeof(dst));
    assert_string_equal(dst, "blah");
    assert_int_equal(zret, 4);

    zret = CommandArg0_bound(dst, "\"\"", sizeof(dst));
    assert_string_equal(dst, "");
    assert_int_equal(zret, 0);
    zret = CommandArg0_bound(dst, "\"blah\"", sizeof(dst));
    assert_string_equal(dst, "blah");
    assert_int_equal(zret, 4);
    zret = CommandArg0_bound(dst, "\"blah", sizeof(dst));
    assert_string_equal(dst, "blah");
    assert_int_equal(zret, 4);
    zret = CommandArg0_bound(dst, "\"blah blue", sizeof(dst));
    assert_string_equal(dst, "blah blue");
    assert_int_equal(zret, 9);

    zret = CommandArg0_bound(dst, "\"\" blus", sizeof(dst));
    assert_string_equal(dst, "");
    assert_int_equal(zret, 0);
    zret = CommandArg0_bound(dst, "\"blah\" blue", sizeof(dst));
    assert_string_equal(dst, "blah");
    assert_int_equal(zret, 4);
    zret = CommandArg0_bound(dst, "\"blah\"blue", sizeof(dst));
    assert_string_equal(dst, "blah");
    assert_int_equal(zret, 4);

    zret = CommandArg0_bound(dst, "blah ", sizeof(dst));
    assert_string_equal(dst, "blah");
    assert_int_equal(zret, 4);

    zret = CommandArg0_bound(dst, "\" \"", sizeof(dst));
    assert_string_equal(dst, " ");
    assert_int_equal(zret, 1);
    zret = CommandArg0_bound(dst, "\" \" ", sizeof(dst));
    assert_string_equal(dst, " ");
    assert_int_equal(zret, 1);

    zret = CommandArg0_bound(dst, "blah \"blue\"", sizeof(dst));
    assert_string_equal(dst, "blah");
    assert_int_equal(zret, 4);

    zret = CommandArg0_bound(dst, "blah\"blue\"", sizeof(dst));
    assert_string_equal(dst, "blah\"blue\"");
    assert_int_equal(zret, 10);

    zret = CommandArg0_bound(dst, "blah\"blue", sizeof(dst));
    assert_string_equal(dst, "blah\"blue");
    assert_int_equal(zret, 9);

    /* TEST OVERFLOW */

    zret = CommandArg0_bound(dst, "", 0);
    assert_int_equal(zret, (size_t) -1);
    zret = CommandArg0_bound(dst, "blah", 0);
    assert_int_equal(zret, (size_t) -1);
    zret = CommandArg0_bound(dst, " ", 0);
    assert_int_equal(zret, (size_t) -1);
    zret = CommandArg0_bound(dst, "\"blah\"", 0);
    assert_int_equal(zret, (size_t) -1);

    zret = CommandArg0_bound(dst, "blah", 1);
    assert_int_equal(zret, (size_t) -1);
    zret = CommandArg0_bound(dst, "\"blah\"", 1);
    assert_int_equal(zret, (size_t) -1);

    zret = CommandArg0_bound(dst, "b", 1);
    assert_int_equal(zret, (size_t) -1);
    zret = CommandArg0_bound(dst, "\"b\"", 1);
    assert_int_equal(zret, (size_t) -1);

    zret = CommandArg0_bound(dst, "", 1);
    assert_int_equal(zret, 0);                         /* empty string fits */
    zret = CommandArg0_bound(dst, " ", 1);
    assert_int_equal(zret, 0);                         /* empty string fits */
}

int main()
{
    PRINT_TEST_BANNER();
    const UnitTest tests[] =
    {
        unit_test(test_string_is_boolean),
        unit_test(test_boolean_from_string),
        unit_test(test_int_from_string),
        unit_test(test_double_from_string),
        unit_test(test_integer_range_from_string),
        unit_test(test_time_range_from_string),
        unit_test(test_CommandArg0_bound),
    };

    return run_tests(tests);
}
