/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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 <ignite/odbc/system/odbc_constants.h>

#include <boost/test/unit_test.hpp>

#include <ignite/guid.h>
#include <ignite/common/decimal.h>

#include <ignite/odbc/app/application_data_buffer.h>
#include <ignite/odbc/utility.h>

#define FLOAT_PRECISION 0.0000001f

using namespace ignite;
using namespace ignite::odbc;
using namespace ignite::odbc::app;
using namespace ignite::odbc::type_traits;

using ignite::impl::binary::BinaryUtils;

BOOST_AUTO_TEST_SUITE(ApplicationDataBufferTestSuite)

BOOST_AUTO_TEST_CASE(TestPutIntToString)
{
    char buffer[1024];
    SqlLen reslen = 0;

    ApplicationDataBuffer appBuf(OdbcNativeType::AI_CHAR, buffer, sizeof(buffer), &reslen);

    appBuf.PutInt8(12);
    BOOST_CHECK(!strcmp(buffer, "12"));
    BOOST_CHECK(reslen == strlen("12"));

    appBuf.PutInt8(-12);
    BOOST_CHECK(!strcmp(buffer, "-12"));
    BOOST_CHECK(reslen == strlen("-12"));

    appBuf.PutInt16(9876);
    BOOST_CHECK(!strcmp(buffer, "9876"));
    BOOST_CHECK(reslen == strlen("9876"));

    appBuf.PutInt16(-9876);
    BOOST_CHECK(!strcmp(buffer, "-9876"));
    BOOST_CHECK(reslen == strlen("-9876"));

    appBuf.PutInt32(1234567);
    BOOST_CHECK(!strcmp(buffer, "1234567"));
    BOOST_CHECK(reslen == strlen("1234567"));

    appBuf.PutInt32(-1234567);
    BOOST_CHECK(!strcmp(buffer, "-1234567"));
    BOOST_CHECK(reslen == strlen("-1234567"));
}

BOOST_AUTO_TEST_CASE(TestPutFloatToString)
{
    char buffer[1024];
    SqlLen reslen = 0;

    ApplicationDataBuffer appBuf(OdbcNativeType::AI_CHAR, buffer, sizeof(buffer), &reslen);

    appBuf.PutFloat(12.42f);
    BOOST_CHECK(!strcmp(buffer, "12.42"));
    BOOST_CHECK(reslen == strlen("12.42"));

    appBuf.PutFloat(-12.42f);
    BOOST_CHECK(!strcmp(buffer, "-12.42"));
    BOOST_CHECK(reslen == strlen("-12.42"));

    appBuf.PutDouble(1000.21);
    BOOST_CHECK(!strcmp(buffer, "1000.21"));
    BOOST_CHECK(reslen == strlen("1000.21"));

    appBuf.PutDouble(-1000.21);
    BOOST_CHECK(!strcmp(buffer, "-1000.21"));
    BOOST_CHECK(reslen == strlen("-1000.21"));
}

BOOST_AUTO_TEST_CASE(TestPutGuidToString)
{
    char buffer[1024];
    SqlLen reslen = 0;

    ApplicationDataBuffer appBuf(OdbcNativeType::AI_CHAR, buffer, sizeof(buffer), &reslen);

    ignite::Guid guid(0x1da1ef8f39ff4d62ULL, 0x8b72e8e9f3371801ULL);

    appBuf.PutGuid(guid);

    BOOST_CHECK(!strcmp(buffer, "1da1ef8f-39ff-4d62-8b72-e8e9f3371801"));
    BOOST_CHECK(reslen == strlen("1da1ef8f-39ff-4d62-8b72-e8e9f3371801"));
}

BOOST_AUTO_TEST_CASE(TestPutBinaryToString)
{
    char buffer[1024];
    SqlLen reslen = 0;

    ApplicationDataBuffer appBuf(OdbcNativeType::AI_CHAR, buffer, sizeof(buffer), &reslen);

    uint8_t binary[] = { 0x21, 0x84, 0xF4, 0xDC, 0x01, 0x00, 0xFF, 0xF0 };

    int32_t written = 0;

    appBuf.PutBinaryData(binary, sizeof(binary), written);

    BOOST_CHECK(!strcmp(buffer, "2184f4dc0100fff0"));
    BOOST_CHECK(reslen == strlen("2184f4dc0100fff0"));
}

BOOST_AUTO_TEST_CASE(TestPutStringToString)
{
    char buffer[1024];
    SqlLen reslen = 0;

    ApplicationDataBuffer appBuf(OdbcNativeType::AI_CHAR, buffer, sizeof(buffer), &reslen);

    std::string testString("Test string");

    appBuf.PutString(testString);

    BOOST_CHECK(!strcmp(buffer, testString.c_str()));
    BOOST_CHECK(reslen == testString.size());
}

BOOST_AUTO_TEST_CASE(TestPutStringToWstring)
{
    wchar_t buffer[1024];
    SqlLen reslen = 0;

    ApplicationDataBuffer appBuf(OdbcNativeType::AI_WCHAR, buffer, sizeof(buffer), &reslen);

    std::string testString("Test string");

    appBuf.PutString(testString);
    BOOST_CHECK(!wcscmp(buffer, L"Test string"));
}

