/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#include <open62541/types.h>
#include <check.h>
#include "cj5.h"

#include <stdlib.h>
#include <math.h>
#include <float.h>

/* vs2008 does not have INFINITY and NAN defined */
#ifndef INFINITY
# define INFINITY ((double)(DBL_MAX+DBL_MAX))
#endif
#ifndef NAN
# define NAN ((double)(INFINITY-INFINITY))
#endif

#if defined(_MSC_VER)
# pragma warning(disable: 4056)
# pragma warning(disable: 4756)
#endif

START_TEST(parseObject) {
    const char *json = "{'a':1.0, 'b':2, 'c':'abcde'}";
    cj5_token tokens[32];
    cj5_result r = cj5_parse(json, (unsigned int)strlen(json), tokens, 32, NULL);
    ck_assert(r.error == CJ5_ERROR_NONE);
    double d = 0.0;
    cj5_error_code err = cj5_get_float(&r, 2, &d);
    ck_assert(err == CJ5_ERROR_NONE);
    ck_assert(d == 1.0);

    ck_assert_uint_eq(tokens[6].size, 5);
} END_TEST

START_TEST(parseUTF8) {
    const char *json = "{'a':\"Lindestra\\u00dfe\"}";
    cj5_token tokens[32];
    cj5_result r = cj5_parse(json, (unsigned int)strlen(json), tokens, 32, NULL);
    ck_assert(r.error == CJ5_ERROR_NONE);
    char buf[32];
    cj5_error_code err = cj5_get_str(&r, 2, buf, NULL);
    ck_assert(err == CJ5_ERROR_NONE);

    ck_assert_uint_eq(tokens[2].size, 16);
} END_TEST

START_TEST(parseNestedObject) {
    const char *json = "{'a':{}, 'b':{'c':3}, 'd':true, 'e':false, 'f':null, 'g':[]}";
    cj5_token tokens[32];
    cj5_result r = cj5_parse(json, (unsigned int)strlen(json), tokens, 32, NULL);
    ck_assert(r.error == CJ5_ERROR_NONE);

    /* 12 elements directly under the root object */
    ck_assert_uint_eq(tokens[0].size, 12);

    /* Test the cj5_find method for value lookup in objects */
    unsigned int idx = 0;
    cj5_error_code err = cj5_find(&r, &idx, "b");
    ck_assert_int_eq(err, CJ5_ERROR_NONE);
    ck_assert_uint_eq(idx, 4);

    err = cj5_find(&r, &idx, "c");
    ck_assert(err == CJ5_ERROR_NONE);
    ck_assert(idx == 6);

    int64_t val = 0;
    cj5_get_int(&r, idx, &val);
    ck_assert(val == 3);

    idx = 4;
    err = cj5_find(&r, &idx, "d");
    ck_assert(err == CJ5_ERROR_NOTFOUND);

    idx = 0;
    err = cj5_find(&r, &idx, "d");
    ck_assert_int_eq(err, CJ5_ERROR_NONE);

    bool bval = 0;
    cj5_get_bool(&r, idx, &bval);
    ck_assert(bval == true);

    idx = 0;
    err = cj5_find(&r, &idx, "e");
    ck_assert_int_eq(err, CJ5_ERROR_NONE);

    cj5_get_bool(&r, idx, &bval);
    ck_assert(bval == false);

    idx = 0;
    err = cj5_find(&r, &idx, "f");
    ck_assert_int_eq(err, CJ5_ERROR_NONE);
    ck_assert(tokens[idx].type == CJ5_TOKEN_NULL);
} END_TEST

START_TEST(parseObjectUnquoted) {
    const char *json = "{'a':1, b:true}";
    cj5_token tokens[32];
    cj5_result r = cj5_parse(json, (unsigned int)strlen(json), tokens, 32, NULL);
    ck_assert(r.error == CJ5_ERROR_NONE);
} END_TEST

START_TEST(parseObjectWrongBracket) {
    const char *json = "{'a':1, 'b':2]";
    cj5_token tokens[32];
    cj5_result r = cj5_parse(json, (unsigned int)strlen(json), tokens, 32, NULL);
    ck_assert(r.error == CJ5_ERROR_INVALID);
} END_TEST

