/*
 * Copyright 2009-present MongoDB, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */


#include <common-bits-private.h>
#include <common-string-private.h>

#include <bson/bson.h>

#include <TestSuite.h>
#include <test-libmongoc.h>

static void
test_bson_strdup_printf(void)
{
   char *s;

   s = bson_strdup_printf("%s:%d", "localhost", 27017);
   BSON_ASSERT(!strcmp(s, "localhost:27017"));
   bson_free(s);
}


static void
test_bson_strdup(void)
{
   char *s;

   s = bson_strdup("localhost:27017");
   BSON_ASSERT(!strcmp(s, "localhost:27017"));
   bson_free(s);
}


static void
test_bson_strndup(void)
{
   char *s;

   s = bson_strndup("asdf", 2);
   BSON_ASSERT(!strcmp(s, "as"));
   bson_free(s);

   s = bson_strndup("asdf", 10);
   BSON_ASSERT(!strcmp(s, "asdf"));
   bson_free(s);

   /* Some tests where we truncate to size n-1, n, n+1 */
   s = bson_strndup("asdf", 3);
   BSON_ASSERT(!strcmp(s, "asd"));
   bson_free(s);

   s = bson_strndup("asdf", 4);
   BSON_ASSERT(!strcmp(s, "asdf"));
   bson_free(s);

   s = bson_strndup("asdf", 5);
   BSON_ASSERT(!strcmp(s, "asdf"));
   bson_free(s);
}


static void
test_bson_strnlen(void)
{
   char *s = "test";

   ASSERT_CMPINT((int)strlen(s), ==, (int)bson_strnlen(s, 100));
}


typedef struct {
   const char *str;
   int base;
   int64_t rv;
   const char *remaining;
   int _errno;
} strtoll_test;


static void
test_bson_ascii_strtoll(void)
{
#ifdef END
#undef END
#endif
#define END ""
   int64_t rv;
   int i;
   char *endptr;
   strtoll_test tests[] = {/* input, base, expected output, # of chars parsed, expected errno */
                           {"1", 10, 1, END, 0},
                           {"+1", 10, 1, END, 0},
                           {"-1", 10, -1, END, 0},
                           {"0", 10, 0, END, 0},
                           {"0 ", 10, 0, " ", 0},
                           {" 0 ", 10, 0, " ", 0},
                           {" 0", 10, 0, END, 0},
                           {" 0\"", 10, 0, "\"", 0},
                           {"0l", 10, 0, "l", 0},
                           {"0l ", 10, 0, "l ", 0},
                           {"0u", 10, 0, "u", 0},
                           {"0u ", 10, 0, "u ", 0},
                           {"0L", 10, 0, "L", 0},
                           {"0L ", 10, 0, "L ", 0},
                           {"0U", 10, 0, "U", 0},
                           {"0U ", 10, 0, "U ", 0},
                           {"-0", 10, 0, END, 0},
                           {"+0", 10, 0, END, 0},
                           {"010", 8, 8, END, 0},
                           /* stroll "takes as many characters as possible to form a valid base-n
                            * integer", so it ignores "8" and returns 0 */
                           {"08", 0, 0, "8", 0},
                           {"010", 10, 10, END, 0},
                           {"010", 8, 8, END, 0},
                           {"010", 0, 8, END, 0},
                           {"68719476736", 10, 68719476736, END, 0},
                           {"-68719476736", 10, -68719476736, END, 0},
                           {"+68719476736", 10, 68719476736, END, 0},
                           {"   68719476736  ", 10, 68719476736, "  ", 0},
                           {"   68719476736  ", 0, 68719476736, "  ", 0},
                           {"   -68719476736  ", 10, -68719476736, "  ", 0},
                           {"   -68719476736  ", 0, -68719476736, "  ", 0},
                           {"   4611686018427387904LL", 10, 4611686018427387904LL, "LL", 0},
                           {" -4611686018427387904LL ", 10, -4611686018427387904LL, "LL ", 0},
                           {"0x1000000000", 16, 68719476736, END, 0},
                           {"0x1000000000", 0, 68719476736, END, 0},
                           {"-0x1000000000", 16, -68719476736, END, 0},
                           {"-0x1000000000", 0, -68719476736, END, 0},
                           {"+0x1000000000", 16, 68719476736, END, 0},
                           {"+0x1000000000", 0, 68719476736, END, 0},
                           {"01234", 8, 668, END, 0},
                           {"01234", 0, 668, END, 0},
                           {"-01234", 8, -668, END, 0},
                           {"-01234", 0, -668, END, 0},
                           {"+01234", 8, 668, END, 0},
                           {"+01234", 0, 668, END, 0},
                           {"9223372036854775807", 10, INT64_MAX, END, 0},
                           {"-9223372036854775808", 10, INT64_MIN, END, 0},
                           {"9223372036854775808", 10, INT64_MAX, "8", ERANGE},   /* LLONG_MAX+1   */
                           {"-9223372036854775809", 10, INT64_MIN, "9", ERANGE},  /* LLONG_MIN-1   */
                           {"18446744073709551615", 10, INT64_MAX, "5", ERANGE},  /* 2*LLONG_MAX+1 */
                           {"-18446744073709551618", 10, INT64_MIN, "8", ERANGE}, /* 2*LLONG_MIN-1 */
                           {NULL}};

   for (i = 0; tests[i].str; i++) {
      errno = 0;

      rv = bson_ascii_strtoll(tests[i].str, &endptr, tests[i].base);
      ASSERT_CMPINT64(rv, ==, tests[i].rv);
      ASSERT_CMPINT(errno, ==, tests[i]._errno);
      ASSERT_CMPSTR(endptr, tests[i].remaining);
   }
#undef END
}


static void
test_bson_strncpy(void)
{
   char buf[5];

   bson_strncpy(buf, "foo", sizeof buf);
   ASSERT_CMPSTR("foo", buf);
   bson_strncpy(buf, "foobar", sizeof buf);
   ASSERT_CMPSTR("foob", buf);
   /* CDRIVER-2596 make sure strncpy with size 0 doesn't write to buf[-1] */
   bson_strncpy(buf + 1, "z", 0);
   ASSERT_CMPSTR("foob", buf);
}


static void
test_bson_snprintf(void)
{
   char buf[] = "ab";

   /* CDRIVER-2595 make sure snprintf with size 0 doesn't write to buf[-1] */
   ASSERT_CMPINT(bson_snprintf(buf + 1, 0, "%d", 1), ==, 0);
   ASSERT_CMPSTR(buf, "ab");
}


static void
test_bson_strcasecmp(void)
{
   BSON_ASSERT(!bson_strcasecmp("FoO", "foo"));
   BSON_ASSERT(bson_strcasecmp("Foa", "foo") < 0);
   BSON_ASSERT(bson_strcasecmp("FoZ", "foo") > 0);
}

void
test_string_install(TestSuite *suite)
{
   TestSuite_Add(suite, "/bson/string/strdup", test_bson_strdup);
   TestSuite_Add(suite, "/bson/string/strdup_printf", test_bson_strdup_printf);
   TestSuite_Add(suite, "/bson/string/strndup", test_bson_strndup);
   TestSuite_Add(suite, "/bson/string/ascii_strtoll", test_bson_ascii_strtoll);
   TestSuite_Add(suite, "/bson/string/strncpy", test_bson_strncpy);
   TestSuite_Add(suite, "/bson/string/snprintf", test_bson_snprintf);
   TestSuite_Add(suite, "/bson/string/strnlen", test_bson_strnlen);
   TestSuite_Add(suite, "/bson/string/strcasecmp", test_bson_strcasecmp);
}