BOOST_AUTO_TEST_CASE(TestPutStringToLong)
{
    SQLINTEGER numBuf;
    SqlLen reslen = 0;

    ApplicationDataBuffer appBuf(OdbcNativeType::AI_SIGNED_LONG, &numBuf, sizeof(numBuf), &reslen);

    appBuf.PutString("424242424");
    BOOST_CHECK(numBuf == 424242424L);

    appBuf.PutString("-424242424");
    BOOST_CHECK(numBuf == -424242424L);
}

BOOST_AUTO_TEST_CASE(TestPutStringToTiny)
{
    int8_t numBuf;
    SqlLen reslen = 0;

    ApplicationDataBuffer appBuf(OdbcNativeType::AI_SIGNED_TINYINT, &numBuf, sizeof(numBuf), &reslen);

    appBuf.PutString("12");
    BOOST_CHECK(numBuf == 12);

    appBuf.PutString("-12");
    BOOST_CHECK(numBuf == -12);
}

BOOST_AUTO_TEST_CASE(TestPutStringToFloat)
{
    float numBuf;
    SqlLen reslen = 0;

    ApplicationDataBuffer appBuf(OdbcNativeType::AI_FLOAT, &numBuf, sizeof(numBuf), &reslen);

    appBuf.PutString("12.21");
    BOOST_CHECK_CLOSE_FRACTION(numBuf, 12.21, FLOAT_PRECISION);

    appBuf.PutString("-12.21");
    BOOST_CHECK_CLOSE_FRACTION(numBuf, -12.21, FLOAT_PRECISION);
}

BOOST_AUTO_TEST_CASE(TestPutIntToFloat)
{
    float numBuf;
    SqlLen reslen = 0;

    ApplicationDataBuffer appBuf(OdbcNativeType::AI_FLOAT, &numBuf, sizeof(numBuf), &reslen);

    appBuf.PutInt8(5);
    BOOST_CHECK_CLOSE_FRACTION(numBuf, 5.0, FLOAT_PRECISION);

    appBuf.PutInt8(-5);
    BOOST_CHECK_CLOSE_FRACTION(numBuf, -5.0, FLOAT_PRECISION);

    appBuf.PutInt16(4242);
    BOOST_CHECK_CLOSE_FRACTION(numBuf, 4242.0, FLOAT_PRECISION);

    appBuf.PutInt16(-4242);
    BOOST_CHECK_CLOSE_FRACTION(numBuf, -4242.0, FLOAT_PRECISION);

    appBuf.PutInt32(1234567);
    BOOST_CHECK_CLOSE_FRACTION(numBuf, 1234567.0, FLOAT_PRECISION);

    appBuf.PutInt32(-1234567);
    BOOST_CHECK_CLOSE_FRACTION(numBuf, -1234567.0, FLOAT_PRECISION);
}

BOOST_AUTO_TEST_CASE(TestPutFloatToShort)
{
    short numBuf;
    SqlLen reslen = 0;

    ApplicationDataBuffer appBuf(OdbcNativeType::AI_SIGNED_SHORT, &numBuf, sizeof(numBuf), &reslen);

    appBuf.PutDouble(5.42);
    BOOST_CHECK(numBuf == 5);

    appBuf.PutDouble(-5.42);
    BOOST_CHECK(numBuf == -5.0);

    appBuf.PutFloat(42.99f);
    BOOST_CHECK(numBuf == 42);

    appBuf.PutFloat(-42.99f);
    BOOST_CHECK(numBuf == -42);
}

BOOST_AUTO_TEST_CASE(TestPutDecimalToDouble)
{
    double numBuf;
    SqlLen reslen = 0;

    ApplicationDataBuffer appBuf(OdbcNativeType::AI_DOUBLE, &numBuf, sizeof(numBuf), &reslen);

    common::Decimal decimal;

    BOOST_CHECK_CLOSE_FRACTION(static_cast<double>(decimal), 0.0, FLOAT_PRECISION);

    appBuf.PutDecimal(decimal);
    BOOST_CHECK_CLOSE_FRACTION(numBuf, 0.0, FLOAT_PRECISION);

    int8_t mag1[] = { 1, 0 };

    decimal = common::Decimal(mag1, sizeof(mag1), 0, 1);

    appBuf.PutDecimal(decimal);
    BOOST_CHECK_CLOSE_FRACTION(numBuf, 256.0, FLOAT_PRECISION);

    int8_t mag2[] = { 2, 23 };

    decimal = common::Decimal(mag2, sizeof(mag2), 1, -1);

    appBuf.PutDecimal(decimal);
    BOOST_CHECK_CLOSE_FRACTION(numBuf, -53.5, FLOAT_PRECISION);
}

