/*! @file

    @id $Id$
*/
//       1         2         3         4         5         6         7         8
// 45678901234567890123456789012345678901234567890123456789012345678901234567890

#include <xml-cxx/xml.hxx>
#include <cppunit/TestFixture.h>
#include <cppunit/ui/text/TestRunner.h>
#include <cppunit/extensions/HelperMacros.h>
#include <cppunit/extensions/TestFactoryRegistry.h>
#include <cppunit/XmlOutputter.h>
#include <fstream>

class A: public xml::Serialize {
  public:
    int _anInteger;
    bool _aBool;
    double _aDouble;
    std::string _aString;
    std::string _anotherString;
    unsigned long _aLong;
  protected:
    void initXmlMembers() {
      className("A");
      persist(_anInteger, "anInteger");
      persist(_aBool, "aBool");
      persist(_aDouble, "aDouble");
      persist(_aString, "aString");
      persist(_anotherString, "anotherString");
      persist(_aLong, "aLong");
    }
};

class B: public xml::Serialize {
  public:
    long aLong;
    char aChar;
  protected:
    void initXmlMembers() {
      className("B");
      persist(aLong, "aLong");
      persist(aChar, "aChar");
    }
};

class B2: public B {
  public:
    float aFloat;
    short aShort;
  protected:
    void initXmlMembers() {
      B::initXmlMembers();
      className("B2");
      persist(aFloat, "float");
      persist(aShort, "short");
    }
};

// Complex Example: Class A2 is a class that inherits another class
// (A) and contains a class (B2) that inherits another class (B)
class A2: public A {
  public:
    std::string aText;
    B2 childOfB;
  protected:
    void initXmlMembers() {
      A::initXmlMembers();
      className("A2");
      persist(aText, "aText");
      persist(childOfB, "childOfB");
    }
};

class C: public xml::Serialize {
  public:
    A2 a2;
    B2 b2;
  protected:
    void initXmlMembers() {
      className("C")
        .persist(a2, "a2")
        .persist(b2, "b2");
    }
};

