/*
 * Project: MoleCuilder
 * Description: creates and alters molecular systems
 * Copyright (C)  2012 University of Bonn. All rights reserved.
 * 
 *
 *   This file is part of MoleCuilder.
 *
 *    MoleCuilder 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.
 *
 *    MoleCuilder 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 MoleCuilder.  If not, see .
 */
/*
 * LinkedCell_ModelUnitTest.cpp
 *
 *  Created on: Nov 17, 2011
 *      Author: heber
 */
// include config.h
#ifdef HAVE_CONFIG_H
#include 
#endif
using namespace std;
#include 
#include 
#include 
#include 
#include "Box.hpp"
#include "CodePatterns/Assert.hpp"
#include "CodePatterns/Log.hpp"
#include "LinearAlgebra/RealSpaceMatrix.hpp"
#include "LinkedCell/LinkedCell.hpp"
#include "LinkedCell/LinkedCell_Model.hpp"
#include "LinkedCell/LinkedCell_Model_changeModel.hpp"
#include "LinkedCell/LinkedCell_Model_LinkedCellArrayCache.hpp"
#include "LinkedCell/LinkedCell_Model_Update.hpp"
#include "LinkedCell/PointCloudAdaptor.hpp"
#include "LinkedCell/unittests/defs.hpp"
#include "World.hpp"
#include "WorldTime.hpp"
#include "LinkedCell_ModelUnitTest.hpp"
#ifdef HAVE_TESTRUNNER
#include "UnitTestMain.hpp"
#endif /*HAVE_TESTRUNNER*/
/********************************************** Test classes **************************************/
// Registers the fixture into the 'registry'
CPPUNIT_TEST_SUITE_REGISTRATION( LinkedCell_ModelTest );
void LinkedCell_ModelTest::setUp()
{
  // failing asserts should be thrown
  ASSERT_DO(Assert::Throw);
  setVerbosity(3);
  // create diag(20.) matrix
  RealSpaceMatrix BoxM;
  BoxM.setIdentity();
  BoxM *= DOMAINLENGTH;
  // create Box with this matrix
  domain = new Box(BoxM);
  // create LinkedCell structure with this Box
  LC = new LinkedCell::LinkedCell_Model(EDGELENGTH, *domain);
  // create a list of nodes and add to LCImpl
  std::vector< Vector > VectorList;
  for (size_t i=0;i<((size_t)floor(NUMBERCELLS));++i)
    VectorList.push_back(Vector((double)i*EDGELENGTH,(double)i*EDGELENGTH,(double)i*EDGELENGTH));
  CPPUNIT_ASSERT_EQUAL( ((size_t)floor(NUMBERCELLS)), VectorList.size() );
  for (size_t i=0;isetName(std::string("Walker")+toString(i));
    Walker->setPosition(VectorList[i]);
    NodeList.insert(Walker);
  }
  CPPUNIT_ASSERT_EQUAL( VectorList.size(), NodeList.size() );
}
void LinkedCell_ModelTest::tearDown()
{
  delete LC;
  delete domain;
  // remove all nodes again
  for (PointSet::iterator iter = NodeList.begin();
      !NodeList.empty();
      iter = NodeList.begin()) {
    delete *iter;
    NodeList.erase(iter);
  }
  // delete in correct order
  World::purgeInstance();
  WorldTime::purgeInstance();
}
/** UnitTest for correct construction
 */
void LinkedCell_ModelTest::AllocationTest()
{
  // check that first cell is allocated
  LinkedCell::tripleIndex index;
  index[0] = index[1] = index[2] = 0;
  CPPUNIT_ASSERT(LC->N->getN()(index) != NULL);
  // check that very last cell is allocated
  index[0] = index[1] = index[2] = (size_t)floor(NUMBERCELLS)-1;
  CPPUNIT_ASSERT(LC->N->getN()(index) != NULL);
}
/** UnitTest for getSize()
 */
void LinkedCell_ModelTest::getSizeTest()
{
  // check getSize()
  for(size_t i=0; igetSize(i));
#ifndef NDEBUG
  std::cout << "The following assertion is intended and is not a failure of the code." << std::endl;
  CPPUNIT_ASSERT_THROW( LC->getSize(4), Assert::AssertionFailure);
#endif
}
/** UnitTest for Reset()
 */
void LinkedCell_ModelTest::ResetTest()
{
  LC->Reset();
  for(size_t i=0; igetSize(i));
}
/** UnitTest for insertPointCloud()
 */
void LinkedCell_ModelTest::insertPointCloudTest()
{
  // create the linked cell structure
  PointCloudAdaptor< PointSet > cloud(&NodeList, std::string("NodeList"));
  LC->insertPointCloud(cloud);
  // assure that we are updated before accessing internals
  CPPUNIT_ASSERT_EQUAL( true, *(LC->N->UpToDate) );
  // test structure
  CPPUNIT_ASSERT_EQUAL(((size_t)floor(NUMBERCELLS)), LC->CellLookup.size());
  LinkedCell::tripleIndex index;
  for (size_t i=0;i<((size_t)floor(NUMBERCELLS));++i) {
    index[0] = index[1] = index[2] = i;
    // assert that in the destined cell we have one Walker
    CPPUNIT_ASSERT_EQUAL((size_t)1, LC->N->getN()(index)->size());
  }
  index[0] = 9;
  index[1] = index[2] = 0;
  // assert that in the destined cell we have one Walker
  CPPUNIT_ASSERT_EQUAL((size_t)0, LC->N->getN()(index)->size());
}
/** UnitTest for setPartition()
 */