BOOST_AUTO_TEST_CASE(TestPutDecimalToLong)
{
    SQLINTEGER numBuf;
    SqlLen reslen = 0;

    ApplicationDataBuffer appBuf(OdbcNativeType::AI_SIGNED_LONG, &numBuf, sizeof(numBuf), &reslen);

    common::Decimal decimal;

    appBuf.PutDecimal(decimal);
    BOOST_CHECK(numBuf == 0);

    int8_t mag1[] = { 1, 0 };

    decimal = common::Decimal(mag1, sizeof(mag1), 0, 1);

    appBuf.PutDecimal(decimal);
    BOOST_CHECK(numBuf == 256);

    int8_t mag2[] = { 2, 23 };

    decimal = common::Decimal(mag2, sizeof(mag2), 1, -1);

    appBuf.PutDecimal(decimal);
    BOOST_CHECK(numBuf == -53);
}

BOOST_AUTO_TEST_CASE(TestPutDecimalToString)
{
    char strBuf[64];
    SqlLen reslen = 0;

    ApplicationDataBuffer appBuf(OdbcNativeType::AI_CHAR, &strBuf, sizeof(strBuf), &reslen);

    common::Decimal decimal;

    appBuf.PutDecimal(decimal);
    BOOST_CHECK(std::string(strBuf, reslen) == "0");

    int8_t mag1[] = { 1, 0 };

    decimal = common::Decimal(mag1, sizeof(mag1), 0, 1);

    appBuf.PutDecimal(decimal);
    BOOST_CHECK(std::string(strBuf, reslen) == "256");

    int8_t mag2[] = { 2, 23 };

    decimal = common::Decimal(mag2, sizeof(mag2), 1, -1);

    appBuf.PutDecimal(decimal);
    BOOST_CHECK(std::string(strBuf, reslen) == "-53.5");
}

BOOST_AUTO_TEST_CASE(TestPutDecimalToNumeric)
{
    SQL_NUMERIC_STRUCT buf;
    SqlLen reslen = 0;

    ApplicationDataBuffer appBuf(OdbcNativeType::AI_NUMERIC, &buf, sizeof(buf), &reslen);

    common::Decimal decimal;

    appBuf.PutDecimal(decimal);
    BOOST_CHECK_EQUAL(1, buf.sign);         // Positive
    BOOST_CHECK_EQUAL(0, buf.scale);        // Scale is 0 by default according to specification
    BOOST_CHECK_EQUAL(1, buf.precision);    // Precision is 1 for default constructed Decimal (0).

    for (int i = 0; i < SQL_MAX_NUMERIC_LEN; ++i)
        BOOST_CHECK_EQUAL(0, buf.val[i]);

    // Trying to store 123.45 => 12345 => 0x3039 => [0x30, 0x39].
    uint8_t mag1[] = { 0x30, 0x39 };

    decimal = common::Decimal(reinterpret_cast<int8_t*>(mag1), sizeof(mag1), 2, 1);

    appBuf.PutDecimal(decimal);
    BOOST_CHECK_EQUAL(1, buf.sign);         // Positive
    BOOST_CHECK_EQUAL(0, buf.scale);        // Scale is 0 by default according to specification
    BOOST_CHECK_EQUAL(3, buf.precision);    // Precision is 3, as the scale is set to 0.

    // 123.45 => (scale=0) 123 => 0x7B => [0x7B].
    BOOST_CHECK_EQUAL(buf.val[0], 0x7B);

    for (int i = 1; i < SQL_MAX_NUMERIC_LEN; ++i)
        BOOST_CHECK_EQUAL(0, buf.val[i]);

    // Trying to store 12345.678 => 12345678 => 0xBC614E => [0xBC, 0x61, 0x4E].
    uint8_t mag2[] = { 0xBC, 0x61, 0x4E };

    decimal = common::Decimal(reinterpret_cast<int8_t*>(mag2), sizeof(mag2), 3, -1);

    appBuf.PutDecimal(decimal);
    BOOST_CHECK_EQUAL(0, buf.sign);         // Negative
    BOOST_CHECK_EQUAL(0, buf.scale);        // Scale is 0 by default according to specification
    BOOST_CHECK_EQUAL(5, buf.precision);    // Precision is 5, as the scale is set to 0.

    // 12345.678 => (scale=0) 12345 => 0x3039 => [0x39, 0x30].
    BOOST_CHECK_EQUAL(buf.val[0], 0x39);
    BOOST_CHECK_EQUAL(buf.val[1], 0x30);

    for (int i = 2; i < SQL_MAX_NUMERIC_LEN; ++i)
        BOOST_CHECK_EQUAL(0, buf.val[i]);
}

BOOST_AUTO_TEST_CASE(TestPutDateToString)
{
    char strBuf[64] = { 0 };
    SqlLen reslen = 0;

    ApplicationDataBuffer appBuf(OdbcNativeType::AI_CHAR, &strBuf, sizeof(strBuf), &reslen);

    Date date = common::MakeDateGmt(1999, 2, 22);

    appBuf.PutDate(date);

    BOOST_CHECK_EQUAL(std::string(strBuf, reslen), std::string("1999-02-22"));
}

