// Copyright (C) 2002-2003 Chad C. Yates cyates@uidaho.edu
//  
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
// 
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software 
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
// 
// As a special exception to the GNU General Public License, permission is 
// granted for additional uses of the text contained in its release 
// of Common C++.
// 
// The exception is that, if you link the Common C++ library with other
// files to produce an executable, this does not by itself cause the
// resulting executable to be covered by the GNU General Public License.
// Your use of that executable is in no way restricted on account of
// linking the Common C++ library code into it.
//
// This exception does not however invalidate any other reasons why
// the executable file might be covered by the GNU General Public License.
//
// This exception applies only to the code released under the 
// name Common C++.  If you copy code from other releases into a copy of
// Common C++, as the General Public License permits, the exception does
// not apply to the code that you add in this way.  To avoid misleading
// anyone as to the status of such modified files, you must delete
// this exception notice from them.
// 
// If you write modifications of your own for Common C++, it is your choice
// whether to permit this exception to apply to your modifications.
// If you do not wish that, delete this exception notice.

#include "Test_Engine.h"
#include <cstdio>

CPPUNIT_TEST_SUITE_REGISTRATION(EngineTest);

void EngineTest::setUp()
{
  // Create an example object with some sub objects and various data elements
  complexObject.iInteger = 5;
  complexObject.strString = "String 1";
  complexObject.strString2 = "String 2";
  complexObject.uninitializedNullPtr = NULL;  // initialized here instead of constructor to see if it will be unpersisted as a null (vs defaulting to null from the constructor)
  for(int i = 0; i < 10; i++)
    complexObject.numbers.push_back(i);
  complexObject.strings.push_back("a");
  complexObject.strings.push_back("b");
  complexObject.strings.push_back("c");
  complexObject.strings.push_back("d");
  complexObject.strings.push_back("e");

  complexObject.moreStrings.push_back("z");
  complexObject.moreStrings.push_back("y");
  complexObject.moreStrings.push_back("x");
  complexObject.moreStrings.push_back("w");
  complexObject.moreStrings.push_back("v");

  complexObject.subObjectPtr = new TestSub;
  complexObject.subObjectPtr2 = complexObject.subObjectPtr; // set to point to the same thing as subObjectPtr to test unpersisting of shared instances
  complexObject.subObjectPtr->iInteger = 5;
  complexObject.subObjectPtr->strString = "SubStringPtr 1";
  complexObject.subObjectPtr->strString2 = "SubStringPtr 2";
  for(int j = 10; j < 20; j++)
    complexObject.subObjectPtr->numbers.push_back(j);

  complexObject.subObjectRef.iInteger = 5;
  complexObject.subObjectRef.strString = "SubString2 1";
  complexObject.subObjectRef.strString2 = "SubString2 2";
  for(int k = 30; k < 35; k++)
    complexObject.subObjectRef.numbers.push_back(k);

  // make a std::deque of TestSub objects
  for(int l = 0; l < 2; l++)
  {
    TestSub newSubObject;
    newSubObject.iInteger = l;
    char tmp[50];
    sprintf(tmp, "test %d", l+1);
    newSubObject.strString = tmp;
    for(int k = 30; k < 35; k++)
      newSubObject.numbers.push_back(k);
    complexObject.subObjects.push_back(newSubObject);
  }
}

void EngineTest::tearDown() 
{}

void EngineTest::testPrimitives()
{
  // write primitive types
  std::fstream outputArchive("EnginePrimitiveTest.dat", std::ios::out|std::ios::binary);
  Engine outputEngine(outputArchive, ost::Engine::modeWrite);

  TEST_PRIMITIVE_OUTPUT(int8, 0x01);
  TEST_PRIMITIVE_OUTPUT(uint8, 0x01);
  TEST_PRIMITIVE_OUTPUT(int16, 0x0123);
  TEST_PRIMITIVE_OUTPUT(uint16, 0x0123);
  TEST_PRIMITIVE_OUTPUT(int32, 0x01234567);
  TEST_PRIMITIVE_OUTPUT(uint32, 0x01234567);
  //TEST_PRIMITIVE_OUTPUT(int64, 0x0123456789ABCDEF); // warning: integer constant larger than the maximum value of an unsigned long int
  //TEST_PRIMITIVE_OUTPUT(uint64, 0x0123456789ABCDEF); // warning: integer constant larger than the maximum value of an unsigned long int
  TEST_PRIMITIVE_OUTPUT(float, 3.141592653589793238462643f);
  TEST_PRIMITIVE_OUTPUT(double, 3.141592653589793238462643);
  TEST_PRIMITIVE_OUTPUT(string, "abcdefghijklmnopqrstuvwxyz0123456789");

  outputEngine.sync(); // flush Engine buffers before closing file
  outputArchive.close();

  // read primitive types back in and check
  std::fstream inputArchive("EnginePrimitiveTest.dat", std::ios::in|std::ios::binary);
  Engine inputEngine(inputArchive, ost::Engine::modeRead);

  TEST_PRIMITIVE_INPUT(int8);
  TEST_PRIMITIVE_INPUT(uint8);
  TEST_PRIMITIVE_INPUT(int16);
  TEST_PRIMITIVE_INPUT(uint16);
  TEST_PRIMITIVE_INPUT(int32);
  TEST_PRIMITIVE_INPUT(uint32);
  //TEST_PRIMITIVE_INPUT(int64);
  //TEST_PRIMITIVE_INPUT(uint64);
  TEST_PRIMITIVE_INPUT(float);
  TEST_PRIMITIVE_INPUT(double);
  TEST_PRIMITIVE_INPUT(string);

  inputArchive.close();
}


