/*
 Copyright (c) 2010, 2015, Oracle and/or its affiliates. All rights reserved.

 This program is free software; you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
 the Free Software Foundation; version 2 of the License.

 This program is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU General Public License for more details.

 You should have received a copy of the GNU General Public License
 along with this program; if not, write to the Free Software
 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
*/

#ifdef TEST_MYSQL_UTILS_TEST

#include "my_global.h"
#include <string.h> // not using namespaces yet
#include <stdio.h> // not using namespaces yet
#include <stdlib.h> // not using namespaces yet

#include <util/NdbTap.hpp>
#include <util/BaseString.hpp>

#include "dbug_utils.hpp"
#include "decimal_utils.hpp"
#include "CharsetMap.hpp"

#include "my_sys.h"
#include "mysql.h"

// need two levels of macro substitution
#define STRINGIFY(x) #x
#define TOSTRING(x) STRINGIFY(x)

// return non-zero value on failed condition
// C99's __func__ not supported by some C++ compilers yet (Solaris)
#define CHECK(cond)                                                     \
    do {                                                                \
        if (!(cond)) {                                                  \
            fflush(stdout);                                             \
            fprintf(stderr, "\n!!! failed check: " TOSTRING(cond)       \
                    ", file: " __FILE__                                 \
                    ", line: " TOSTRING(__LINE__)                       \
                    ".\n");                                             \
            fflush(stderr);                                             \
            return 1;                                                   \
        }                                                               \
    } while (0)

int test_dbug_utils()
{
    printf("\n==== DBUG Utilities ====\n");
    const int DBUG_BUF_SIZE = 1024;
    char buffer[DBUG_BUF_SIZE];

    const char * s = "some initial string";
    const char * const s0 = "";
    s = dbugExplain(buffer, DBUG_BUF_SIZE);
    CHECK(!s || !strcmp(s, s0));

    s = dbugExplain(NULL, DBUG_BUF_SIZE);
    CHECK(!s);

    s = dbugExplain(buffer, 0);
    CHECK(!s);

    const char * const s1 = "t";
    dbugSet(s1);
    s = dbugExplain(buffer, DBUG_BUF_SIZE);
    CHECK(!s || !strcmp(s, s1));

    dbugSet(NULL);
    s = dbugExplain(buffer, DBUG_BUF_SIZE);
    CHECK(!s || !strcmp(s, s1));

    /* Build dbug string honoring setting of TMPDIR
       using the format "d,somename:o,<TMPDIR>/somepath"
     */
    BaseString tmp("d,somename:o,");
    const char* tmpd = getenv("TMPDIR");
    if(tmpd)
      tmp.append(tmpd);
    else
      tmp.append("/tmp");
    tmp.append("/somepath");
    const char * const s2 = tmp.c_str();
    dbugPush(s2);
    s = dbugExplain(buffer, DBUG_BUF_SIZE);
    CHECK(!s || !strcmp(s, s2));

    dbugPush(NULL);
    s = dbugExplain(buffer, DBUG_BUF_SIZE);
    CHECK(!s || !strcmp(s, s2));

    const char * const s3 = "d,a,b,c,x,y,z";
    dbugPush(s3);
    s = dbugExplain(buffer, DBUG_BUF_SIZE);
    CHECK(!s || (strspn(s, s3) == strlen(s3))); // allow for different order

    dbugPop();
    s = dbugExplain(buffer, DBUG_BUF_SIZE);
    CHECK(!s || !strcmp(s, s2));

    dbugPop();
    s = dbugExplain(buffer, DBUG_BUF_SIZE);
    CHECK(!s || !strcmp(s, s1));

    dbugPush(NULL);
    s = dbugExplain(buffer, DBUG_BUF_SIZE);
    CHECK(!s || !strcmp(s, s1));

    dbugPop();
    s = dbugExplain(buffer, DBUG_BUF_SIZE);
    CHECK(!s || !strcmp(s, s0));

    return 0;
}

int test_decimal(const char *s, int prec, int scale, int expected_rv)
{
    char bin_buff[128], str_buff[128];
    int r1, r2 = 0;

    str_buff[0] = 0;

    // cast: decimal_str2bin expects 'int' for size_t strlen()
    r1 = decimal_str2bin(s, (int)strlen(s), prec, scale, bin_buff, 128);
    if(r1 <= E_DEC_OVERFLOW) {
        r2 = decimal_bin2str(bin_buff, 128, prec, scale, str_buff, 128);
        CHECK(r2 == E_DEC_OK);
    }
    printf("[%-2d,%-2d] %-29s => res=%d,%d     %s\n",
           prec, scale, s, r1, r2, str_buff);

    if(r1 != expected_rv)
        printf("decimal_str2bin returned %d when %d was expected.\n",
               r1, expected_rv);
    CHECK(r1 == expected_rv);

    return 0;
}