START_TEST(parseObjectWrongBracket2) {
    const char *json = "{{'a':1, 'b':2]}";
    cj5_token tokens[32];
    cj5_result r = cj5_parse(json, (unsigned int)strlen(json), tokens, 32, NULL);
    ck_assert(r.error == CJ5_ERROR_INVALID);
} END_TEST

START_TEST(parseObjectIncomplete) {
    const char *json = "{'a':1, 'b':2";
    cj5_token tokens[32];
    cj5_result r = cj5_parse(json, (unsigned int)strlen(json), tokens, 32, NULL);
    ck_assert(r.error == CJ5_ERROR_INCOMPLETE);
} END_TEST

START_TEST(parseObjectNoRoot) {
    const char *json = "'a':1, 'b':2";
    cj5_token tokens[32];
    cj5_result r = cj5_parse(json, (unsigned int)strlen(json), tokens, 32, NULL);
    ck_assert(r.error == CJ5_ERROR_NONE);
} END_TEST

START_TEST(parseObjectStopEarly) {
    cj5_options opt;
    opt.stop_early = true;
    const char *json = "{'a':1}, x";
    cj5_token tokens[32];
    cj5_result r = cj5_parse(json, (unsigned int)strlen(json), tokens, 32, &opt);
    ck_assert(r.error == CJ5_ERROR_NONE);
    ck_assert_uint_eq(r.tokens[0].end, 6);
} END_TEST

START_TEST(parseObjectNoRootUnquoted) {
    const char *json = "a:1, 'b':2";
    cj5_token tokens[32];
    cj5_result r = cj5_parse(json, (unsigned int)strlen(json), tokens, 32, NULL);
    ck_assert(r.error == CJ5_ERROR_NONE);
} END_TEST

START_TEST(parseObjectCloseNoRoot) {
    const char *json = "'a':1, 'b':2}";
    cj5_token tokens[32];
    cj5_result r = cj5_parse(json, (unsigned int)strlen(json), tokens, 32, NULL);
    ck_assert(r.error == CJ5_ERROR_INVALID);
} END_TEST

START_TEST(parseArray) {
    const char *json = "[1.23456,2,3,null]";
    cj5_token tokens[32];
    cj5_result r = cj5_parse(json, (unsigned int)strlen(json), tokens, 32, NULL);
    ck_assert(r.error == CJ5_ERROR_NONE);

    double val = 0;
    cj5_get_float(&r, 1, &val);
    ck_assert(fabs(val - 1.23456) < 0.00001);
} END_TEST

START_TEST(parseArrayStopEarly) {
    cj5_options opt;
    opt.stop_early = true;
    const char *json = "[1] }";
    cj5_token tokens[32];
    cj5_result r = cj5_parse(json, (unsigned int)strlen(json), tokens, 32, &opt);
    ck_assert(r.error == CJ5_ERROR_NONE);
    ck_assert_uint_eq(r.tokens[0].end, 2);
} END_TEST

START_TEST(parseValue) {
    const char *json = "null";
    cj5_token tokens[32];
    cj5_result r = cj5_parse(json, (unsigned int)strlen(json), tokens, 32, NULL);
    ck_assert(r.error == CJ5_ERROR_NONE);
} END_TEST

START_TEST(parseValueStopEarly) {
    cj5_options opt;
    opt.stop_early = true;
    const char *json = "1.0{";
    cj5_token tokens[32];
    cj5_result r = cj5_parse(json, (unsigned int)strlen(json), tokens, 32, &opt);
    ck_assert(r.error == CJ5_ERROR_NONE);
    ck_assert_uint_eq(r.tokens[0].end, 2);
} END_TEST

START_TEST(parseInf) {
    const char *json = "Infinity";
    cj5_token tokens[32];
    cj5_result r = cj5_parse(json, (unsigned int)strlen(json), tokens, 32, NULL);
    ck_assert(r.error == CJ5_ERROR_NONE);

    double val = 0;
    cj5_get_float(&r, 0, &val);
    ck_assert_msg(val == INFINITY, "val: %f", val);
} END_TEST

START_TEST(parseNegInf) {
    const char *json = "-Infinity";
    cj5_token tokens[32];
    cj5_result r = cj5_parse(json, (unsigned int)strlen(json), tokens, 32, NULL);
    ck_assert(r.error == CJ5_ERROR_NONE);

    double val = 0;
    cj5_get_float(&r, 0, &val);
    ck_assert_msg(val == -INFINITY, "val: %f", val);
} END_TEST

