#define BOOST_TEST_DYN_LINK
#define BOOST_TEST_MODULE DataTable

#include <boost/test/unit_test.hpp>
#include "datatable/field/DateFieldFormat.h"
#include "datatable/encoding/ClassicEncodingSettings.h"
#include "datatable/TableFormat.h"
#include "datatable/DataRecord.h"
#include "datatable/DataTableReplication.h"
#include "datatable/DataTableQuery.h"
#include "datatable/validator/LimitsValidator.h"
#include "data/Data.h"
#include "util/CloneUtils.h"
#include "util/simpleobject/AgString.h"
#include "util/simpleobject/AgFloat.h"
#include "util/simpleobject/AgDouble.h"
#include "util/simpleobject/AgLong.h"
#include "util/simpleobject/AgInteger.h"
#include "util/simpleobject/AgBoolean.h"
#include "IllegalArgumentException.h"
#include <ctime>

BOOST_AUTO_TEST_SUITE (DateTable)

BOOST_AUTO_TEST_CASE(testAgStringGetBytes)
{
    std::vector<unsigned char> data;
    for (int i = 0; i < 255; i++)
    {
        data.push_back(i);
    }
    AgString sss = AgString::fromBytes(data, ASCII_CHARSET);
    std::vector<unsigned char> data2 = sss.getBytes(ASCII_CHARSET);
    BOOST_CHECK(data == data2);
}



BOOST_AUTO_TEST_CASE(testDateFieldFormat)
{
    DateFieldFormat *ff = new DateFieldFormat("name");

    BOOST_CHECK_THROW(ff->valueFromString("invalid date string", ClassicEncodingSettingsPtr(), false), IllegalArgumentException);
    BOOST_CHECK_NO_THROW(ff->valueFromString("2002-Jan-01 14:23:11", ClassicEncodingSettingsPtr(), false));
    time_t t;
    time(&t);
    int64_t l = t * 1000;
    BOOST_CHECK_NO_THROW(ff->valueFromString(AgString::fromInt64(l), ClassicEncodingSettingsPtr(), false));

    delete ff;
}


BOOST_AUTO_TEST_CASE(testValueToAndFromString)
{
    FieldFormatPtr ff = FieldFormatPtr(FieldFormat::create("test", 'A'));
    DataPtr data = DataPtr(new Data());

    DataPtr nd =  boost::static_pointer_cast<Data>(ff->valueFromString(ff->valueToString(data)));
    BOOST_CHECK(nd->getId() == 0);
    BOOST_CHECK(nd->getName().empty());
    BOOST_CHECK(nd->getPreview().size() == 0);
    BOOST_CHECK(nd->getData().size() == 0);

    data = DataPtr(new Data());

    std::string s1 = "/preview//";
    std::string s2 = "/data";

    std::vector<unsigned char> preview(s1.begin(), s1.end());
    std::vector<unsigned char> originalData(s2.begin(), s2.end());
    for (int i = 0; i < 250; i++)
    {
        originalData.push_back(i);
    }

    data->setId(123);
    data->setName("name");
    data->setPreview(preview);
    data->setData(originalData);

    ClassicEncodingSettingsPtr classicEncoding = ClassicEncodingSettingsPtr(new ClassicEncodingSettings(false));

    nd = boost::static_pointer_cast<Data>(ff->valueFromEncodedString(ff->valueToEncodedString(data, classicEncoding), classicEncoding, true));

    BOOST_CHECK(nd->getId() == 123);
    BOOST_CHECK(nd->getName() == AgString("name"));
    BOOST_CHECK(nd->getPreview() == preview);
    BOOST_CHECK(nd->getData() == originalData);
}

TableFormatPtr createFormat(AgString format)
{
    return TableFormatPtr(new TableFormat(format, ClassicEncodingSettingsPtr(new ClassicEncodingSettings(true))));
}