int test_decimal_conv()
{
    printf("\n==== decimal_str2bin() / decimal_bin2str() ====\n");
    CHECK(test_decimal("100", 3, -1, E_DEC_BAD_SCALE) == 0);
    CHECK(test_decimal("3.3", 2, 1, E_DEC_OK) == 0);
    CHECK(test_decimal("124.000", 20, 4, E_DEC_OK) == 0);
    CHECK(test_decimal("-11", 14, 1, E_DEC_OK) == 0);
    CHECK(test_decimal("1.123456000000000", 20, 16, E_DEC_OK) == 0);
    CHECK(test_decimal("-20.333", 4, 2, E_DEC_TRUNCATED) == 0);
    CHECK(test_decimal("0", 20, 10, E_DEC_OK) == 0);
    CHECK(test_decimal("1 ", 20, 10, E_DEC_OK) == 0);
    CHECK(test_decimal("1,35", 20, 10, E_DEC_OK) == 0);
    CHECK(test_decimal("text", 20, 10, E_DEC_BAD_NUM) == 0);

    return 0;
}

int test_charset_map()
{
    printf("\n==== CharsetMap ====\n");
    printf("init MySQL lib, CharsetMap...\n");
    my_init();
    CharsetMap::init();

    /* CharsetMap */
    CharsetMap csmap;
    int utf8_num = csmap.getUTF8CharsetNumber();
    int utf16_num = csmap.getUTF16CharsetNumber();

    /* If this mysql build does not include UTF-8 and either UCS-2 or UTF-16
       then the test suite must fail.
    */
    printf("UTF-8 charset num: %d     UTF-16 or UCS-2 charset num:  %d\n",
           utf8_num, utf16_num);
    CHECK(utf8_num != 0);
    CHECK(utf16_num != 0);

    /* test csmap.getName()
     */
    const char *utf8 = csmap.getName(utf8_num);
    CHECK(!strcmp(utf8,"UTF-8"));

    /* MySQL 5.1 and earlier will have UCS-2 but later versions may have true
       UTF-16.  For information, print whether UTF-16 or UCS-2 is being used.
    */
    const char *utf16 = csmap.getMysqlName(csmap.getUTF16CharsetNumber());
    printf("Using mysql's %s for UTF-16.\n", utf16);

    /* Now we're going to recode.
       We test with the string "ülker", which begins with the character
       LATIN SMALL LETTER U WITH DIARESIS - unicode code point U+00FC.
       In the latin1 encoding this is a literal 0xFC,
       but in the UTF-8 representation it is 0xC3 0xBC.
    */
    // use numeric escape sequencesx.. (or downcast integer literal to char)
    // to avoid narrowing conversion compile warnings
    const char my_word_latin1[6]    = { '\xFC', 'l', 'k', 'e', 'r', 0};
    const char my_word_utf8[7]      = { '\xC3', '\xBC', 'l', 'k', 'e', 'r', 0};
    const char my_word_truncated[5] = { '\xC3', '\xBC', 'l', 'k', 0};
    // no need for 'unsigned char[]'
    const char my_bad_utf8[5]       = { 'l', '\xBC', 'a', 'd', 0};
    char result_buff_1[32];
    char result_buff_2[32];
    char result_buff_too_small[4];
    int lengths[2];

    /* latin1 must be available to run the recode test */
    int latin1_num = csmap.getCharsetNumber("latin1");
    printf("latin1 charset number: %d  standard name: \"%s\" \n",
           latin1_num, csmap.getName(latin1_num));
    CHECK(latin1_num != 0);
    CHECK(! strcmp(csmap.getName(latin1_num), "windows-1252"));

    printf("Latin1: \"%s\"                       UTF8:  \"%s\" \n",
           my_word_latin1, my_word_utf8);

    /* RECODE TEST 1: recode from UTF-8 to Latin 1 */
    lengths[0] = 7;
    lengths[1] = 32;
    CharsetMap::RecodeStatus rr1 = csmap.recode(lengths, utf8_num, latin1_num,
                                                my_word_utf8, result_buff_1);
    printf("Recode Test 1 - UTF-8 to Latin-1: %d %d %d \"%s\" => \"%s\" \n",
           rr1, lengths[0], lengths[1], my_word_utf8, result_buff_1);
    CHECK(rr1 == CharsetMap::RECODE_OK);
    CHECK(lengths[0] == 7);
    CHECK(lengths[1] == 6);
    CHECK(!strcmp(result_buff_1, my_word_latin1));

    /* RECODE TEST 2: recode from Latin1 to to UTF-8 */
    lengths[0] = 6;
    lengths[1] = 32;
    CharsetMap::RecodeStatus rr2 = csmap.recode(lengths, latin1_num, utf8_num,
                                                my_word_latin1, result_buff_2);
    printf("Recode Test 2 - Latin-1 to UTF-8: %d %d %d \"%s\" => \"%s\" \n",
           rr2, lengths[0], lengths[1], my_word_latin1, result_buff_2);
    CHECK(rr2 == CharsetMap::RECODE_OK);
    CHECK(lengths[0] == 6);
    CHECK(lengths[1] == 7);
    CHECK(!(strcmp(result_buff_2, my_word_utf8)));

    /* RECODE TEST 3: recode with a too-small result buffer */
    lengths[0] = 6;
    lengths[1] = 4;
    CharsetMap::RecodeStatus rr3 = csmap.recode(lengths, latin1_num, utf8_num,
                                                my_word_latin1, result_buff_too_small);
    printf("Recode Test 3 - too-small buffer: %d %d %d \"%s\" => \"%s\" \n",
           rr3, lengths[0], lengths[1], my_word_latin1, result_buff_too_small);
    CHECK(rr3 == CharsetMap::RECODE_BUFF_TOO_SMALL);
    CHECK(lengths[0] == 3);
    CHECK(lengths[1] == 4);
    /* Confirm that the first four characters were indeed recoded: */
    CHECK(!(strncmp(result_buff_too_small, my_word_truncated, 4)));

    /* RECODE TEST 4: recode with an invalid character set */
    CharsetMap::RecodeStatus rr4 = csmap.recode(lengths, 0, 999, my_word_latin1, result_buff_2);
    printf("Recode Test 4 - invalid charset: %d \n", rr4);
    CHECK(rr4 == CharsetMap::RECODE_BAD_CHARSET);

    /* RECODE TEST 5: source string is ill-formed UTF-8 */
    lengths[0] = 5;
    lengths[1] = 32;
    int rr5 = csmap.recode(lengths, utf8_num, latin1_num,
                           my_bad_utf8, result_buff_2);
    printf("Recode Test 5 - ill-formed source string: %d \n", rr5);
    CHECK(rr5 == CharsetMap::RECODE_BAD_SRC);


    printf("isMultibyte TEST: ");
    const bool * result1, * result2, * result3;
    result1 = csmap.isMultibyte(latin1_num);
    result2 = csmap.isMultibyte(utf16_num);
    result3 = csmap.isMultibyte(utf8_num);
    printf("latin 1: %s      UTF16: %s       UTF8: %s\n",
           *result1 ? "Yes" : "No",
           *result2 ? "Yes" : "No",
           *result3 ? "Yes" : "No");
    CHECK(! *result1);
    CHECK(*result2);
    CHECK(*result3);

    int nNull = 0, nSingle = 0, nMulti = 0;
    for(int i = 0 ; i < 256 ; i++) {
      const bool *r = csmap.isMultibyte(i);
      if(r) {
        if(*r) nMulti++;
        else nSingle++;
      }
      else nNull++;
    }
    printf("Charset stats:  %d unused, %d single-byte, %d multi-byte\n",
           nNull, nSingle, nMulti);
    // If there is not at least one of each, then something is probably wrong
    CHECK(nNull && nSingle && nMulti);

    printf("unload CharsetMap...\n");
    CharsetMap::unload();

    return 0;
}

int main(int argc, const char** argv)
{
    // TAP: print number of tests to run
    plan(3);

    // init MySQL lib
    if (my_init())
        BAIL_OUT("my_init() failed");

    // TAP: report test result: ok(passed, non-null format string)
    ok(test_dbug_utils() == 0, "subtest: dbug_utils");
    ok(test_decimal_conv() == 0, "subtest: decimal_conv");
    ok(test_charset_map() == 0, "subtest: charset_map");

    // TAP: print summary report and return exit status
    return exit_status();
}

#endif