/* Additional coverage tests */

START_TEST(parseTokenOverflow) {
    /* More tokens needed than buffer allows */
    const char *json = "{'a':1, 'b':2, 'c':3}";
    cj5_token tokens[2]; /* Too small */
    cj5_result r = cj5_parse(json, (unsigned int)strlen(json), tokens, 2, NULL);
    ck_assert(r.error == CJ5_ERROR_OVERFLOW);
    ck_assert_uint_gt(r.num_tokens, 2);
} END_TEST

START_TEST(parseEmptyObject) {
    const char *json = "{}";
    cj5_token tokens[32];
    cj5_result r = cj5_parse(json, (unsigned int)strlen(json), tokens, 32, NULL);
    ck_assert(r.error == CJ5_ERROR_NONE);
    ck_assert(tokens[0].type == CJ5_TOKEN_OBJECT);
    ck_assert_uint_eq(tokens[0].size, 0);
} END_TEST

START_TEST(parseEmptyArray) {
    const char *json = "[]";
    cj5_token tokens[32];
    cj5_result r = cj5_parse(json, (unsigned int)strlen(json), tokens, 32, NULL);
    ck_assert(r.error == CJ5_ERROR_NONE);
    ck_assert(tokens[0].type == CJ5_TOKEN_ARRAY);
    ck_assert_uint_eq(tokens[0].size, 0);
} END_TEST

START_TEST(parseNegativeNumber) {
    const char *json = "-42";
    cj5_token tokens[32];
    cj5_result r = cj5_parse(json, (unsigned int)strlen(json), tokens, 32, NULL);
    ck_assert(r.error == CJ5_ERROR_NONE);
    int64_t val = 0;
    cj5_get_int(&r, 0, &val);
    ck_assert_int_eq(val, -42);
} END_TEST

START_TEST(parseExponentNumber) {
    const char *json = "1.5e10";
    cj5_token tokens[32];
    cj5_result r = cj5_parse(json, (unsigned int)strlen(json), tokens, 32, NULL);
    ck_assert(r.error == CJ5_ERROR_NONE);
    double val = 0;
    cj5_get_float(&r, 0, &val);
    ck_assert(fabs(val - 1.5e10) < 1.0);
} END_TEST

START_TEST(parseHexNumber) {
    const char *json = "0xFF";
    cj5_token tokens[32];
    cj5_result r = cj5_parse(json, (unsigned int)strlen(json), tokens, 32, NULL);
    ck_assert(r.error == CJ5_ERROR_NONE);
    uint64_t val = 0;
    cj5_get_uint(&r, 0, &val);
    ck_assert_uint_eq(val, 255);
} END_TEST

START_TEST(parseStringEscapes) {
    /* Test various escape sequences */
    const char *json = "'hello\\nworld\\t!'";
    cj5_token tokens[32];
    cj5_result r = cj5_parse(json, (unsigned int)strlen(json), tokens, 32, NULL);
    ck_assert(r.error == CJ5_ERROR_NONE);
    char buf[64];
    cj5_get_str(&r, 0, buf, NULL);
    ck_assert(strchr(buf, '\n') != NULL);
    ck_assert(strchr(buf, '\t') != NULL);
} END_TEST

START_TEST(parseGetBoolWrongType) {
    const char *json = "42";
    cj5_token tokens[32];
    cj5_result r = cj5_parse(json, (unsigned int)strlen(json), tokens, 32, NULL);
    ck_assert(r.error == CJ5_ERROR_NONE);
    bool val = false;
    cj5_error_code err = cj5_get_bool(&r, 0, &val);
    ck_assert(err != CJ5_ERROR_NONE);
} END_TEST

START_TEST(parseGetFloatWrongType) {
    const char *json = "true";
    cj5_token tokens[32];
    cj5_result r = cj5_parse(json, (unsigned int)strlen(json), tokens, 32, NULL);
    ck_assert(r.error == CJ5_ERROR_NONE);
    double val = 0;
    cj5_error_code err = cj5_get_float(&r, 0, &val);
    ck_assert(err != CJ5_ERROR_NONE);
} END_TEST