class SerializationTest: public CppUnit::TestFixture {
  public:
    std::string _file;
    A _a;
    void setUp() {
      _a._anInteger = 15;
      _a._aBool = true;
      _a._aDouble = 123.456;
      _a._aString = "Hello World";
      _a._anotherString = "This is another Text";
      _a._aLong = 4123674622ul;
      try {
        std::stringstream ss;
        _a.saveXml(ss);
        _file = ss.str();
      } catch (...) {}
    }
    void memberDeclaration() {
      // This is more a compile time test than a runtime test.
      A a;
      a._anInteger = 15;
      CPPUNIT_ASSERT_EQUAL(15, a._anInteger);
      a._aString = "Hello World";
      CPPUNIT_ASSERT_EQUAL(std::string("Hello World"), a._aString);
      std::stringstream ss;
    }
    void store() {
      {
        std::stringstream ss;
        CPPUNIT_ASSERT_NO_THROW(_a.saveXml(ss));
        CPPUNIT_ASSERT_EQUAL(std::string("<A>\n"
                                         "\t<anInteger>15</anInteger>\n"
                                         "\t<aBool>true</aBool>\n"
                                         "\t<aDouble>123.456</aDouble>\n"
                                         "\t<aString>Hello World</aString>\n"
                                         "\t<anotherString>This is"
                                         " another Text</anotherString>\n"
                                         "\t<aLong>4123674622</aLong>\n"
                                         "</A>"),
                             ss.str());
      } { // again - initialisation of A should be done only once
        std::stringstream ss;
        CPPUNIT_ASSERT_NO_THROW(_a.saveXml(ss));
        CPPUNIT_ASSERT_EQUAL(_file, ss.str());
      }
    }
    void restore() {
      A a;
      std::stringstream ss(_file);
      CPPUNIT_ASSERT(_file.size()>0);
      CPPUNIT_ASSERT_EQUAL(_file, ss.str());
      CPPUNIT_ASSERT_NO_THROW(a.loadXml(ss));
      CPPUNIT_ASSERT_EQUAL(15, a._anInteger);
      CPPUNIT_ASSERT_EQUAL(true, a._aBool);
      CPPUNIT_ASSERT_EQUAL(123.456, a._aDouble);
      CPPUNIT_ASSERT_EQUAL(std::string("Hello World"), a._aString);
      CPPUNIT_ASSERT_EQUAL(std::string("This is another Text"),
                           a._anotherString);
      CPPUNIT_ASSERT_EQUAL(4123674622ul, a._aLong);
    }
    void complexLoad() {
      std::stringstream ss("<A2>\n"
                           "  <anInteger>-1234</anInteger>\n"
                           "  <aBool>true</aBool>\n"
                           "  <aDouble>3.141</aDouble>\n"
                           "  <aString>This is A inside of A2</aString>\n"
                           "  <anotherString>Another A-String</anotherString>\n"
                           "  <aLong>1234567890</aLong>\n"
                           "  <aText>Text from A2</aText>\n"
                           "  <childOfB>\n"
                           "      <aLong>987654321</aLong>\n"
                           "      <aChar>Q</aChar>\n"
                           "      <float>2.5</float>\n"
                           "      <short>-127</short>\n"
                           "  </childOfB>\n"
                           "</A2>");
      A2 a2;
      CPPUNIT_ASSERT_EQUAL(std::string("<A2>\n"
                                       "\t<anInteger/>\n"
                                       "\t<aBool/>\n"
                                       "\t<aDouble/>\n"
                                       "\t<aString/>\n"
                                       "\t<anotherString/>\n"
                                       "\t<aLong/>\n"
                                       "\t<aText/>\n"
                                       "\t<childOfB>\n"
                                       "\t\t<aLong/>\n"
                                       "\t\t<aChar/>\n"
                                       "\t\t<float/>\n"
                                       "\t\t<short/>\n"
                                       "\t</childOfB>\n"
                                       "</A2>"), a2.schema());
      CPPUNIT_ASSERT_NO_THROW(a2.loadXml(ss));
      CPPUNIT_ASSERT_EQUAL(-1234, a2._anInteger);
      CPPUNIT_ASSERT_EQUAL(true, a2._aBool);
      CPPUNIT_ASSERT_EQUAL(3.141, a2._aDouble);
      CPPUNIT_ASSERT_EQUAL(std::string("This is A inside of A2"), a2._aString);
      CPPUNIT_ASSERT_EQUAL(std::string("Another A-String"), a2._anotherString);
      CPPUNIT_ASSERT_EQUAL(1234567890ul, a2._aLong);
      CPPUNIT_ASSERT_EQUAL(std::string("Text from A2"), a2.aText);
      CPPUNIT_ASSERT_EQUAL(987654321l, a2.childOfB.aLong);
      CPPUNIT_ASSERT_EQUAL('Q', a2.childOfB.aChar);
      CPPUNIT_ASSERT_EQUAL(2.5f, a2.childOfB.aFloat);
      CPPUNIT_ASSERT_EQUAL((short)-127, a2.childOfB.aShort);
    }
    void complexSave() {
      A2 a2;
      a2._anInteger = -1234;
      a2._aBool = true;
      a2._aDouble = 3.141;
      a2._aString = std::string("This is A inside of A2");
      a2._anotherString = std::string("Another A-String");
      a2._aLong = 1234567890ul;
      a2.aText = std::string("Text from A2");
      a2.childOfB.aLong = 987654321l;
      a2.childOfB.aChar = 'Q';
      a2.childOfB.aFloat = 2.5f;
      a2.childOfB.aShort = (short)-127;
      std::stringstream ss;
      a2.saveXml(ss);
      CPPUNIT_ASSERT_EQUAL
        (std::string("<A2>\n"
                     "\t<anInteger>-1234</anInteger>\n"
                     "\t<aBool>true</aBool>\n"
                     "\t<aDouble>3.141</aDouble>\n"
                     "\t<aString>This is A inside of A2</aString>\n"
                     "\t<anotherString>Another A-String</anotherString>\n"
                     "\t<aLong>1234567890</aLong>\n"
                     "\t<aText>Text from A2</aText>\n"
                     "\t<childOfB>\n"
                     "\t\t<aLong>987654321</aLong>\n"
                     "\t\t<aChar>Q</aChar>\n"
                     "\t\t<float>2.5</float>\n"
                     "\t\t<short>-127</short>\n"
                     "\t</childOfB>\n"
                     "</A2>"), ss.str());
    }
    void moreComplexLoad() {
      std::stringstream ss
        ("<C>\n"
         "  <a2>\n"
         "    <anInteger>-1234</anInteger>\n"
         "    <aBool>true</aBool>\n"
         "    <aDouble>3.141</aDouble>\n"
         "    <aString>This is A inside of A2</aString>\n"
         "    <anotherString>Another A-String</anotherString>\n"
         "    <aLong>1234567890</aLong>\n"
         "    <aText>Text from A2</aText>\n"
         "    <childOfB>\n"
         "      <aLong>987654321</aLong>\n"
         "      <aChar>Q</aChar>\n"
         "      <float>2.5</float>\n"
         "      <short>-127</short>\n"
         "    </childOfB>\n"
         "  </a2>\n"
         "  <b2>\n"
         "    <aLong>212121</aLong>\n"
         "    <aChar>W</aChar>\n"
         "    <float>2.25</float>\n"
         "    <short>124</short>\n"
         "  </b2>\n"
         "</C>");
      C c;
      CPPUNIT_ASSERT_EQUAL(std::string("<C>\n"
                                       "\t<a2>\n"
                                       "\t\t<anInteger/>\n"
                                       "\t\t<aBool/>\n"
                                       "\t\t<aDouble/>\n"
                                       "\t\t<aString/>\n"
                                       "\t\t<anotherString/>\n"
                                       "\t\t<aLong/>\n"
                                       "\t\t<aText/>\n"
                                       "\t\t<childOfB>\n"
                                       "\t\t\t<aLong/>\n"
                                       "\t\t\t<aChar/>\n"
                                       "\t\t\t<float/>\n"
                                       "\t\t\t<short/>\n"
                                       "\t\t</childOfB>\n"
                                       "\t</a2>\n"
                                       "\t<b2>\n"
                                       "\t\t<aLong/>\n"
                                       "\t\t<aChar/>\n"
                                       "\t\t<float/>\n"
                                       "\t\t<short/>\n"
                                       "\t</b2>\n"
                                       "</C>"), c.schema());
      CPPUNIT_ASSERT_NO_THROW(c.loadXml(ss));
      CPPUNIT_ASSERT_EQUAL(-1234, c.a2._anInteger);
      CPPUNIT_ASSERT_EQUAL(true, c.a2._aBool);
      CPPUNIT_ASSERT_EQUAL(3.141, c.a2._aDouble);
      CPPUNIT_ASSERT_EQUAL(std::string("This is A inside of A2"),
                           c.a2._aString);
      CPPUNIT_ASSERT_EQUAL(std::string("Another A-String"),
                           c.a2._anotherString);
      CPPUNIT_ASSERT_EQUAL(1234567890ul, c.a2._aLong);
      CPPUNIT_ASSERT_EQUAL(std::string("Text from A2"), c.a2.aText);
      CPPUNIT_ASSERT_EQUAL(987654321l, c.a2.childOfB.aLong);
      CPPUNIT_ASSERT_EQUAL('Q', c.a2.childOfB.aChar);
      CPPUNIT_ASSERT_EQUAL(2.5f, c.a2.childOfB.aFloat);
      CPPUNIT_ASSERT_EQUAL((short)-127, c.a2.childOfB.aShort);
      CPPUNIT_ASSERT_EQUAL(212121l, c.b2.aLong);
      CPPUNIT_ASSERT_EQUAL('W', c.b2.aChar);
      CPPUNIT_ASSERT_EQUAL(2.25f, c.b2.aFloat);
      CPPUNIT_ASSERT_EQUAL((short)124, c.b2.aShort);
    }
    void moreComplexSave() {
      C c;
      c.a2._anInteger = -1234;
      c.a2._aBool = true;
      c.a2._aDouble = 3.141;
      c.a2._aString = std::string("This is A inside of A2");
      c.a2._anotherString = std::string("Another A-String");
      c.a2._aLong = 1234567890ul;
      c.a2.aText = std::string("Text from A2");
      c.a2.childOfB.aLong = 987654321l;
      c.a2.childOfB.aChar = 'Q';
      c.a2.childOfB.aFloat = 2.5f;
      c.a2.childOfB.aShort = (short)-127;
      c.b2.aLong = 212121l;
      c.b2.aChar = 'W';
      c.b2.aFloat = 2.25f;
      c.b2.aShort = (short)124;
      std::stringstream ss;
      c.saveXml(ss);
      CPPUNIT_ASSERT_EQUAL
        (std::string("<C>\n"
                     "\t<a2>\n"
                     "\t\t<anInteger>-1234</anInteger>\n"
                     "\t\t<aBool>true</aBool>\n"
                     "\t\t<aDouble>3.141</aDouble>\n"
                     "\t\t<aString>This is A inside of A2</aString>\n"
                     "\t\t<anotherString>Another A-String</anotherString>\n"
                     "\t\t<aLong>1234567890</aLong>\n"
                     "\t\t<aText>Text from A2</aText>\n"
                     "\t\t<childOfB>\n"
                     "\t\t\t<aLong>987654321</aLong>\n"
                     "\t\t\t<aChar>Q</aChar>\n"
                     "\t\t\t<float>2.5</float>\n"
                     "\t\t\t<short>-127</short>\n"
                     "\t\t</childOfB>\n"
                     "\t</a2>\n"
                     "\t<b2>\n"
                     "\t\t<aLong>212121</aLong>\n"
                     "\t\t<aChar>W</aChar>\n"
                     "\t\t<float>2.25</float>\n"
                     "\t\t<short>124</short>\n"
                     "\t</b2>\n"
                     "</C>"), ss.str());
    }
    CPPUNIT_TEST_SUITE(SerializationTest);
    CPPUNIT_TEST(memberDeclaration);
    CPPUNIT_TEST(store);
    CPPUNIT_TEST(restore);
    CPPUNIT_TEST(complexLoad);
    CPPUNIT_TEST(complexSave);
    CPPUNIT_TEST(moreComplexLoad);
    CPPUNIT_TEST(moreComplexSave);
    CPPUNIT_TEST_SUITE_END();
};
CPPUNIT_TEST_SUITE_REGISTRATION(SerializationTest);

int main(int argc, char** argv) try {
  std::ofstream ofs((*argv+std::string(".xml")).c_str());
  CppUnit::TextUi::TestRunner runner;
  runner.setOutputter(new CppUnit::XmlOutputter(&runner.result(), ofs));
  runner.addTest(CppUnit::TestFactoryRegistry::getRegistry().makeTest());
  return runner.run() ? 0 : 1;
 } catch (std::exception& e) {
  std::cerr<<"***Exception: "<<e.what()<<std::endl;
  return 1;
 }