BOOST_AUTO_TEST_CASE(TestPutDateToDate)
{
    SQL_DATE_STRUCT buf = { 0 };
    SqlLen reslen = sizeof(buf);

    ApplicationDataBuffer appBuf(OdbcNativeType::AI_TDATE, &buf, sizeof(buf), &reslen);

    Date date = common::MakeDateGmt(1984, 5, 27);

    appBuf.PutDate(date);

    BOOST_CHECK_EQUAL(1984, buf.year);
    BOOST_CHECK_EQUAL(5, buf.month);
    BOOST_CHECK_EQUAL(27, buf.day);
}

BOOST_AUTO_TEST_CASE(TestPutDateToTimestamp)
{
    SQL_TIMESTAMP_STRUCT buf = { 0 };

    SqlLen reslen = sizeof(buf);

    ApplicationDataBuffer appBuf(OdbcNativeType::AI_TTIMESTAMP, &buf, sizeof(buf), &reslen);

    Date date = common::MakeDateGmt(1984, 5, 27);

    appBuf.PutDate(date);

    BOOST_CHECK_EQUAL(1984, buf.year);
    BOOST_CHECK_EQUAL(5, buf.month);
    BOOST_CHECK_EQUAL(27, buf.day);
    BOOST_CHECK_EQUAL(0, buf.hour);
    BOOST_CHECK_EQUAL(0, buf.minute);
    BOOST_CHECK_EQUAL(0, buf.second);
    BOOST_CHECK_EQUAL(0, buf.fraction);
}

BOOST_AUTO_TEST_CASE(TestPutTimeToString)
{
    char strBuf[64] = { 0 };
    SqlLen reslen = 0;

    ApplicationDataBuffer appBuf(OdbcNativeType::AI_CHAR, &strBuf, sizeof(strBuf), &reslen);

    Time time = common::MakeTimeGmt(7, 15, 0);

    appBuf.PutTime(time);

    BOOST_CHECK_EQUAL(std::string(strBuf, reslen), std::string("07:15:00"));
}

BOOST_AUTO_TEST_CASE(TestPutTimeToTime)
{
    SQL_TIME_STRUCT buf = { 0 };
    SqlLen reslen = sizeof(buf);

    ApplicationDataBuffer appBuf(OdbcNativeType::AI_TTIME, &buf, sizeof(buf), &reslen);

    Time time = common::MakeTimeGmt(23, 51, 1);

    appBuf.PutTime(time);

    BOOST_CHECK_EQUAL(23, buf.hour);
    BOOST_CHECK_EQUAL(51, buf.minute);
    BOOST_CHECK_EQUAL(1, buf.second);
}

BOOST_AUTO_TEST_CASE(TestPutTimestampToString)
{
    char strBuf[64] = { 0 };
    SqlLen reslen = 0;

    ApplicationDataBuffer appBuf(OdbcNativeType::AI_CHAR, &strBuf, sizeof(strBuf), &reslen);

    Timestamp date = common::MakeTimestampGmt(2018, 11, 1, 17, 45, 59);

    appBuf.PutTimestamp(date);

    BOOST_CHECK_EQUAL(std::string(strBuf, reslen), std::string("2018-11-01 17:45:59"));
}

BOOST_AUTO_TEST_CASE(TestPutTimestampToDate)
{
    SQL_DATE_STRUCT buf = { 0 };
    SqlLen reslen = sizeof(buf);

    ApplicationDataBuffer appBuf(OdbcNativeType::AI_TDATE, &buf, sizeof(buf), &reslen);

    Timestamp ts = common::MakeTimestampGmt(2004, 8, 14, 6, 34, 51, 573948623);

    appBuf.PutTimestamp(ts);

    BOOST_CHECK_EQUAL(2004, buf.year);
    BOOST_CHECK_EQUAL(8, buf.month);
    BOOST_CHECK_EQUAL(14, buf.day);
}

BOOST_AUTO_TEST_CASE(TestPutTimestampToTime)
{
    SQL_TIME_STRUCT buf = { 0 };
    SqlLen reslen = sizeof(buf);

    ApplicationDataBuffer appBuf(OdbcNativeType::AI_TTIME, &buf, sizeof(buf), &reslen);

    Timestamp ts = common::MakeTimestampGmt(2004, 8, 14, 6, 34, 51, 573948623);

    appBuf.PutTimestamp(ts);

    BOOST_CHECK_EQUAL(6, buf.hour);
    BOOST_CHECK_EQUAL(34, buf.minute);
    BOOST_CHECK_EQUAL(51, buf.second);
}