BOOST_AUTO_TEST_CASE(testTableFormat)
{
    TableFormatPtr rf1 = createFormat("<<s1><S>> <<s2><S><i1><I>> <<i2><I>>");
    TableFormatPtr rf2 = createFormat("<<s1><S>> <<s2><S><i1><I>> <<i2><I>>");
    BOOST_CHECK(rf1->extend(rf2) == true);

    rf1 = createFormat("<<s1><S>> <<l1><L>> <<s2><S>> <<i1><I>> <<i2><I>>");
    rf2 = createFormat("<<s1><S>> <<s2><S>> <<i1><I>> <<i2><I>> <<b1><B><F=O>>");
    BOOST_CHECK(rf1->extend(rf2) == true);

    rf1 = createFormat("<<s1><S>> <<l1><L>> <<s2><S>> <<i1><I>> <<i2><I>>");
    rf2 = createFormat("<<s1><S>> <<s2><S>> <<i1><I><F=N>> <<i2><I>> <<b1><B><F=O>>");
    BOOST_CHECK(rf1->extend(rf2) == false);

    rf1 = createFormat("<<s1><S>> <<l1><L>> <<s2><S><i1><I>> <<i2><I>>");
    rf2 = createFormat("<<s1><S>> <<s2><S>> <<i2><I>> <<b1><B><F=O>> <<b2><B>>");
    BOOST_CHECK(rf1->extend(rf2) == false);

    rf1 = createFormat("<<one><S>>");
    rf2 = createFormat("<<one><I>>");
    BOOST_CHECK(rf1->extend(rf2) == false);

    rf1 = createFormat("<<0><S><A=test><D=test>>");

    BOOST_CHECK(rf1->getField(0)->isNullable() == false);

    AgString strTest("test");
    BOOST_CHECK(rf1->getField(0)->getDefaultValue()->equals(&strTest));
}

BOOST_AUTO_TEST_CASE(testFieldFormat2)
{
    {
        FieldFormatPtr ff1 = FieldFormatPtr(FieldFormat::create("<s1><S><F=N><D=Test>"));
        FieldFormatPtr ff2 = FieldFormatPtr(FieldFormat::create("<s1><S><D=Test>"));
        AgString sss = ff1->extendMessage(ff2.get());
        BOOST_CHECK(ff1->extend(ff2.get()));
    }

    {
        FieldFormatPtr ff1 = FieldFormatPtr(FieldFormat::create("<s1><F><A=123.456>"));
        float val = ff1->getDefaultValue()->toFloat();
        BOOST_CHECK(fabs(123.456f - val ) < 0.0000000000001f);
    }
    {
        AgString format = "<s1><S><F=N><A=default><D=Test><S=<desc=default><desc2=val2>><V=<L=1 10>>";
        FieldFormatPtr ff = FieldFormatPtr(FieldFormat::create(format));
        FieldFormatPtr cl = FieldFormatPtr(ff->clone());
        BOOST_CHECK(format == cl->encode(ClassicEncodingSettingsPtr(new ClassicEncodingSettings(true))));
    }
    {
        FieldFormatPtr ff = FieldFormatPtr(FieldFormat::create("<theBigValue><S>"));
        BOOST_CHECK(AgString("The Big Value") == ff->getDescription());
    }
    {
        FieldFormatPtr ff = FieldFormatPtr(FieldFormat::create("<s1><F>"));
        float f = 12345678901234567890.12345678901234567890F;
        AgObjectPtr fo = AgObjectPtr(new AgFloat(f));
        BOOST_CHECK(fabs(ff->valueFromString(ff->valueToString(fo))->toFloat() - f) < 0.0000000000001f) ;
    }

    {
        FieldFormatPtr ff = FieldFormatPtr(FieldFormat::create("<s1><E>"));
        double d = 12345678901234567890.12345678901234567890;
        AgObjectPtr ddo = AgObjectPtr(new AgDouble(d));

        BOOST_CHECK(fabs(d - ff->valueFromString(ff->valueToString(ddo))->toDouble()) < 0.000000000000000000001);
    }

}

