/* 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

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);

    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;
}