BOOST_AUTO_TEST_CASE(TestPutTimestampToTimestamp)
{
    SQL_TIMESTAMP_STRUCT buf = { 0 };
    SqlLen reslen = sizeof(buf);

    ApplicationDataBuffer appBuf(OdbcNativeType::AI_TTIMESTAMP, &buf, sizeof(buf), &reslen);

    Timestamp ts = common::MakeTimestampGmt(2004, 8, 14, 6, 34, 51, 573948623);

    appBuf.PutTimestamp(ts);

    BOOST_CHECK_EQUAL(2004, buf.year);
    BOOST_CHECK_EQUAL(8, buf.month);
    BOOST_CHECK_EQUAL(14, buf.day);
    BOOST_CHECK_EQUAL(6, buf.hour);
    BOOST_CHECK_EQUAL(34, buf.minute);
    BOOST_CHECK_EQUAL(51, buf.second);
    BOOST_CHECK_EQUAL(573948623, buf.fraction);
}

BOOST_AUTO_TEST_CASE(TestGetGuidFromString)
{
    char buffer[] = "1da1ef8f-39ff-4d62-8b72-e8e9f3371801";
    SqlLen reslen = sizeof(buffer) - 1;

    ApplicationDataBuffer appBuf(OdbcNativeType::AI_CHAR, buffer, sizeof(buffer) - 1, &reslen);

    ignite::Guid guid = appBuf.GetGuid();

    BOOST_CHECK_EQUAL(guid, Guid(0x1da1ef8f39ff4d62ULL, 0x8b72e8e9f3371801ULL));
}

BOOST_AUTO_TEST_CASE(TestGetStringFromLong)
{
    long numBuf = 42;
    SqlLen reslen = sizeof(numBuf);

    ApplicationDataBuffer appBuf(OdbcNativeType::AI_SIGNED_LONG, &numBuf, reslen, &reslen);

    std::string res = appBuf.GetString(32);

    BOOST_CHECK(res == "42");

    numBuf = -77;

    res = appBuf.GetString(32);

    BOOST_CHECK(res == "-77");
}

BOOST_AUTO_TEST_CASE(TestGetStringFromDouble)
{
    double numBuf = 43.36;
    SqlLen reslen = sizeof(numBuf);

    ApplicationDataBuffer appBuf(OdbcNativeType::AI_DOUBLE, &numBuf, reslen, &reslen);

    std::string res = appBuf.GetString(32);

    BOOST_CHECK(res == "43.36");

    numBuf = -58.91;

    res = appBuf.GetString(32);

    BOOST_CHECK(res == "-58.91");
}

BOOST_AUTO_TEST_CASE(TestGetStringFromString)
{
    char buf[] = "Some data 32d2d5hs";
    SqlLen reslen = sizeof(buf);

    ApplicationDataBuffer appBuf(OdbcNativeType::AI_CHAR, &buf, reslen, &reslen);

    std::string res = appBuf.GetString(reslen);

    BOOST_CHECK(res.compare(buf));
}

BOOST_AUTO_TEST_CASE(TestGetFloatFromUshort)
{
    unsigned short numBuf = 7162;
    SqlLen reslen = sizeof(numBuf);

    ApplicationDataBuffer appBuf(OdbcNativeType::AI_UNSIGNED_SHORT, &numBuf, reslen, &reslen);

    float resFloat = appBuf.GetFloat();

    BOOST_CHECK_CLOSE_FRACTION(resFloat, 7162.0f, FLOAT_PRECISION);

    double resDouble = appBuf.GetDouble();

    BOOST_CHECK_CLOSE_FRACTION(resDouble, 7162.0, FLOAT_PRECISION);
}

BOOST_AUTO_TEST_CASE(TestGetFloatFromString)
{
    char buf[] = "28.562";
    SqlLen reslen = sizeof(buf);

    ApplicationDataBuffer appBuf(OdbcNativeType::AI_CHAR, &buf, reslen, &reslen);

    float resFloat = appBuf.GetFloat();

    BOOST_CHECK_CLOSE_FRACTION(resFloat, 28.562f, FLOAT_PRECISION);

    double resDouble = appBuf.GetDouble();

    BOOST_CHECK_CLOSE_FRACTION(resDouble, 28.562, FLOAT_PRECISION);
}

BOOST_AUTO_TEST_CASE(TestGetFloatFromFloat)
{
    float buf = 207.49f;
    SqlLen reslen = sizeof(buf);

    ApplicationDataBuffer appBuf(OdbcNativeType::AI_FLOAT, &buf, reslen, &reslen);

    float resFloat = appBuf.GetFloat();

    BOOST_CHECK_CLOSE_FRACTION(resFloat, 207.49f, FLOAT_PRECISION);

    double resDouble = appBuf.GetDouble();

    BOOST_CHECK_CLOSE_FRACTION(resDouble, 207.49, FLOAT_PRECISION);
}

BOOST_AUTO_TEST_CASE(TestGetFloatFromDouble)
{
    double buf = 893.162;
    SqlLen reslen = sizeof(buf);

    ApplicationDataBuffer appBuf(OdbcNativeType::AI_DOUBLE, &buf, reslen, &reslen);

    float resFloat = appBuf.GetFloat();

    BOOST_CHECK_CLOSE_FRACTION(resFloat, 893.162f, FLOAT_PRECISION);

    double resDouble = appBuf.GetDouble();

    BOOST_CHECK_CLOSE_FRACTION(resDouble, 893.162, FLOAT_PRECISION);
}