BOOST_AUTO_TEST_CASE(testDataRecord)
{
    DataRecordPtr rec = DataRecordPtr(new DataRecord(createFormat("<<str><S>> <<int><I>> <<bool><B>> <<long><L>>")));
    BOOST_CHECK(rec->getString("str") == AgString(""));
    BOOST_CHECK(rec->getInt("int") == 0);
    BOOST_CHECK(rec->getBoolean("bool") == false);
    BOOST_CHECK(rec->getLong("long") == 0);

    try
    {
        rec->addString("ok");
        rec->addString("failure");
        assert(false);
    }
    catch (AggreGateException ex)
    {
    }

    try
    {
        rec->getBoolean("int");
        assert(false);
    }
    catch (AggreGateException ex1)
    {
    }

    rec->setValue("long", AgObjectPtr(new AgLong(80)));
    BOOST_CHECK(rec->getLong("long") == 80);
    BOOST_CHECK(boost::dynamic_pointer_cast<AgInteger>(rec->getValue("int"))->getValue() == 0);

    rec = DataRecordPtr(new DataRecord(createFormat("<<str><S><F=N>> <<int><I><F=N>> <<bool><B><F=N>> <<long><L><F=N>>")));
    BOOST_CHECK(AgString("") == rec->getString("str"));
    BOOST_CHECK(NULL == rec->getValue("int"));
    BOOST_CHECK(NULL == rec->getValue("bool"));
    BOOST_CHECK(NULL == rec->getValue("long"));

    rec = DataRecordPtr(new DataRecord(createFormat("<<str><S><F=N><A=flower>> <<int><I><F=N><A=11>> <<bool><B><F=N><A=1>> <<long><L><F=N><A=123>>")));
    BOOST_CHECK(AgString("flower") == rec->getString("str"));
    BOOST_CHECK(11 == rec->getValue("int")->toInteger());
    BOOST_CHECK(true == rec->getValue("bool")->toBoolean());
    BOOST_CHECK(123 == rec->getValue("long")->toLong());

    DataRecordPtr rec2 = DataRecordPtr(new DataRecord(createFormat("<<str><S><F=N><A=flower>> <<int><I><F=N><A=11>> <<bool><B><F=N><A=1>> <<long><L><F=N><A=123>>")));
    BOOST_CHECK(rec->equals(rec2));

    rec2 = DataRecordPtr(new DataRecord(createFormat("<<str><S><F=N><A=flower>> <<int><I><F=N><A=11>> <<bool><B><F=N><A=1>>")));
    BOOST_CHECK(!rec->equals(rec2));

    FieldFormatPtr ff1 = FieldFormatPtr(FieldFormat::create("test", 'I'));
    TableFormatPtr rf1 = TableFormatPtr(new TableFormat(ff1));
    rec = DataRecordPtr(new DataRecord(rf1));
    rec->addInt(0);

    rf1 = createFormat("<<str><S><F=N><A=flower>> <<st2><S><F=N>> <<int><I><F=N><A=11>> <<bool><B><F=N><A=1>>");

    std::list<AgObjectPtr> list;
    list.push_back(AgObjectPtr(new AgString("xx1")));
    list.push_back(AgObjectPtr());
    list.push_back(AgObjectPtr(new AgInteger(4)));
    list.push_back(AgObjectPtr(new AgBoolean(true)));
    rec = DataRecordPtr(new DataRecord(rf1, list));

    BOOST_CHECK(AgString("xx1") == rec->getString("str"));
    BOOST_CHECK(AgString("") == rec->getString("st2"));
    BOOST_CHECK(4 == rec->getValue("int")->toInteger());
    BOOST_CHECK(true == rec->getBoolean("bool"));

    TableFormatPtr rf3 = createFormat("<<str><S><F=N><A=flower>> <<int><I><F=N><A=11>> <<bool><B><F=N><A=1>>");
    rec = DataRecordPtr(new DataRecord(rf3));
    rec->setValue("int", AgObjectPtr());
    BOOST_CHECK(NULL == rec->getValue("int"));

    rec->setValue("bool", AgObjectPtr(new AgBoolean(true)));
    BOOST_CHECK(true ==  rec->getValue("bool")->toBoolean());

    rec = DataRecordPtr(new DataRecord(createFormat("<<str><S><F=N><A=flower>> <<int><I><F=N><A=11>> <<bool><B><F=N><A=1>>")));
    rec->setValue("bool", AgObjectPtr(new AgBoolean(true)));
    BOOST_CHECK(true == rec->getBoolean("bool"));

    ff1 = FieldFormatPtr(FieldFormat::create("<val><I>"));
    ff1->getValidators().push_back(FieldValidatorPtr(new LimitsValidator(10, 20)));

    rec = DataRecordPtr(new DataRecord(TableFormatPtr(new TableFormat(ff1))));
    rec->setValue(0, AgObjectPtr(new AgInteger(15)));

    try
    {
        rec->setValue(0, AgObjectPtr(new AgInteger(5)));
        assert(false);
    }
    catch (AggreGateException ex2)
    {
    }

    try
    {
        rec->setValue(0, AgObjectPtr(new AgInteger(25)));
        assert(false);
    }
    catch (AggreGateException ex2)
    {
    }
}



