#include <iostream>
#include <sstream>

#include "UnitTest++.h"
#include "test_utils.hh"

#include "geners/listIO.hh"
#include "geners/arrayIO.hh"
#include "geners/dequeIO.hh"
#include "geners/setIO.hh"
#include "geners/mapIO.hh"
#include "geners/complexIO.hh"

namespace haha {
    typedef struct {char a[2];} MyDummy;
}

gs_declare_type_as_pod(haha::MyDummy)
gs_specialize_class_id(haha::MyDummy, 0)

namespace {
   class Simple
   {
   public:
       inline Simple() : a_(0.0), b_(0) {}
       inline Simple(double a, int b) : a_(a), b_(b) {}

       inline double a() const {return a_;}
       inline int b() const {return b_;}

       inline bool operator==(const Simple& r) const
           {return a_ == r.a_ && b_ == r.b_;}
       inline bool operator!=(const Simple& r) const
           {return !(*this == r);}

       // Methods related to I/O follow
       inline gs::ClassId classId() const {return gs::ClassId(*this);}
       bool write(std::ostream& of) const;

       static inline const char* classname() {return "Simple";}
       static inline unsigned version() {return 1;}
       static void restore(const gs::ClassId& id, std::istream& in,
                           Simple* objectPtr);
   private:
       double a_;
       int b_;
   };

   bool Simple::write(std::ostream& of) const
   {
       gs::write_pod(of, a_);
       gs::write_pod(of, b_);
       return !of.fail();
   }

   void Simple::restore(const gs::ClassId& id, std::istream& in,
                        Simple* objectPtr)
   {
       static const gs::ClassId myId(gs::ClassId::makeId<Simple>());
       myId.ensureSameId(id);

       assert(objectPtr);
       gs::read_pod(in, &objectPtr->a_);
       gs::read_pod(in, &objectPtr->b_);
       if (in.fail()) throw gs::IOReadFailure();
   }

   struct Order
   {
       inline bool operator()(const std::vector<int>& v1,
                              const std::vector<int>& v2)
       {
           const unsigned sz1 = v1.size();
           const unsigned sz2 = v2.size();
           for (unsigned i=0; i<sz1 && i<sz2; ++i)
           {
               if (v1[i] < v2[i])
                   return true;
               if (v2[i] < v1[i])
                   return false;
           }
           if (sz1 < sz2)
               return true;
           else
               return false;
       }
   };
}

gs_specialize_class_id(Order, 1)

using namespace gs;
using namespace std;
using namespace haha;

