#include <sstream>
#include <cstring>

#include "UnitTest++.h"

#include "geners/ClassId.hh"
#include "geners/IOException.hh"
#include "geners/BooleanString.hh"

using namespace gs;
using namespace std;

namespace {
    static int flag = 0;

    class DummyClass1
    {
    public:
        static const char* classname() {return "DummyClass1";}
        static unsigned version() {return 1U;}
    };

    class DummyClass2
    {
    public:
        DummyClass2() : clid_(ClassId::invalidId()), haveClid_(false) {}

        inline const ClassId& classId() const
        {
            flag = 2;
            if (!haveClid_)
            {
                haveClid_ = true;
                clid_ = ClassId(*this);
            }
            return clid_;
        }
        static const char* classname() {return "DummyClass2";}
        static unsigned version() {return 1U;}

    private:
        mutable ClassId clid_;
        mutable bool haveClid_;
    };

    class DummyClass3
    {
    public:
        virtual ~DummyClass3() {}

        inline virtual ClassId classId() const
        {
            flag = 3;
            return ClassId(*this);
        }
        static const char* classname() {return "DummyClass3";}
        static unsigned version() {return 1U;}
    };

    class DummyClass4 : public DummyClass3
    {
    public:
        virtual ~DummyClass4() {}

        inline virtual ClassId classId() const
        {
            flag = 4;
            return ClassId(*this);
        }
        static const char* classname() {return "DummyClass4";}
        static unsigned version() {return 1U;}
    };

    template <class T>
    class DummyClass5
    {
    public:
        inline const ClassId classId() const {return ClassId(*this);}
        static const char* classname() {return classname_.c_str();}
        static unsigned version() {return 1U;}

    private:
        static std::string classname_;
    };

    template<class T>
    std::string DummyClass5<T>::classname_ = 
        template_class_name<T>("DummyClass5");

    template <class T1, class T2>
    class DummyClass6
    {
    public:
        inline const ClassId classId() const {return ClassId(*this);}
        static const char* classname() {return classname_.c_str();}
        static unsigned version() {return 1U;}

    private:
        static std::string classname_;
    };

    template<class T1, class T2>
    std::string DummyClass6<T1,T2>::classname_ = 
        template_class_name<T1,T2>("DummyClass6");

    TEST(ClassId)
    {
        ClassId idaa2("aa", 2);
        ClassId idaa3("aa", 3);
        CHECK(idaa2 != idaa3);
        idaa2.setVersion(3);
        CHECK(idaa2 == idaa3);

        DummyClass1 obj;
        ClassId id(obj);
        CHECK(!id.name().empty());

        ostringstream os;
        CHECK(id.write(os));

        istringstream is(os.str());
        ClassId id2(is, 1);
        CHECK(!id2.name().empty());
        CHECK(id2 == id);

        bool caught = false;
        try 
        {
            ClassId id3(is, 1);
        }
        catch (IOReadFailure& e)
        {
            caught = true;
        }
        CHECK(caught);

        DummyClass2 dc2;
        flag = 0;
        ClassId::itemId(dc2);
        CHECK_EQUAL(2, flag);

        DummyClass3 dc3;
        flag = 0;
        ClassId::itemId(dc3);
        CHECK_EQUAL(3, flag);

        DummyClass4 dc4;
        flag = 0;
        ClassId::itemId(dc4);
        CHECK_EQUAL(4, flag);

        flag = 0;
        CHECK(ClassId::itemId(flag) == ClassId::makeId<int>());
        CHECK_EQUAL(0, flag);

        DummyClass3& dref(dc4);
        flag = 0;
        ClassId::itemId(dref);
        CHECK_EQUAL(4, flag);

        CHECK(ClassId(std::string("int(0)")) == ClassId::makeId<int>());
    }

    TEST(TemplateClassId)
    {
        std::vector<std::vector<ClassId> > params;

        ClassId id5(DummyClass5<int>().classId());
        CHECK(id5 == ClassId(std::string("DummyClass5<int(0)>(1)")));
        CHECK(id5.isTemplate());
        id5.templateParameters(&params);
        CHECK_EQUAL(1U, params.size());
        CHECK(params[0][0] == ClassId::makeId<int>());

        ClassId id6(DummyClass6<int,double>().classId());
        CHECK(id6.isTemplate());
        id6.templateParameters(&params);
        CHECK_EQUAL(2U, params.size());
        CHECK(params[0][0] == ClassId::makeId<int>());
        CHECK(params[1][0] == ClassId::makeId<double>());

        ClassId id7(DummyClass6<DummyClass5<int>,double>().classId());
        CHECK(id7.isTemplate());
        id7.templateParameters(&params);
        CHECK_EQUAL(2U, params.size());
        CHECK(params[0][0] == id5);
        CHECK(params[1][0] == ClassId::makeId<double>());
    }

    TEST(BooleanString)
    {
        CHECK(BooleanString<true>::value);
        CHECK(!BooleanString<false>::value);
        CHECK_EQUAL(0, strcmp(BooleanString<false>::name(), "false"));
        CHECK_EQUAL(0, strcmp(BooleanString<true>::name(), "true"));
    }
}
