#include <numeric>

#include "UnitTest++.h"

#include "test_utils.hh"

#include "geners/StringArchive.hh"
#include "geners/ColumnPacker.hh"
#include "geners/IOPtr.hh"
#include "geners/CP_column_iterator.hh"

using namespace gs;
using namespace std;

#ifdef CPP11_STD_AVAILABLE

#define io_ptr(obj) gs::make_IOPtr(obj)
#define io_proxy(obj) gs::make_IOProxy( obj , #obj )

namespace {
    TEST(ColumnPacker)
    {
        StringArchive ar0("archive");

        // Test that archive works with bare tuples
        auto tuple1 = tuple<int, float, double, long long>();

        typedef ColumnPacker<decltype(tuple1)> Packer1;
        const unsigned bufferSize = 1024;
        Packer1 packer({"i1", "f1", "d1", "ll1"},
                       "Title", ar0, "Name", "Category", bufferSize);
        std::vector<decltype(tuple1)> t1copy;

        // We need to have enough data to fill at least a few buffers
        // in order to understand if things work correctly.
        unsigned datasize = sizeof(int) + sizeof(float) + sizeof(double) +
            sizeof(long long);
        const unsigned long rows_per_buffer = bufferSize/datasize + 1;
        const unsigned long nrows = 50*rows_per_buffer + 1;

        for (unsigned long row=0; row<nrows; ++row)
        {
            tuple1 = make_tuple(
                (int)(test_rng()*10000),
                (float)(test_rng()),
                (double)(test_rng()),
                (long long)(test_rng()*100000)
            );
            t1copy.push_back(tuple1);
            CHECK(packer.fill(tuple1));
        }

        // Now, let see if we can compare the contents to the
        // original tuple
        CHECK_EQUAL(nrows, packer.nRows());
        CHECK_EQUAL(std::tuple_size<decltype(tuple1)>::value,
                    packer.nColumns());
        for (unsigned long row=0; row<nrows; ++row)
        {
            packer.rowContents(row, &tuple1);
            CHECK(tuple1 == t1copy[row]);
        }

        // Write the packer to the archive and redo the comparison
        packer.write();
        CHECK_EQUAL(nrows, packer.nRows());
        CHECK_EQUAL(std::tuple_size<decltype(tuple1)>::value,
                    packer.nColumns());
        for (unsigned long row=0; row<nrows; ++row)
        {
            packer.rowContents(row, &tuple1);
            CHECK(tuple1 == t1copy[row]);
        }

        // Check if we can read the packer back
        std::unique_ptr<Packer1> rp = 
            CPReference<Packer1>(ar0, "Name", "Category").get(0);
        assert(rp.get());

        CHECK_EQUAL(nrows, rp->nRows());
        CHECK_EQUAL(std::tuple_size<decltype(tuple1)>::value, rp->nColumns());
        for (unsigned long row=0; row<nrows; ++row)
        {
            rp->rowContents(row, &tuple1);
            CHECK(tuple1 == t1copy[row]);
        }

        CHECK(*rp == packer);

        // Check single column lookup
        double sum = 0.0;
        double dummy = 0;
        for (unsigned long row=0; row<nrows; ++row)
        {
            packer.rowContents(row, &tuple1);

            // Storage given by IOPtr
            packer.fetchItem<2>(row, io_ptr(dummy));

            // Make sure all results are the same
            CHECK_EQUAL(std::get<2>(tuple1), dummy);
            sum += dummy;
        }

        // Try to use the iterator
        double sum2 = std::accumulate(CP_column_begin<2>(packer,io_ptr(dummy)),
                                  CP_column_end<2>(packer,io_ptr(dummy)), 0.0);
        CHECK_EQUAL(sum, sum2);

        // Check that we can extract a fraction of the columns
        float f1;
        double d1;
        unsigned haha;
        auto proto = std::make_tuple(
            io_proxy(d1),
            io_proxy(f1),
            io_proxy(haha)
        );
        typedef ColumnPacker<decltype(proto)> Packer2;
        std::unique_ptr<Packer2> rp2 = 
            CPReference<Packer2>(proto, ar0, "Name", "Category").get(0);
        assert(rp2.get());        

        double sum3 = std::accumulate(CP_column_begin<0>(*rp2,io_ptr(dummy)),
                                  CP_column_end<0>(*rp2,io_ptr(dummy)), 0.0);
        CHECK_EQUAL(sum, sum3);

        // Cycle both readback tuples
        for (unsigned long row=0; row<nrows; ++row)
        {
            packer.rowContents(row, &tuple1);
            rp2->rowContents(row, &proto);
            CHECK_EQUAL(d1, std::get<2>(tuple1));
            CHECK_EQUAL(f1, std::get<1>(tuple1));
        }

        // Check the "original" settings for rp2
        CHECK_EQUAL(4U, rp2->nOriginalColumns());
        CHECK_EQUAL(std::string("i1"), rp2->originalColumnName(0));
        CHECK_EQUAL(std::string("f1"), rp2->originalColumnName(1));
        CHECK_EQUAL(std::string("d1"), rp2->originalColumnName(2));
        CHECK_EQUAL(std::string("ll1"), rp2->originalColumnName(3));
        CHECK(packer.columnNames() == rp2->originalColumnNames());
        CHECK_EQUAL(2U, rp2->nColumnsReadBack());
        const std::vector<unsigned char> mask = {1, 1, 0};
        CHECK(mask == rp2->readbackMask());
    }
}

#else // CPP11_STD_AVAILABLE

namespace {
    TEST(ColumnPacker)
    {
    }
}

#endif // CPP11_STD_AVAILABLE