BOOST_AUTO_TEST_CASE(testSelect)
{
    TableFormatPtr rf = TableFormatPtr(new TableFormat());
    rf->addField("<str><S>");
    rf->addField("<int><I>");
    rf->addField("<val><S>");
    DataTablePtr t = DataTablePtr(new DataTable(rf));

    t->addRecord()->addString("line1").addInt(1).addValue(AgObjectPtr(new AgString("test1")));
    t->addRecord()->addString("line2").addInt(2).addValue(AgObjectPtr(new AgString("test2")));
    t->addRecord()->addString("line3").addInt(3).addValue(AgObjectPtr(new AgString("test3")));
    t->addRecord()->addString("line3").addInt(4).addValue(AgObjectPtr(new AgString("test4")));

    BOOST_CHECK(AgString("test2") == t->select("str", AgObjectPtr(new AgString("line2")))->getString("val"));

    std::list<QueryConditionPtr> conds;
    conds.push_back(QueryConditionPtr(new QueryCondition("str", AgObjectPtr(new AgString("line3")))));
    conds.push_back(QueryConditionPtr(new QueryCondition("int", AgObjectPtr(new AgInteger(4)))));

    DataTableQueryPtr query = DataTableQueryPtr(new DataTableQuery(conds));
    BOOST_CHECK(AgString("test4") == t->select(query)->getString("val"));

}


BOOST_AUTO_TEST_CASE(testDataTable)
{
    TableFormatPtr format = createFormat("<<val><I>> <<str><S>> <<bool><B>>");
    const int emptyRecords = 20;
    DataTablePtr dt = DataTablePtr(new DataTable(format, emptyRecords));

    BOOST_CHECK(emptyRecords == dt->getRecordCount());
    BOOST_CHECK(3 == dt->getFieldCount());

    std::list<AgObjectPtr> list;
    list.push_back(AgObjectPtr(new AgInteger(5)));
    list.push_back(AgObjectPtr(new AgString("test")));
    list.push_back(AgObjectPtr(new AgBoolean(true)));

    DataTablePtr tbl = DataTablePtr(new DataTable(createFormat("<<int><I>> <<str><S>> <<bool><B>>"), list));
    dt->getFieldCount();
    DataRecordPtr rec = DataRecordPtr(new DataRecord(FieldFormatPtr(FieldFormat::create("<<tbl><T>>"))->wrap()));
    rec->addDataTable(tbl);

    DataTablePtr t1 = DataTablePtr(new DataTable(rec));
    AgString str = t1->encode(ClassicEncodingSettingsPtr(new ClassicEncodingSettings(false)));
    std::string sss = str.toUtf8();
    t1 = DataTablePtr(new DataTable(t1->encode(ClassicEncodingSettingsPtr(new ClassicEncodingSettings(false)))));

    t1 = t1->rec()->getDataTable("tbl");

    BOOST_CHECK(1 == t1->getRecordCount());
    BOOST_CHECK(3 == t1->getFieldCount());
    BOOST_CHECK(5 == t1->rec()->getInt("int"));
    BOOST_CHECK(AgString("test") == t1->rec()->getString("str"));
    BOOST_CHECK(true == t1->rec()->getBoolean("bool"));
}

BOOST_AUTO_TEST_CASE(testClone)
{
    /*FieldFormatPtr ff = FieldFormatPtr(FieldFormat::create("<s1><S><F=N><A=default><D=Test><S=<desc=default><desc2=val2>><V=<L=1 10>>"));
    TableFormatPtr format = ff->wrap();
    DataTablePtr dt = DataTablePtr(new DataTable(format, 5));

    BOOST_CHECK(2 == dt->getFormat()->getField("s1")->getSelectionValues().size());

    DataTablePtr cl = DataTablePtr(dt->clone());
    BOOST_CHECK(format->equals(cl->getFormat().get()));
    BOOST_CHECK(2 == cl->getFormat()->getField("s1")->getSelectionValues().size());
    dt->getRecord(2)->setValue("s1", AgObjectPtr(new AgString("val2")));
    BOOST_CHECK(AgString("default") == cl->getRecord(2)->getString("s1"));*/
}