START_TEST(parseFindOnNonObject) {
    const char *json = "[1, 2, 3]";
    cj5_token tokens[32];
    cj5_result r = cj5_parse(json, (unsigned int)strlen(json), tokens, 32, NULL);
    ck_assert(r.error == CJ5_ERROR_NONE);
    unsigned int idx = 0;
    cj5_error_code err = cj5_find(&r, &idx, "a");
    ck_assert(err != CJ5_ERROR_NONE);
} END_TEST

START_TEST(parseTrailingComma) {
    /* JSON5 allows trailing comma */
    const char *json = "{'a':1, 'b':2,}";
    cj5_token tokens[32];
    cj5_result r = cj5_parse(json, (unsigned int)strlen(json), tokens, 32, NULL);
    ck_assert(r.error == CJ5_ERROR_NONE);
} END_TEST

START_TEST(parseSingleLineComment) {
    const char *json = "{'a':1 // comment\n, 'b':2}";
    cj5_token tokens[32];
    cj5_result r = cj5_parse(json, (unsigned int)strlen(json), tokens, 32, NULL);
    ck_assert(r.error == CJ5_ERROR_NONE);
} END_TEST

START_TEST(parseMultiLineComment) {
    const char *json = "{'a':1 /* comment */, 'b':2}";
    cj5_token tokens[32];
    cj5_result r = cj5_parse(json, (unsigned int)strlen(json), tokens, 32, NULL);
    ck_assert(r.error == CJ5_ERROR_NONE);
} END_TEST

START_TEST(parseHashComment) {
    const char *json = "{'a':1 # comment\n, 'b':2}";
    cj5_token tokens[32];
    cj5_result r = cj5_parse(json, (unsigned int)strlen(json), tokens, 32, NULL);
    ck_assert(r.error == CJ5_ERROR_NONE);
} END_TEST

START_TEST(parseNanValue) {
    const char *json = "NaN";
    cj5_token tokens[32];
    cj5_result r = cj5_parse(json, (unsigned int)strlen(json), tokens, 32, NULL);
    ck_assert(r.error == CJ5_ERROR_NONE);
    double val = 0;
    cj5_get_float(&r, 0, &val);
    ck_assert(val != val); /* NaN != NaN */
} END_TEST

START_TEST(parseZeroInteger) {
    const char *json = "0";
    cj5_token tokens[32];
    cj5_result r = cj5_parse(json, (unsigned int)strlen(json), tokens, 32, NULL);
    ck_assert(r.error == CJ5_ERROR_NONE);
    int64_t val = 42;
    cj5_get_int(&r, 0, &val);
    ck_assert_int_eq(val, 0);
} END_TEST

START_TEST(parseSkipObject) {
    const char *json = "{'a':{'b':1}, 'c':2}";
    cj5_token tokens[32];
    cj5_result r = cj5_parse(json, (unsigned int)strlen(json), tokens, 32, NULL);
    ck_assert(r.error == CJ5_ERROR_NONE);

    /* Skip from the nested object ('a' value) */
    unsigned int idx = 2; /* index of the inner object */
    cj5_skip(&r, &idx);
    /* Should now point to 'c' key */
    ck_assert_uint_gt(idx, 2);
} END_TEST

START_TEST(parseDoubleQuotedString) {
    const char *json = "{\"key\":\"value\"}";
    cj5_token tokens[32];
    cj5_result r = cj5_parse(json, (unsigned int)strlen(json), tokens, 32, NULL);
    ck_assert(r.error == CJ5_ERROR_NONE);
} END_TEST

START_TEST(parseBoolCaseInsensitive) {
    /* cj5 is case-sensitive per JSON spec, so uppercase TRUE is not a bool */
    const char *json = "TRUE";
    cj5_token tokens[32];
    cj5_result r = cj5_parse(json, (unsigned int)strlen(json), tokens, 32, NULL);
    /* This may parse as an error or non-bool token */
    if(r.error == CJ5_ERROR_NONE) {
        bool val = false;
        cj5_error_code err = cj5_get_bool(&r, 0, &val);
        /* If parsed as something other than bool, get_bool returns error */
        (void)err;
    }
} END_TEST