void LinkedCell_ModelTest::setPartitionTest()
{
  RealSpaceMatrix Pmatrix = LC->Partition;
  RealSpaceMatrix Dmatrix = LC->Dimensions;
  LC->Reset();
  LC->setPartition(2.*EDGELENGTH);
  Pmatrix *= 0.5;
  Dmatrix *= 0.5;
  CPPUNIT_ASSERT_EQUAL(Pmatrix, LC->Partition);
  CPPUNIT_ASSERT_EQUAL(Dmatrix, LC->Dimensions);
}
/** UnitTest for getStep()
 */
void LinkedCell_ModelTest::getStepTest()
{
  // zero distance
  LinkedCell::tripleIndex index = LC->getStep(0.);
  for (size_t i = 0; igetStep(length);
    for (size_t i = 0; igetStep(length);
    for (size_t i = 0; igetIndexToVector(test);
    for (size_t i = 0; igetIndexToVector(test);
    for (size_t i = 0; igetIndexToVector(test);
    for (size_t i = 0; isetName(std::string("Walker9"));
  Walker->setPosition(Vector(9.8,7.6,5.4));
  PointCloudAdaptor< PointSet > cloud(&NodeList, std::string("NodeList"));
  LC->insertPointCloud(cloud);
  // check addNode
  {
    LC->addNode(Walker);
    // assure that we are updated before accessing internals
    CPPUNIT_ASSERT_EQUAL( true, *(LC->N->UpToDate) );
    CPPUNIT_ASSERT_EQUAL( NodeList.size()+1, LC->CellLookup.size());
    LinkedCell::tripleIndex index1 = LC->getIndexToVector(Walker->getPosition());
    const LinkedCell::tripleIndex &index2 = LC->CellLookup[Walker]->getIndices();
    CPPUNIT_ASSERT(index1 == index2);
  }
  // check moveNode
  {
    LinkedCell::tripleIndex index1 = LC->getIndexToVector(Walker->getPosition());
    const LinkedCell::tripleIndex &index2 = LC->CellLookup[Walker]->getIndices();
    Walker->setPosition(Vector(0.,0.,0.));
    LinkedCell::tripleIndex newindex1 = LC->getIndexToVector(Walker->getPosition());
    CPPUNIT_ASSERT( index1 != newindex1);
    // we have to call moveNode ourselves, as we have just added TesselPoints, not via World
    LC->moveNode(Walker);
    // assure that we are updated before accessing internals
    CPPUNIT_ASSERT_EQUAL( true, *(LC->N->UpToDate) );
    const LinkedCell::tripleIndex &newindex2 = LC->CellLookup[Walker]->getIndices();
    CPPUNIT_ASSERT( index2 != newindex2);
  }
  // check deleteNode
  {
    LC->deleteNode(Walker);
    // assure that we are updated before accessing internals
    CPPUNIT_ASSERT_EQUAL( true, *(LC->N->UpToDate) );
    CPPUNIT_ASSERT_EQUAL( NodeList.size(), LC->CellLookup.size());
  }
  delete Walker;
}
/** UnitTest whether lazy updates are working.
 */
void LinkedCell_ModelTest::lazyUpdatesTest()
{
  // create point
  TesselPoint * Walker = new TesselPoint();
  Walker->setName(std::string("Walker9"));
  Walker->setPosition(Vector(9.8,7.6,5.4));
  TesselPoint * Walker2 = new TesselPoint();
  Walker2->setName(std::string("Walker10"));
  Walker2->setPosition(Vector(9.8,7.6,5.4));
  PointCloudAdaptor< PointSet > cloud(&NodeList, std::string("NodeList"));
  LC->insertPointCloud(cloud);
  // initial read operation
  {
    CPPUNIT_ASSERT( !NodeList.empty() );
    LinkedCell::tripleIndex index = LC->getIndexToVector((*NodeList.begin())->getPosition());
    const size_t size = LC->N->getN()(index)->size(); // this will cause the update
    CPPUNIT_ASSERT( size >= (size_t)1 );
  }
  // do some write ops
  LC->addNode(Walker);
  LC->addNode(Walker2);
  CPPUNIT_ASSERT( LC->Changes->queue.find(Walker) != LC->Changes->queue.end() );
  LinkedCell::LinkedCell_Model::Update *update = LC->Changes->queue[Walker];
  Walker->setPosition(Vector(0.,0.,0.));
  LC->moveNode(Walker);
  // check that they all reside in cache and nothing is changed so far
  CPPUNIT_ASSERT( LC->Changes->queue.size() > (size_t)0 );
  // check that add priority is prefered over move
  CPPUNIT_ASSERT( LC->Changes->queue[Walker] == update );
  // perform read op
  {
    LinkedCell::tripleIndex index = LC->getIndexToVector(Walker->getPosition());
    CPPUNIT_ASSERT( LC->N->getN()(index)->size() >= (size_t)1 );
  }
  // check that cache is emptied
  CPPUNIT_ASSERT_EQUAL( LC->Changes->queue.size(), (size_t)0 );
  delete Walker;
  delete Walker2;
}