BOOST_AUTO_TEST_CASE(testParameterizedReportThatWorksIncorrectly)
{
    TableFormatPtr format = createFormat("<<devices><T><A=<F=<<devicePath><S><A=><D=Device Path>>><R=<aaa>><R=<bbb>>>>");

    DataTablePtr result = DataTablePtr(new DataTable(format, 1));
    DataTablePtr devices = result->rec()->getDataTable("devices");
    BOOST_CHECK(2 == devices->getRecordCount());
    BOOST_CHECK(AgString("bbb") == devices->getRecord(0)->getString("devicePath"));
    BOOST_CHECK(AgString("aaa") == devices->getRecord(1)->getString("devicePath"));
}

BOOST_AUTO_TEST_CASE(testInnerTableRecordsAreFilledProperly)
{
    TableFormatPtr innerFormat = TableFormatPtr(new TableFormat());
    innerFormat->addField('S', "devicePath", "Device Path");
    DataTablePtr defaultInnerTable = DataTablePtr(new DataTable(innerFormat));
    defaultInnerTable->addRecord(1, "aaa"); // fix gcc error: cannot pass objects of non-trivially-copyable type 'AgObjectPtr {aka class boost::shared_ptr<AgObject>}' through '...'
    defaultInnerTable->addRecord(1, "bbb"); // fix gcc error: cannot pass objects of non-trivially-copyable type 'AgObjectPtr {aka class boost::shared_ptr<AgObject>}' through '...'

    TableFormatPtr outerFormat = TableFormatPtr(new TableFormat());
    outerFormat->addField('T', "devices");
    outerFormat->getField(0)->setDefault(defaultInnerTable);

    AgString encodedFormat = outerFormat->encode(true);
    std::string sss = encodedFormat.toUtf8();
    BOOST_CHECK(AgString("<<devices><T><A=<F=<<devicePath><S><A=><D=Device Path>>><R=<aaa>><R=<bbb>>>>") == encodedFormat);

    TableFormatPtr decodedFormat = createFormat(encodedFormat);
    DataTablePtr innerDefaultTable = boost::dynamic_pointer_cast<DataTable>(decodedFormat->getField("devices")->getDefaultValue());
    BOOST_CHECK(2 == innerDefaultTable->getRecordCount());
    BOOST_CHECK(AgString("aaa") == innerDefaultTable->getRecord(0)->getString("devicePath"));

    DataTablePtr result = DataTablePtr(new DataTable(decodedFormat, 1));
    DataTablePtr devices = result->rec()->getDataTable("devices");
    BOOST_CHECK(2 == devices->getRecordCount());
    BOOST_CHECK(AgString("aaa") == devices->getRecord(0)->getString("devicePath"));
    BOOST_CHECK(AgString("bbb") == devices->getRecord(1)->getString("devicePath"));
}

BOOST_AUTO_TEST_CASE(testCopyWithoutKeysFields)
{
    TableFormatPtr rf = TableFormatPtr(new TableFormat());
    rf->addField("<st><S>");
    rf->addField("<val><I>");
    DataTablePtr master = DataTablePtr(new DataTable(rf));

    master->addRecord()->addString("line1").addInt(11);
    master->addRecord()->addString("line2").addInt(22);
    master->addRecord()->addString("line3").addInt(33);
    master->addRecord()->addString("line4").addInt(44);

    TableFormatPtr rf2 = TableFormatPtr(new TableFormat(0, 10));
    rf2->addField("<st><S>");
    rf2->addField("<val><I>");
    rf2->addField("<test><I><A=555>");
    DataTablePtr slave = DataTablePtr(new DataTable(rf2));

    slave->addRecord()->addString("xxx1").addInt(111).addInt(111);
    slave->addRecord()->addString("xxx2").addInt(222).addInt(222);
    slave->addRecord()->addString("xxx3").addInt(333).addInt(333);

    DataTableReplication::copy(*master, slave);

    BOOST_CHECK(4 == master->getRecordCount());
    BOOST_CHECK(4 == slave->getRecordCount());

    BOOST_CHECK(AgString("line1") == slave->getRecord(0)->getString("st"));
    BOOST_CHECK(555 == slave->getRecord(3)->getInt("test"));

    BOOST_CHECK(AgString("line2") == master->getRecord(1)->getString("st"));
}