void EngineTest::testRawBinary()
{
  int i;

  // write raw binary data
  std::fstream outputArchive("EngineRawBinaryTest.dat", std::ios::out|std::ios::binary);
  Engine outputEngine(outputArchive, ost::Engine::modeWrite);

  unsigned int binaryBuffer[BINARY_BUFFER_SIZE];
  for(i = 0; i < BINARY_BUFFER_SIZE; i++)
    binaryBuffer[i] = i;

  outputEngine.writeBinary((const uint8*) binaryBuffer, sizeof(binaryBuffer));
  outputEngine.sync(); // flush Engine buffers before closing file
  outputArchive.close();

  // read binary data back in and check
  std::fstream inputArchive("EngineRawBinaryTest.dat", std::ios::in|std::ios::binary);
  Engine inputEngine(inputArchive, ost::Engine::modeRead);

  unsigned int binaryBuffer2[BINARY_BUFFER_SIZE];
  inputEngine.readBinary((uint8*) binaryBuffer2, sizeof(binaryBuffer2));
  inputArchive.close();

  for(i = 0; i < BINARY_BUFFER_SIZE; i++)
    CPPUNIT_ASSERT(binaryBuffer[i] == binaryBuffer2[i]);
}

void EngineTest::testSTLVector()
{
  int i;

  // write STL data
  std::fstream outputArchive("EngineSTLVectorTest.dat", std::ios::out|std::ios::binary);
  Engine outputEngine(outputArchive, ost::Engine::modeWrite);

  std::vector<int32> intVector;
  std::vector<int32>* pIntVector = new std::vector<int32>;
  for(i = 0; i < STL_CONTAINER_SIZE; i++)
  {
    intVector.push_back(i);
    pIntVector->push_back(i);
  }

  outputEngine << intVector;
  outputEngine << *pIntVector;
  outputEngine.sync(); // flush Engine buffers before closing file
  outputArchive.close();

  // read STL std::vector back in and check
  std::fstream inputArchive("EngineSTLVectorTest.dat", std::ios::in|std::ios::binary);
  Engine inputEngine(inputArchive, ost::Engine::modeRead);

  std::vector<int32> intVector2;
  inputEngine >> intVector2;
  CPPUNIT_ASSERT(intVector == intVector2);

  std::vector<int32>* pIntVector2 = new std::vector<int32>;
  inputEngine >> *pIntVector2;
  CPPUNIT_ASSERT(*pIntVector == *pIntVector2);
  delete pIntVector2;

  inputArchive.close();

  delete pIntVector;
}

void EngineTest::testSTLDeque()
{
  int i;

  // write STL std::deque data
  std::fstream outputArchive("EngineSTLDequeTest.dat", std::ios::out|std::ios::binary);
  Engine outputEngine(outputArchive, ost::Engine::modeWrite);

  std::deque<int32> intDeque;
  std::deque<int32>* pIntDeque = new std::deque<int32>;
  for(i = 0; i < STL_CONTAINER_SIZE; i++)
  {
    intDeque.push_back(i);
    pIntDeque->push_back(i);
  }

  outputEngine << intDeque;
  outputEngine << *pIntDeque;
  outputEngine.sync(); // flush Engine buffers before closing file
  outputArchive.close();

  // read STL std::deque back in and check
  std::fstream inputArchive("EngineSTLDequeTest.dat", std::ios::in|std::ios::binary);
  Engine inputEngine(inputArchive, ost::Engine::modeRead);

  std::deque<int32> intDeque2;
  inputEngine >> intDeque2;
  CPPUNIT_ASSERT(intDeque == intDeque2);

  std::deque<int32>* pIntDeque2 = new std::deque<int32>;
  inputEngine >> *pIntDeque2;
  CPPUNIT_ASSERT(*pIntDeque == *pIntDeque2);
  delete pIntDeque2;

  inputArchive.close();

  delete pIntDeque;
}