BOOST_AUTO_TEST_CASE(TestGetIntFromString)
{
    char buf[] = "39";
    SqlLen reslen = sizeof(buf);

    ApplicationDataBuffer appBuf(OdbcNativeType::AI_CHAR, &buf, reslen, &reslen);

    int64_t resInt64 = appBuf.GetInt64();

    BOOST_CHECK(resInt64 == 39);

    int32_t resInt32 = appBuf.GetInt32();

    BOOST_CHECK(resInt32 == 39);

    int16_t resInt16 = appBuf.GetInt16();

    BOOST_CHECK(resInt16 == 39);

    int8_t resInt8 = appBuf.GetInt8();

    BOOST_CHECK(resInt8 == 39);
}

BOOST_AUTO_TEST_CASE(TestGetIntFromFloat)
{
    float buf = -107.49f;
    SqlLen reslen = sizeof(buf);

    ApplicationDataBuffer appBuf(OdbcNativeType::AI_FLOAT, &buf, reslen, &reslen);

    int64_t resInt64 = appBuf.GetInt64();

    BOOST_CHECK(resInt64 == -107);

    int32_t resInt32 = appBuf.GetInt32();

    BOOST_CHECK(resInt32 == -107);

    int16_t resInt16 = appBuf.GetInt16();

    BOOST_CHECK(resInt16 == -107);

    int8_t resInt8 = appBuf.GetInt8();

    BOOST_CHECK(resInt8 == -107);
}

BOOST_AUTO_TEST_CASE(TestGetIntFromDouble)
{
    double buf = 42.97f;
    SqlLen reslen = sizeof(buf);

    ApplicationDataBuffer appBuf(OdbcNativeType::AI_DOUBLE, &buf, reslen, &reslen);

    int64_t resInt64 = appBuf.GetInt64();

    BOOST_CHECK(resInt64 == 42);

    int32_t resInt32 = appBuf.GetInt32();

    BOOST_CHECK(resInt32 == 42);

    int16_t resInt16 = appBuf.GetInt16();

    BOOST_CHECK(resInt16 == 42);

    int8_t resInt8 = appBuf.GetInt8();

    BOOST_CHECK(resInt8 == 42);
}

BOOST_AUTO_TEST_CASE(TestGetIntFromBigint)
{
    uint64_t buf = 19;
    SqlLen reslen = sizeof(buf);

    ApplicationDataBuffer appBuf(OdbcNativeType::AI_UNSIGNED_BIGINT, &buf, reslen, &reslen);

    int64_t resInt64 = appBuf.GetInt64();

    BOOST_CHECK(resInt64 == 19);

    int32_t resInt32 = appBuf.GetInt32();

    BOOST_CHECK(resInt32 == 19);

    int16_t resInt16 = appBuf.GetInt16();

    BOOST_CHECK(resInt16 == 19);

    int8_t resInt8 = appBuf.GetInt8();

    BOOST_CHECK(resInt8 == 19);
}

BOOST_AUTO_TEST_CASE(TestGetIntWithOffset)
{
    struct TestStruct
    {
        uint64_t val;
        SqlLen reslen;
    };

    TestStruct buf[2] = {
        { 12, sizeof(uint64_t) },
        { 42, sizeof(uint64_t) }
    };

    ApplicationDataBuffer appBuf(OdbcNativeType::AI_UNSIGNED_BIGINT, &buf[0].val, sizeof(buf[0].val), &buf[0].reslen);

    int64_t val = appBuf.GetInt64();

    BOOST_CHECK(val == 12);

    appBuf.SetByteOffset(sizeof(TestStruct));

    val = appBuf.GetInt64();

    BOOST_CHECK(val == 42);

    appBuf.SetByteOffset(0);

    val = appBuf.GetInt64();

    BOOST_CHECK(val == 12);
}

BOOST_AUTO_TEST_CASE(TestSetStringWithOffset)
{
    struct TestStruct
    {
        char val[64];
        SqlLen reslen;
    };

    TestStruct buf[2] = {
        { "", 0 },
        { "", 0 }
    };
    
    ApplicationDataBuffer appBuf(OdbcNativeType::AI_CHAR, &buf[0].val, sizeof(buf[0].val), &buf[0].reslen);

    appBuf.PutString("Hello Ignite!");

    std::string res(buf[0].val, buf[0].reslen);

    BOOST_CHECK(buf[0].reslen == strlen("Hello Ignite!"));
    BOOST_CHECK(res == "Hello Ignite!");
    BOOST_CHECK(res.size() == strlen("Hello Ignite!"));

    appBuf.SetByteOffset(sizeof(TestStruct));

    appBuf.PutString("Hello with offset!");

    res.assign(buf[0].val, buf[0].reslen);

    BOOST_CHECK(res == "Hello Ignite!");
    BOOST_CHECK(res.size() == strlen("Hello Ignite!"));
    BOOST_CHECK(buf[0].reslen == strlen("Hello Ignite!"));

    res.assign(buf[1].val, buf[1].reslen);

    BOOST_CHECK(res == "Hello with offset!");
    BOOST_CHECK(res.size() == strlen("Hello with offset!"));
    BOOST_CHECK(buf[1].reslen == strlen("Hello with offset!"));
}