namespace {
    TEST(write_item)
    {
        std::stringstream stream;
        std::vector<ClassId> proto;

        const int d = 234;
        int d2 = 0;
        CHECK(write_item(stream, d, true));
        restore_item(stream, &d2, true);
        CHECK(d == d2);

        std::complex<double> cmplx(3.0, 17.0), rcmplx(0.0, 0.0);
        write_pod(stream, cmplx);
        read_pod(stream, &rcmplx);
        CHECK(cmplx == rcmplx);
        rcmplx = std::complex<double>(0.0, 0.0);
        CHECK(write_item(stream, cmplx, true));
        restore_item(stream, &rcmplx, true);
        CHECK(cmplx == rcmplx);

        int intarr[3] = {3, 2, 1}, readarr[3] = {0,};
        CHECK(write_array(stream, intarr, 3));
        read_array(stream, readarr, 3);
        for (unsigned i=0; i<3; ++i)
            CHECK_EQUAL(intarr[i], readarr[i]);

        std::pair<double,Simple> p1, p3;
        p1 = std::make_pair(23.0, Simple(45.0, -123));
        CHECK(write_item(stream, p1, true));
        restore_item(stream, &p3, true);
        CHECK(p1 == p3);

        std::basic_string<char> s, sin = "hahahahswqdeq";
        s += 13;
        s += 27;
        CHECK(write_item(stream, s, false));
        restore_item(stream, &sin, false);
        CHECK(sin == s);

        CPP11_array<std::complex<float>,3> carr, r_carr;
        carr[0] = std::complex<float>(1.f, 2.f);
        carr[1] = std::complex<float>(17.f, 23.f);
        carr[2] = std::complex<float>(234.f, -15.f);
        CHECK(write_item(stream, carr, true));
        restore_item(stream, &r_carr, true);
        CHECK(carr == r_carr);

        CPP11_array<float,15> arr, r_arr;
        for (unsigned i=0; i<15; ++i)
            arr[i] = i + 1.f;
        CHECK(write_item(stream, arr, true));
        restore_item(stream, &r_arr, true);
        CHECK(r_arr == arr);

        Simple obj1(15.0, 777), obj2;
        CHECK(obj1.classId().write(stream) && obj1.write(stream));
        ClassId simpleId(stream, 1);
        Simple::restore(simpleId, stream, &obj2);
        CHECK(obj1 == obj2);

        std::vector<Simple> sarr(2);
        std::vector<Simple> r_sarr;
        sarr[0] = Simple(23.0, 17);
        sarr[1] = Simple(-10.0, 3);
        CHECK(write_item(stream, sarr, true));
        restore_item(stream, &r_sarr, true);
        CHECK(r_sarr == sarr);

        CPP11_array<MyDummy,2> darr, r_darr;
        darr[0].a[0] = 'a';
        darr[0].a[1] = 's';
        darr[1].a[0] = 'd';
        darr[1].a[1] = 'f';
        r_darr[0].a[0] = 'h';
        CHECK(write_item(stream, darr, true));
        restore_item(stream, &r_darr, true);
        CHECK_EQUAL('a', r_darr[0].a[0]);
        CHECK_EQUAL('s', r_darr[0].a[1]);
        CHECK_EQUAL('d', r_darr[1].a[0]);
        CHECK_EQUAL('f', r_darr[1].a[1]);

        std::string haha = "crap", dudle = "dudle";
        CHECK(write_item(stream, haha, true));
        CHECK(write_item(stream, dudle, false));

        std::string r_haha;
        restore_item(stream, &r_haha, true);
        CHECK(r_haha == haha);
        restore_item(stream, &r_haha, false);
        CHECK(r_haha == dudle);

        CPP11_array<std::string,2> arrs;
        arrs[0] = haha;
        arrs[1] = dudle;
        process_const_item<GenericWriter>(arrs, stream, &proto, true);

        CPP11_array<std::string,2> r_arrs;
        process_item<GenericReader>(r_arrs, stream, &proto, true);
        CHECK(r_arrs == arrs);

        std::basic_string<int> bs;
        bs += 10;
        bs += 20;
        process_const_item<GenericWriter>(bs, stream, &proto, true);

        std::basic_string<int> rbs;
        process_item<GenericReader>(rbs, stream, &proto, true);
        CHECK(rbs == bs);

        std::vector<int> v1, v2;
        for (unsigned i=0; i<109; ++i)
            v1.push_back(static_cast<int>(test_rng()*1000000));
        process_const_item<GenericWriter>(v1, stream, &proto, true);
        process_item<GenericReader>(v2, stream, &proto, true);
        CHECK(v1 == v2);

        std::list<int> lst, lst2;
        lst.push_back(1);
        lst.push_back(2);
        lst.push_back(3);
        process_const_item<GenericWriter>(lst, stream, &proto, false);
        process_item<GenericReader>(lst2, stream, &proto, false);
        CHECK(lst == lst2);

        std::deque<double> dq, dq2;
        dq.push_back(4.0);
        dq.push_back(5.0);
        process_const_item<GenericWriter>(dq, stream, &proto, true);
        process_item<GenericReader>(dq2, stream, &proto, true);
        CHECK(dq == dq2);

        std::multiset<float> sf, sf2;
        sf.insert(2.f);
        sf.insert(4.f);
        sf.insert(4.f);
        sf.insert(3.f);
        process_const_item<GenericWriter>(sf, stream, &proto, true);
        process_item<GenericReader>(sf2, stream, &proto, true);
        CHECK(sf == sf2);

        std::map<int,double> mp, mp2;
        mp.insert(std::make_pair(1, 10.0));
        mp.insert(std::make_pair(2, 15.0));
        process_const_item<GenericWriter>(mp, stream, &proto, true);
        process_item<GenericReader>(mp2, stream, &proto, true);
        CHECK(mp == mp2);

        std::vector<bool> vb, vb2;
        vb.push_back(1);
        vb.push_back(0);
        vb.push_back(1);
        process_const_item<GenericWriter>(vb, stream, &proto, true);
        process_item<GenericReader>(vb2, stream, &proto, true);
        CHECK(vb == vb2);

        int ival = 34456, kval = 978645, jval = 0, lval = 32390, mval = 2579;
        process_const_item<GenericWriter>(ival, stream, &proto, true);
        process_const_item<GenericWriter>(kval, stream, &proto, true);
        process_const_item<GenericWriter>(lval, stream, &proto, true);
        process_const_item<GenericWriter>(mval, stream, &proto, true);

        int* iread = 0, *jread = &jval;
        process_item<GenericReader>(iread, stream, &proto, true);
        CHECK(*iread == ival);
        process_item<GenericReader>(jread, stream, &proto, true);
        CHECK(jval == kval);
        delete iread;
        CPP11_shared_ptr<int> sptr(new int());
        process_item<GenericReader>(sptr, stream, &proto, true);
        CHECK(*sptr == lval);
        CPP11_shared_ptr<int> sptr2;
        process_item<GenericReader>(sptr2, stream, &proto, true);
        CHECK(*sptr2 == mval);

        std::vector<int*> vec1;
        for (unsigned i=0; i<10; ++i)
            vec1.push_back(new int(static_cast<int>(test_rng()*1000000)));
        process_const_item<GenericWriter>(vec1, stream, &proto, true);

        std::vector<int*> vec3;
        CHECK(process_item<GenericReader>(vec3, stream, &proto, true));
        CHECK_EQUAL(10U, vec3.size());
        for (unsigned i=0; i<10; ++i)
        {
            CHECK(*vec1[i] == *vec3[i]);
            delete vec3[i];
        }

        std::vector<CPP11_shared_ptr<int> > vec2;
        process_const_item<GenericWriter>(vec1, stream, &proto, true);
        CHECK(process_item<GenericReader>(vec2, stream, &proto, true));
        for (unsigned i=0; i<10; ++i)
        {
            CHECK(*vec1[i] == *vec2[i]);
            delete vec1[i];
        }

        // Check that writing out an item by pointer is equivalent
        // to writing it out by reference -- up to a point. Writing
        // out the class id will be automatically turned on if the
        // item is written out by pointer.
        ival = 456;
        CHECK(write_item(stream, &ival, false));
        CHECK(ival == 456);

        int ival_2 = 0;
        restore_item(stream, &ival_2, true);
        CHECK(ival == ival_2);

        // Check write_array/read_array for objects (non-PODs)
        const unsigned nsimple = 5;
        Simple simparr[nsimple];
        for (unsigned i=0; i<nsimple; ++i)
            simparr[i] = Simple(test_rng(), static_cast<int>(test_rng()*100));
        CHECK(write_array(stream, simparr, nsimple));

        Simple simparr2[nsimple];
        read_array(stream, simparr2, nsimple);
        for (unsigned i=0; i<nsimple; ++i)
            CHECK(simparr[i] == simparr2[i]);

        {
            std::map<std::vector<int>,double,Order> mp1;
            std::vector<int> v1(2), v2(2);
            v1[0] = 1;
            v1[1] = 2;
            v2[0] = 1;
            v2[1] = 3;
            mp1[v1] = 2.0;
            mp1[v2] = 3.0;
            std::stringstream s;
            CHECK(write_item(s, mp1, true));
            std::map<std::vector<int>,double,Order> mp2;
            restore_item(s, &mp2, true);
            CHECK(mp1 == mp2);
        }
    }
}