void EngineTest::testSTLMap()
{
  int i;

  // write STL std::map data
  std::fstream outputArchive("EngineSTLMapTest.dat", std::ios::out|std::ios::binary);
  Engine outputEngine(outputArchive, ost::Engine::modeWrite);

  std::map<int32, int32> intMap;
  std::map<int32, int32>* pIntMap = new std::map<int32, int32>;
  for(i = 0; i < STL_CONTAINER_SIZE; i++)
  {
    intMap.insert(std::pair<int32, int32>(i, i+10));
    pIntMap->insert(std::pair<int32, int32>(i, i+11));
  }

  outputEngine << intMap;
  outputEngine << *pIntMap;
  outputEngine.sync(); // flush Engine buffers before closing file
  outputArchive.close();

  // read STL std::map back in and check
  std::fstream inputArchive("EngineSTLMapTest.dat", std::ios::in|std::ios::binary);
  Engine inputEngine(inputArchive, ost::Engine::modeRead);

  std::map<int32, int32> intMap2;
  inputEngine >> intMap2;
  CPPUNIT_ASSERT(intMap == intMap2);

  std::map<int32, int32>* pIntMap2 = new std::map<int32, int32>;
  inputEngine >> *pIntMap2;
  CPPUNIT_ASSERT(*pIntMap == *pIntMap2);
  delete pIntMap2;

  inputArchive.close();

  delete pIntMap;
}

void EngineTest::testComplexObject()
{
  // write BaseObject hierarchy
  std::fstream outputArchive("EngineComplexObjectTest.dat", std::ios::out|std::ios::binary);
  Engine outputEngine(outputArchive, ost::Engine::modeWrite);
  outputEngine << complexObject;
  outputEngine.sync(); // flush Engine buffers before closing file
  outputArchive.close();

  // Unpersist a new object structure into an uninitialized object
  {
    Test* myObjPtr = NULL; // must initialize pointer or new persistence engine will think it is already allocated
    std::fstream inputArchive("EngineComplexObjectTest.dat", std::ios::in);
    Engine inputEngine(inputArchive, ost::Engine::modeRead);
    inputEngine >> myObjPtr;
    inputArchive.close();

    CPPUNIT_ASSERT_MESSAGE("Unpersisted into unallocated pointer == Original", *myObjPtr == complexObject);
    CPPUNIT_ASSERT_MESSAGE("nullPtr is NULL", myObjPtr->nullPtr == NULL);
    CPPUNIT_ASSERT_MESSAGE("uninitializedNullPtr is NULL", myObjPtr->uninitializedNullPtr == NULL);

    myObjPtr->subObjectPtr->strString2 = "Changed SubStringPtr 1";
    CPPUNIT_ASSERT_MESSAGE("subObjectPtr.strString2 should always equal subObjectPtr2.strString2", myObjPtr->subObjectPtr->strString2 == myObjPtr->subObjectPtr2->strString2);
    CPPUNIT_ASSERT_MESSAGE("subObjectPtr.strString2 should now equal 'Changed SubStringPtr 1'", myObjPtr->subObjectPtr->strString2 == "Changed SubStringPtr 1");
    CPPUNIT_ASSERT_MESSAGE("subObjectPtr.strString2 should still equal subObjectPtr2.strString2", myObjPtr->subObjectPtr->strString2 == myObjPtr->subObjectPtr2->strString2);
  }

  // Unpersist a new object structure into an instantiated class variable
  {
    Test myObjInstance;
    std::fstream inputArchive("EngineComplexObjectTest.dat", std::ios::in);
    Engine inputEngine(inputArchive, ost::Engine::modeRead);
    inputEngine >> myObjInstance;
    inputArchive.close();

    CPPUNIT_ASSERT_MESSAGE("Unpersisted into default constructed instance == Original", myObjInstance == complexObject);
    CPPUNIT_ASSERT_MESSAGE("nullPtr is NULL", myObjInstance.nullPtr == NULL);
    CPPUNIT_ASSERT_MESSAGE("UninitializedNullPtr is NULL", myObjInstance.uninitializedNullPtr == NULL);
  }

  // Unpersist a new object structure into a pre-initialized pointer to an object
  {
    Test* myObjAllocatedPtr = new Test;
    std::fstream inputArchive("EngineComplexObjectTest.dat", std::ios::in);
    Engine inputEngine(inputArchive, ost::Engine::modeRead);
    inputEngine >> myObjAllocatedPtr;
    outputEngine.sync(); // flush Engine buffers before closing file
    inputArchive.close();

    CPPUNIT_ASSERT_MESSAGE("Unpersisted into pre-allocated pointer", *myObjAllocatedPtr == complexObject);
    CPPUNIT_ASSERT_MESSAGE("nullPtr is NULL", myObjAllocatedPtr->nullPtr == NULL);
    CPPUNIT_ASSERT_MESSAGE("uninitializedNullPtr is NULL", myObjAllocatedPtr->uninitializedNullPtr == NULL);
  }
}

void EngineTest::testModeExceptions()
{
  // write primitive types
  std::fstream outputArchive("EnginePrimitiveTest.dat", std::ios::in|std::ios::binary);
  Engine outputEngine(outputArchive, ost::Engine::modeWrite);

  int32 test_Int32 = 0x01234567;
  try
  {
    outputEngine << test_Int32;
    //CPPUNIT_FAIL("Call to persist to an input stream should throw an exception");
  }
  catch(Engine::Exception ex)
  {    
    CPPUNIT_ASSERT_MESSAGE(ex.getString(), true);
  }

  outputArchive.close();
}