START_TEST(parseNullCaseInsensitive) {
    /* JSON spec requires lowercase null */
    const char *json = "NULL";
    cj5_token tokens[32];
    cj5_result r = cj5_parse(json, (unsigned int)strlen(json), tokens, 32, NULL);
    /* Uppercase NULL may fail to parse or parse as non-null token */
    (void)r;
} END_TEST

START_TEST(parseNestedArray) {
    const char *json = "[[1,2],[3,4]]";
    cj5_token tokens[32];
    cj5_result r = cj5_parse(json, (unsigned int)strlen(json), tokens, 32, NULL);
    ck_assert(r.error == CJ5_ERROR_NONE);
    ck_assert(tokens[0].type == CJ5_TOKEN_ARRAY);
} END_TEST

START_TEST(parseGetUnsigned) {
    const char *json = "42";
    cj5_token tokens[32];
    cj5_result r = cj5_parse(json, (unsigned int)strlen(json), tokens, 32, NULL);
    ck_assert(r.error == CJ5_ERROR_NONE);
    uint64_t val = 0;
    cj5_error_code err = cj5_get_uint(&r, 0, &val);
    ck_assert(err == CJ5_ERROR_NONE);
    ck_assert_uint_eq(val, 42);
} END_TEST

static Suite *testSuite_builtin_json(void) {
    TCase *tc_parse= tcase_create("cj5_parse");
    tcase_add_test(tc_parse, parseObject);
    tcase_add_test(tc_parse, parseUTF8);
    tcase_add_test(tc_parse, parseNestedObject);
    tcase_add_test(tc_parse, parseObjectUnquoted);
    tcase_add_test(tc_parse, parseObjectWrongBracket);
    tcase_add_test(tc_parse, parseObjectWrongBracket2);
    tcase_add_test(tc_parse, parseObjectIncomplete);
    tcase_add_test(tc_parse, parseObjectNoRoot);
    tcase_add_test(tc_parse, parseObjectNoRootUnquoted);
    tcase_add_test(tc_parse, parseObjectCloseNoRoot);
    tcase_add_test(tc_parse, parseObjectStopEarly);
    tcase_add_test(tc_parse, parseArray);
    tcase_add_test(tc_parse, parseArrayStopEarly);
    tcase_add_test(tc_parse, parseValue);
    tcase_add_test(tc_parse, parseValueStopEarly);
    tcase_add_test(tc_parse, parseInf);
    tcase_add_test(tc_parse, parseNegInf);
    tcase_add_test(tc_parse, parseTokenOverflow);
    tcase_add_test(tc_parse, parseEmptyObject);
    tcase_add_test(tc_parse, parseEmptyArray);
    tcase_add_test(tc_parse, parseNegativeNumber);
    tcase_add_test(tc_parse, parseExponentNumber);
    tcase_add_test(tc_parse, parseHexNumber);
    tcase_add_test(tc_parse, parseStringEscapes);
    tcase_add_test(tc_parse, parseGetBoolWrongType);
    tcase_add_test(tc_parse, parseGetFloatWrongType);
    tcase_add_test(tc_parse, parseFindOnNonObject);
    tcase_add_test(tc_parse, parseTrailingComma);
    tcase_add_test(tc_parse, parseSingleLineComment);
    tcase_add_test(tc_parse, parseMultiLineComment);
    tcase_add_test(tc_parse, parseHashComment);
    tcase_add_test(tc_parse, parseNanValue);
    tcase_add_test(tc_parse, parseZeroInteger);
    tcase_add_test(tc_parse, parseSkipObject);
    tcase_add_test(tc_parse, parseDoubleQuotedString);
    tcase_add_test(tc_parse, parseBoolCaseInsensitive);
    tcase_add_test(tc_parse, parseNullCaseInsensitive);
    tcase_add_test(tc_parse, parseNestedArray);
    tcase_add_test(tc_parse, parseGetUnsigned);

    Suite *s = suite_create("Test JSON decoding with the cj5 library");
    suite_add_tcase(s, tc_parse);
    return s;
}

int main(void) {
    int number_failed = 0;
    Suite *s = testSuite_builtin_json();
    SRunner *sr = srunner_create(s);
    srunner_set_fork_status(sr, CK_NOFORK);
    srunner_run_all(sr, CK_NORMAL);
    number_failed += srunner_ntests_failed(sr);
    srunner_free(sr);
    return (number_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE;
}