BOOST_AUTO_TEST_CASE(testCopyWithKeysFields)
{
    TableFormatPtr rf1 = TableFormatPtr(new TableFormat());
    rf1->addField("<st><S><F=K>");
    rf1->addField("<val><I>");
    DataTablePtr master = DataTablePtr(new DataTable(rf1));

    master->addRecord()->addString("line1").addInt(11);
    master->addRecord()->addString("line2").addInt(22);
    master->addRecord()->addString("line3").addInt(33);
    master->addRecord()->addString("line4").addInt(44);

    TableFormatPtr rf2 = TableFormatPtr(new TableFormat(0, 10));
    rf2->addField("<st><S><F=K>");
    rf2->addField("<val><I>");
    rf2->addField("<test><I><A=555>");
    DataTablePtr slave = DataTablePtr(new DataTable(rf2));

    slave->addRecord()->addString("xxx1").addInt(111).addInt(111);
    slave->addRecord()->addString("xxx2").addInt(222).addInt(222);
    slave->addRecord()->addString("line1").addInt(333).addInt(333);

    DataTableReplication::copy(*master, slave);

    BOOST_CHECK(4 == master->getRecordCount());
    BOOST_CHECK(4 == slave->getRecordCount());

    BOOST_CHECK(555 == slave->select("st", AgObjectPtr(new AgString("line3")))->getInt("test"));
    BOOST_CHECK(333 == slave->select("st", AgObjectPtr(new AgString("line1")))->getInt("test"));
}

BOOST_AUTO_TEST_CASE(testCopyWithMaxRecordsReached)
{
    TableFormatPtr rf = TableFormatPtr(new TableFormat());
    rf->addField("<st><S>");
    rf->addField("<val><I>");
    DataTablePtr master = DataTablePtr(new DataTable(rf));

    master->addRecord()->addString("line1").addInt(11);
    master->addRecord()->addString("line2").addInt(22);
    master->addRecord()->addString("line3").addInt(33);
    master->addRecord()->addString("line4").addInt(44);

    TableFormatPtr rf2 = TableFormatPtr(new TableFormat(0, 3));
    rf2->addField("<st><S>");
    rf2->addField("<val><I>");
    rf2->addField("<test><I><A=555>");
    DataTablePtr slave = DataTablePtr(new DataTable(rf2));

    slave->addRecord()->addString("xxx1").addInt(111).addInt(111);
    slave->addRecord()->addString("xxx2").addInt(222).addInt(222);
    slave->addRecord()->addString("xxx3").addInt(333).addInt(333);

    std::set<AgString> errors = DataTableReplication::copy(*master, slave);

    BOOST_CHECK(3 == slave->getRecordCount());

    BOOST_CHECK(errors.size() == 1);
}

BOOST_AUTO_TEST_CASE(testUtfEncoding)
{
    /*AgChar c1(0xFFFF);
    AgChar c2(0x0000);
    AgChar c3(0x0123);
    AgString s = AgString(c1) + AgString(c2) + AgString(c3);

    FieldFormatPtr ff = FieldFormatPtr(FieldFormat::create("f", AGG_GLOBAL.STRING_FIELD));
    TableFormatPtr tf = TableFormatPtr(new TableFormat(1, 1, ff.get()));

    std::list<AgObjectPtr> list;
    list.push_back(AgObjectPtr(new AgString(s)));
    DataTablePtr st = DataTablePtr(new DataTable(tf, list));

    AgString enc = st->encode();
    std::string sss = enc.toUtf8();

    DataTablePtr dt = DataTablePtr(new DataTable(enc));

    AgString d = dt->rec()->getString("f");

    BOOST_CHECK(s == d);*/
}


BOOST_AUTO_TEST_SUITE_END( )
