#include "UnitTest++.h"

#include "test_utils.hh"

#include "geners/VarPack.hh"
#include "geners/IOPtr.hh"
#include "geners/StringArchive.hh"
#include "geners/Record.hh"
#include "geners/Reference.hh"

using namespace gs;
using namespace std;

#ifdef CPP11_STD_AVAILABLE

#define io_ptr(obj) gs::make_IOPtr(obj)

namespace {
    TEST(VarPack)
    {
        typedef CPP11_shared_ptr<double> Dptr;

        StringArchive ar0;

        // Write a tuple which we will later read as a variable pack
        int i1 = 15;
        Dptr dp(new double());
        auto agw = make_tuple(
            &i1,
            dp,
            35
            );
        i1 = 30;
        *dp = 345.0;
        ar0 << Record(agw, "varpack", "top");
        const unsigned long len0 = ar0.dataSize();

        i1 = 13;
        *dp = 678.0;
        std::get<2>(agw) = 64;
        ar0 << Record(agw, "varpack", "top");
        const unsigned long len1 = ar0.dataSize();

        // Figure out what kind of record size we are expecting for the
        // second write operation. The first thing is the class id of
        // the pack itself.
        const ClassId& agwid = ClassId::itemId(agw);
        unsigned idlen = agwid.id().size() + sizeof(unsigned long);

        // We have a bare int pointer in the pack. The I/O code will switch
        // to writing out the class id for that pointer.
        idlen += ClassId::makeId<int>().id().size() + sizeof(unsigned long);

        // We have a shared double pointer in the pack. The I/O code will
        // switch to writing out the class id for that pointer.
        idlen += ClassId::makeId<double>().id().size() + sizeof(unsigned long);

        // Of course, we also need to take into account the size of
        // the data itself
        unsigned long datasize = sizeof(int) + sizeof(double) + sizeof(int);

        // Make sure the above arguments are correct
        CHECK_EQUAL(datasize + idlen, len1-len0);

        // Make a variable pack with IOPtr objects. For them, the I/O
        // code will not switch to writing out the class ids.
        long lv = 10;
        float fv = 5.f;
        double dv = 1.0;
        auto pack = make_VarPack(
            io_ptr(lv),
            io_ptr(fv),
            io_ptr(dv)
            );
        ar0 << Record(pack, "pack", "top");
        const unsigned long lena = ar0.dataSize();

        lv = 11;
        fv = 6.f;
        dv = 2.0;    
        ar0 << Record(pack, "pack", "top");
        const unsigned long lenb = ar0.dataSize();
        const ClassId& packid = ClassId::itemId(pack);
        idlen = packid.id().size() + sizeof(unsigned long);
        datasize = sizeof(long) + sizeof(float) + sizeof(double);
        CHECK_EQUAL(datasize + idlen, lenb - lena);

        // Make sure we can read varpacks back
        int ival;
        Dptr pd(new double());
        Dptr pk = pd;
        auto rag = make_VarPack(
            &ival,
            pk,
            12
        );
        Reference<decltype(rag)> ref(ar0, "varpack", "top");
        CHECK(ref.size() == 2);

        ref.restore(0, &rag);
        CHECK(ival == 30);
        CHECK(*std::get<1>(rag) == 345.0);
        CHECK(std::get<2>(rag) == 35);

        ref.restore(1, &rag);
        CHECK(ival == 13);
        CHECK(*std::get<1>(rag) == 678.0);
        CHECK(std::get<2>(rag) == 64);

        Reference<decltype(pack)> pref(ar0, "pack", "top");
        CHECK(pref.size() == 2);
        pref.restore(0, &pack);
        CHECK(lv == 10);
        CHECK(fv == 5.f);
        CHECK(dv == 1.0);

        pref.restore(1, &pack);
        CHECK(lv == 11);
        CHECK(fv == 6.f);
        CHECK(dv == 2.0);
    }
}

#else // CPP11_STD_AVAILABLE

namespace {
    TEST(VarPack)
    {
    }
}

#endif // CPP11_STD_AVAILABLE