BOOST_AUTO_TEST_CASE(TestGetDateFromString)
{
    char buf[] = "1999-02-22";
    SqlLen reslen = sizeof(buf);
    
    ApplicationDataBuffer appBuf(OdbcNativeType::AI_CHAR, &buf[0], sizeof(buf), &reslen);

    Date date = appBuf.GetDate();

    tm tmDate;

    bool success = common::DateToCTm(date, tmDate);

    BOOST_REQUIRE(success);

    BOOST_CHECK_EQUAL(1999, tmDate.tm_year + 1900);
    BOOST_CHECK_EQUAL(2, tmDate.tm_mon + 1);
    BOOST_CHECK_EQUAL(22, tmDate.tm_mday);
    BOOST_CHECK_EQUAL(0, tmDate.tm_hour);
    BOOST_CHECK_EQUAL(0, tmDate.tm_min);
    BOOST_CHECK_EQUAL(0, tmDate.tm_sec);
}

BOOST_AUTO_TEST_CASE(TestGetTimeFromString)
{
    char buf[] = "17:5:59";
    SqlLen reslen = sizeof(buf);


    ApplicationDataBuffer appBuf(OdbcNativeType::AI_CHAR, &buf[0], sizeof(buf), &reslen);

    Time time = appBuf.GetTime();

    tm tmTime;

    bool success = common::TimeToCTm(time, tmTime);

    BOOST_REQUIRE(success);

    BOOST_CHECK_EQUAL(1970, tmTime.tm_year + 1900);
    BOOST_CHECK_EQUAL(1, tmTime.tm_mon + 1);
    BOOST_CHECK_EQUAL(1, tmTime.tm_mday);
    BOOST_CHECK_EQUAL(17, tmTime.tm_hour);
    BOOST_CHECK_EQUAL(5, tmTime.tm_min);
    BOOST_CHECK_EQUAL(59, tmTime.tm_sec);
}

BOOST_AUTO_TEST_CASE(TestGetTimestampFromString)
{
    char buf[] = "2018-11-01 17:45:59";
    SqlLen reslen = sizeof(buf);

    ApplicationDataBuffer appBuf(OdbcNativeType::AI_CHAR, &buf[0], sizeof(buf), &reslen);

    Timestamp date = appBuf.GetTimestamp();

    tm tmDate;

    bool success = common::TimestampToCTm(date, tmDate);

    BOOST_REQUIRE(success);

    BOOST_CHECK_EQUAL(2018, tmDate.tm_year + 1900);
    BOOST_CHECK_EQUAL(11, tmDate.tm_mon + 1);
    BOOST_CHECK_EQUAL(1, tmDate.tm_mday);
    BOOST_CHECK_EQUAL(17, tmDate.tm_hour);
    BOOST_CHECK_EQUAL(45, tmDate.tm_min);
    BOOST_CHECK_EQUAL(59, tmDate.tm_sec);
}

BOOST_AUTO_TEST_CASE(TestGetDateFromDate)
{
    SQL_DATE_STRUCT buf = { 0 };

    buf.year = 1984;
    buf.month = 5;
    buf.day   = 27;

    SqlLen reslen = sizeof(buf);

    ApplicationDataBuffer appBuf(OdbcNativeType::AI_TDATE, &buf, sizeof(buf), &reslen);

    Date date = appBuf.GetDate();

    tm tmDate;

    bool success = common::DateToCTm(date, tmDate);

    BOOST_REQUIRE(success);

    BOOST_CHECK_EQUAL(1984, tmDate.tm_year + 1900);
    BOOST_CHECK_EQUAL(5, tmDate.tm_mon + 1);
    BOOST_CHECK_EQUAL(27, tmDate.tm_mday);
    BOOST_CHECK_EQUAL(0, tmDate.tm_hour);
    BOOST_CHECK_EQUAL(0, tmDate.tm_min);
    BOOST_CHECK_EQUAL(0, tmDate.tm_sec);
}

BOOST_AUTO_TEST_CASE(TestGetTimestampFromDate)
{
    SQL_DATE_STRUCT buf = { 0 };

    buf.year = 1984;
    buf.month = 5;
    buf.day = 27;

    SqlLen reslen = sizeof(buf);

    ApplicationDataBuffer appBuf(OdbcNativeType::AI_TDATE, &buf, sizeof(buf), &reslen);

    Timestamp ts = appBuf.GetTimestamp();

    tm tmDate;

    bool success = common::TimestampToCTm(ts, tmDate);

    BOOST_REQUIRE(success);

    BOOST_CHECK_EQUAL(1984, tmDate.tm_year + 1900);
    BOOST_CHECK_EQUAL(5, tmDate.tm_mon + 1);
    BOOST_CHECK_EQUAL(27, tmDate.tm_mday);
    BOOST_CHECK_EQUAL(0, tmDate.tm_hour);
    BOOST_CHECK_EQUAL(0, tmDate.tm_min);
    BOOST_CHECK_EQUAL(0, tmDate.tm_sec);
}

BOOST_AUTO_TEST_CASE(TestGetTimestampFromTime)
{
    SQL_TIME_STRUCT buf = { 0 };

    buf.hour = 6;
    buf.minute = 34;
    buf.second = 51;

    SqlLen reslen = sizeof(buf);

    ApplicationDataBuffer appBuf(OdbcNativeType::AI_TTIME, &buf, sizeof(buf), &reslen);

    Time time = appBuf.GetTime();

    tm tmTime;

    bool success = common::TimeToCTm(time, tmTime);

    BOOST_REQUIRE(success);

    BOOST_CHECK_EQUAL(1970, tmTime.tm_year + 1900);
    BOOST_CHECK_EQUAL(1, tmTime.tm_mon + 1);
    BOOST_CHECK_EQUAL(1, tmTime.tm_mday);
    BOOST_CHECK_EQUAL(6, tmTime.tm_hour);
    BOOST_CHECK_EQUAL(34, tmTime.tm_min);
    BOOST_CHECK_EQUAL(51, tmTime.tm_sec);
}

BOOST_AUTO_TEST_CASE(TestGetTimestampFromTimestamp)
{
    SQL_TIMESTAMP_STRUCT buf = { 0 };

    buf.year = 2004;
    buf.month = 8;
    buf.day = 14;
    buf.hour = 6;
    buf.minute = 34;
    buf.second = 51;
    buf.fraction = 573948623;

    SqlLen reslen = sizeof(buf);

    ApplicationDataBuffer appBuf(OdbcNativeType::AI_TTIMESTAMP, &buf, sizeof(buf), &reslen);

    Timestamp ts = appBuf.GetTimestamp();

    tm tmDate;

    bool success = common::TimestampToCTm(ts, tmDate);

    BOOST_REQUIRE(success);

    BOOST_CHECK_EQUAL(2004, tmDate.tm_year + 1900);
    BOOST_CHECK_EQUAL(8, tmDate.tm_mon + 1);
    BOOST_CHECK_EQUAL(14, tmDate.tm_mday);
    BOOST_CHECK_EQUAL(6, tmDate.tm_hour);
    BOOST_CHECK_EQUAL(34, tmDate.tm_min);
    BOOST_CHECK_EQUAL(51, tmDate.tm_sec);
    BOOST_CHECK_EQUAL(573948623, ts.GetSecondFraction());
}

BOOST_AUTO_TEST_CASE(TestGetDateFromTimestamp)
{
    SQL_TIMESTAMP_STRUCT buf = { 0 };

    buf.year = 2004;
    buf.month = 8;
    buf.day = 14;
    buf.hour = 6;
    buf.minute = 34;
    buf.second = 51;
    buf.fraction = 573948623;

    SqlLen reslen = sizeof(buf);

    ApplicationDataBuffer appBuf(OdbcNativeType::AI_TTIMESTAMP, &buf, sizeof(buf), &reslen);

    Date date = appBuf.GetDate();

    tm tmDate;

    bool success = common::DateToCTm(date, tmDate);

    BOOST_REQUIRE(success);

    BOOST_CHECK_EQUAL(2004, tmDate.tm_year + 1900);
    BOOST_CHECK_EQUAL(8, tmDate.tm_mon + 1);
    BOOST_CHECK_EQUAL(14, tmDate.tm_mday);
    BOOST_CHECK_EQUAL(6, tmDate.tm_hour);
    BOOST_CHECK_EQUAL(34, tmDate.tm_min);
    BOOST_CHECK_EQUAL(51, tmDate.tm_sec);
}

BOOST_AUTO_TEST_CASE(TestGetTimeFromTimestamp)
{
    SQL_TIMESTAMP_STRUCT buf = { 0 };

    buf.year = 2004;
    buf.month = 8;
    buf.day = 14;
    buf.hour = 6;
    buf.minute = 34;
    buf.second = 51;
    buf.fraction = 573948623;

    SqlLen reslen = sizeof(buf);

    ApplicationDataBuffer appBuf(OdbcNativeType::AI_TTIMESTAMP, &buf, sizeof(buf), &reslen);

    Time time = appBuf.GetTime();

    tm tmTime;

    bool success = common::TimeToCTm(time, tmTime);

    BOOST_REQUIRE(success);

    BOOST_CHECK_EQUAL(1970, tmTime.tm_year + 1900);
    BOOST_CHECK_EQUAL(1, tmTime.tm_mon + 1);
    BOOST_CHECK_EQUAL(1, tmTime.tm_mday);
    BOOST_CHECK_EQUAL(6, tmTime.tm_hour);
    BOOST_CHECK_EQUAL(34, tmTime.tm_min);
    BOOST_CHECK_EQUAL(51, tmTime.tm_sec);
}

BOOST_AUTO_TEST_SUITE_END()
