/*
 * Project: MoleCuilder
 * Description: creates and alters molecular systems
 * Copyright (C)  2012 University of Bonn. All rights reserved.
 * Copyright (C)  2013 Frederik Heber. 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 .
 */
/*
 * FragmentUnitTest.cpp
 *
 *  Created on: Aug 09, 2012
 *      Author: heber
 */
// include config.h
#ifdef HAVE_CONFIG_H
#include 
#endif
using namespace std;
#include 
#include 
#include 
// include headers that implement a archive in simple text format
#include 
#include 
#include "FragmentUnitTest.hpp"
#include 
#include 
#include 
#include 
#include 
#include "CodePatterns/Assert.hpp"
#ifdef HAVE_TESTRUNNER
#include "UnitTestMain.hpp"
#endif /*HAVE_TESTRUNNER*/
using namespace boost::assign;
/********************************************** Test classes **************************************/
// Registers the fixture into the 'registry'
CPPUNIT_TEST_SUITE_REGISTRATION( FragmentTest );
void FragmentTest::setUp()
{
  // failing asserts should be thrown
  ASSERT_DO(Assert::Throw);
  Fragment::position_t pos(3,0.);
  positions += pos;
  pos[0] = 1.;
  positions += pos;
  pos[1] = 1.;
  positions += pos;
  pos[2] = 1.;
  positions += pos;
  atomicnumbers += 1, 2, 3, 4;
  charges += 1., 2., 3., 4.;
  CPPUNIT_ASSERT_EQUAL( (size_t)3, pos.size() );
  CPPUNIT_ASSERT( positions.size() == charges.size() );
  CPPUNIT_ASSERT( atomicnumbers.size() == charges.size() );
  fragment = new Fragment(positions, atomicnumbers, charges);
}
void FragmentTest::tearDown()
{
  delete fragment;
}
/** UnitTest for isPositionEqual()
 */
void FragmentTest::isPositionEqual_Test()
{
  // same position
  for (Fragment::positions_t::const_iterator iter = positions.begin();
      iter != positions.end(); ++iter)
    CPPUNIT_ASSERT( Fragment::isPositionEqual(*iter, *iter) );
  // other position
  Fragment::position_t unequalpos(3,2.);
  for (Fragment::positions_t::const_iterator iter = positions.begin();
      iter != positions.end(); ++iter)
    CPPUNIT_ASSERT( !Fragment::isPositionEqual(*iter, unequalpos) );
}
static Fragment::nuclei_t createNucleiFromTriple(
    const Fragment::positions_t &_positions,
    const Fragment::atomicnumbers_t &_atomicnumbers,
    const Fragment::charges_t &_charges
    )
{
  Fragment::nuclei_t returnnuclei;
  Fragment::positions_t::const_iterator piter = _positions.begin();
  Fragment::atomicnumbers_t::const_iterator aiter = _atomicnumbers.begin();
  Fragment::charges_t::const_iterator citer = _charges.begin();
  for (;piter != _positions.end(); ++piter)
    returnnuclei.push_back( Fragment::createNucleus(*piter, *aiter, *citer) );
  return returnnuclei;
}
/** UnitTest for containsNuclei()
 */
void FragmentTest::containsNuclei_Test()
{
  {
    // tests present ones for containment
    Fragment::nuclei_t validnuclei( createNucleiFromTriple(positions, atomicnumbers, charges) );
    BOOST_FOREACH( Fragment::nucleus_t nucleus, validnuclei) {
      CPPUNIT_ASSERT( fragment->containsNuclei(nucleus) );
    }
  }
  {
    // test some others
    Fragment::nuclei_t invalidnuclei;
    Fragment::position_t pos(3, -1.);
    invalidnuclei += Fragment::createNucleus(pos, 1, 1.);
    pos[0] = 0.;
    invalidnuclei += Fragment::createNucleus(pos, 1, 1.);
    pos[1] = 0.;
    invalidnuclei += Fragment::createNucleus(pos, 1, 1.);
    pos[2] = -std::numeric_limits::epsilon()*1e+4;
    invalidnuclei += Fragment::createNucleus(pos, 1, 1.);
    BOOST_FOREACH( Fragment::nucleus_t nucleus, invalidnuclei) {
      CPPUNIT_ASSERT( !fragment->containsNuclei(nucleus) );
    }
  }
}
/** UnitTest for removeNuclei()
 */
void FragmentTest::removeNuclei_Test()
{
  {
    // tests present ones for removal
    size_t size = fragment->nuclei.size();
    Fragment::nuclei_t validnuclei( createNucleiFromTriple(positions, atomicnumbers, charges) );
    BOOST_FOREACH( Fragment::nucleus_t nucleus, validnuclei) {
      CPPUNIT_ASSERT_NO_THROW( fragment->removeNuclei(nucleus) );
      CPPUNIT_ASSERT_EQUAL( --size, fragment->nuclei.size() );
    }
  }
  {
    // test some others
    Fragment::nuclei_t invalidnuclei;
    Fragment::position_t pos(3, -1.);
    invalidnuclei += Fragment::createNucleus(pos, 1, 1.);
    pos[0] = 0;
    invalidnuclei += Fragment::createNucleus(pos, 1, 1.);
    pos[1] = 0;
    invalidnuclei += Fragment::createNucleus(pos, 1, 1.);
    pos[2] = -std::numeric_limits::epsilon()*1e+4;
    invalidnuclei += Fragment::createNucleus(pos, 1, 1.);
    BOOST_FOREACH( Fragment::nucleus_t nucleus, invalidnuclei) {
      CPPUNIT_ASSERT_NO_THROW( fragment->removeNuclei(nucleus) );
    }
  }
}
/** UnitTest for operator==() for Fragment::nucleus_t
 */
void FragmentTest::equalityNucleus_Test()
{
  Fragment::nuclei_t validnuclei( createNucleiFromTriple(positions, atomicnumbers, charges) );
  {
    // create some nuclei
    BOOST_FOREACH( Fragment::nucleus_t nucleus, validnuclei) {
      CPPUNIT_ASSERT( nucleus == nucleus );
    }
  }
  {
    // create nucleus at other position
    Fragment::position_t pos(3, 2.);
    Fragment::nucleus_t unequalposnucleus( Fragment::createNucleus(pos, 1, 1.) );
    BOOST_FOREACH( Fragment::nucleus_t nucleus, validnuclei) {
      CPPUNIT_ASSERT( nucleus != unequalposnucleus );
    }
  }
  {
    // create nucleus with different charge
    Fragment::position_t pos(3, 1.);
    Fragment::nucleus_t unequalchargenucleus( Fragment::createNucleus(pos, 5, 5.) );
    BOOST_FOREACH( Fragment::nucleus_t nucleus, validnuclei) {
      CPPUNIT_ASSERT( nucleus != unequalchargenucleus );
    }
  }
}
/** UnitTest for operator==()
 */
void FragmentTest::equality_Test()
{
  // create different fragment
  Fragment::positions_t otherpositions;
  Fragment::position_t otherpos(3, 2.);
  otherpositions += otherpos;
  otherpos[0] = 0.;
  otherpositions += otherpos;
  otherpos[1] = 0.;
  otherpositions += otherpos;
  Fragment::atomicnumbers_t otheratomicnumbers;
  otheratomicnumbers += 1, 2, 3;
  Fragment::charges_t othercharges;
  othercharges += 1., 2., 3.;
  Fragment otherfragment(otherpositions, otheratomicnumbers, othercharges);
  CPPUNIT_ASSERT( !(*fragment == otherfragment) );
  CPPUNIT_ASSERT( *fragment != otherfragment );
  // test against empty fragment
  Fragment emptyfragment;
  CPPUNIT_ASSERT( !(*fragment == emptyfragment) );
  CPPUNIT_ASSERT( *fragment != emptyfragment );
  // tests against themselves
  CPPUNIT_ASSERT( *fragment == *fragment );
  CPPUNIT_ASSERT( otherfragment == otherfragment );
  CPPUNIT_ASSERT( emptyfragment == emptyfragment );
  // check against ZeroInstance
  CPPUNIT_ASSERT( *fragment != ZeroInstance() );
  CPPUNIT_ASSERT( otherfragment != ZeroInstance() );
}
/** UnitTest for operator+=()
 */
void FragmentTest::assignment_Test()
{
  // create different fragment
  Fragment::positions_t otherpositions;
  Fragment::position_t otherpos(3, 2.);
  otherpositions += otherpos;
  otherpos[0] = 0.;
  otherpositions += otherpos;
  otherpos[1] = 0.;
  otherpositions += otherpos;
  Fragment::atomicnumbers_t otheratomicnumbers;
  otheratomicnumbers += 1, 2, 3;
  Fragment::charges_t othercharges;
  othercharges += 1., 2., 3.;
  Fragment otherfragment(otherpositions, otheratomicnumbers, othercharges);
  // check for inequality
  CPPUNIT_ASSERT( otherfragment.nuclei.size() != fragment->nuclei.size() );
  CPPUNIT_ASSERT( otherfragment != *fragment );
  //assign
  otherfragment = *fragment;
  // check for equality
  CPPUNIT_ASSERT( otherfragment.nuclei.size() == fragment->nuclei.size() );
  CPPUNIT_ASSERT( otherfragment == *fragment );
}
/** UnitTest for operator+=()
 */
void FragmentTest::operatorPlusEqual_NonOverlapping_Test()
{
  {
    // create non-overlapping set
    Fragment::positions_t otherpositions;
    Fragment::position_t otherpos(3, 2.);
    otherpositions += otherpos;
    otherpos[0] = 0.;
    otherpositions += otherpos;
    otherpos[1] = 0.;
    otherpositions += otherpos;
    Fragment::atomicnumbers_t otheratomicnumbers;
    otheratomicnumbers += 1, 2, 3;
    Fragment::charges_t othercharges;
    othercharges += 1., 2., 3.;
    Fragment otherfragment(otherpositions, otheratomicnumbers, othercharges);
    const size_t othersize = otherfragment.nuclei.size();
    const size_t size = fragment->nuclei.size();
    *fragment += otherfragment;
    CPPUNIT_ASSERT_EQUAL( othersize, otherfragment.nuclei.size() );
    CPPUNIT_ASSERT_EQUAL( size+othersize, fragment->nuclei.size() );
    {
      // tests all for containment
      Fragment::nuclei_t validnuclei( createNucleiFromTriple(positions, atomicnumbers, charges) );
      Fragment::nuclei_t othervalidnuclei( createNucleiFromTriple(otherpositions, otheratomicnumbers, othercharges) );
      validnuclei.insert(validnuclei.end(), othervalidnuclei.begin(), othervalidnuclei.end());
      BOOST_FOREACH( Fragment::nucleus_t nucleus, validnuclei) {
        CPPUNIT_ASSERT( fragment->containsNuclei(nucleus) );
      }
    }
    {
      // tests positions for no containment in otherfragment
      Fragment::nuclei_t invalidnuclei( createNucleiFromTriple(positions, atomicnumbers, charges) );
      BOOST_FOREACH( Fragment::nucleus_t nucleus, invalidnuclei) {
        CPPUNIT_ASSERT( !otherfragment.containsNuclei(nucleus) );
      }
    }
    {
      // tests otherpositions for containment in otherfragment
      Fragment::nuclei_t validnuclei( createNucleiFromTriple(otherpositions, otheratomicnumbers, othercharges) );
      BOOST_FOREACH( Fragment::nucleus_t nucleus, validnuclei) {
        CPPUNIT_ASSERT( otherfragment.containsNuclei(nucleus) );
      }
    }
  }
}
/** UnitTest for operator+=()
 */
void FragmentTest::operatorPlusEqual_Test()
{
  {
    // create overlapping set (first overlaps)
    Fragment::positions_t otherpositions;
    Fragment::position_t otherpos(3, 1.);
    otherpositions += otherpos;
    otherpos[0] = 2.;
    otherpositions += otherpos;
    otherpos[1] = 2.;
    otherpositions += otherpos;
    Fragment::atomicnumbers_t otheratomicnumbers;
    otheratomicnumbers += 1, 2, 3;
    Fragment::charges_t othercharges;
    othercharges += 1., 2., 3.;
    Fragment otherfragment(otherpositions, otheratomicnumbers, othercharges);
    const size_t othersize = otherfragment.nuclei.size();
    const size_t size = fragment->nuclei.size();
    *fragment += otherfragment;
    CPPUNIT_ASSERT_EQUAL( othersize, otherfragment.nuclei.size() );
    CPPUNIT_ASSERT_EQUAL( size+othersize-1, fragment->nuclei.size() ); // one for already present
    {
      // tests all for containment
      Fragment::nuclei_t validnuclei( createNucleiFromTriple(positions, atomicnumbers, charges) );
      Fragment::nuclei_t othervalidnuclei( createNucleiFromTriple(otherpositions, otheratomicnumbers, othercharges) );
      validnuclei.insert(validnuclei.end(), othervalidnuclei.begin(), othervalidnuclei.end());
      BOOST_FOREACH( Fragment::nucleus_t nucleus, validnuclei) {
        CPPUNIT_ASSERT( fragment->containsNuclei(nucleus) );
      }
    }
    {
      // tests positions for no containment in otherfragment (but last)
      Fragment::positions_t lesspositions(positions.begin(), --positions.end());
      Fragment::atomicnumbers_t lessatomicnumbers(atomicnumbers.begin(), --atomicnumbers.end());
      Fragment::charges_t lesscharges(charges.begin(), --charges.end());
      Fragment::nuclei_t invalidnuclei( createNucleiFromTriple(lesspositions, lessatomicnumbers, lesscharges) );
      BOOST_FOREACH( Fragment::nucleus_t nucleus, invalidnuclei) {
        CPPUNIT_ASSERT( !otherfragment.containsNuclei(nucleus) );
      }
    }
    {
      // tests otherpositions for containment in otherfragment
      Fragment::nuclei_t validnuclei( createNucleiFromTriple(otherpositions, otheratomicnumbers, othercharges) );
      BOOST_FOREACH( Fragment::nucleus_t nucleus, validnuclei) {
        CPPUNIT_ASSERT( otherfragment.containsNuclei(nucleus) );
      }
    }
  }
}
/** UnitTest for operator-=()
 */
void FragmentTest::operatorMinusEqual_NonOverlapping_Test()
{
  {
    // create non-overlapping set
    Fragment::positions_t otherpositions;
    Fragment::position_t otherpos(3, 2.);
    otherpositions += otherpos;
    otherpos[0] = 0.;
    otherpositions += otherpos;
    otherpos[1] = 0.;
    otherpositions += otherpos;
    Fragment::atomicnumbers_t otheratomicnumbers;
    otheratomicnumbers += 1, 2, 3;
    Fragment::charges_t othercharges;
    othercharges += 1., 2., 3.;
    Fragment otherfragment(otherpositions, otheratomicnumbers, othercharges);
    const size_t othersize = otherfragment.nuclei.size();
    const size_t size = fragment->nuclei.size();
    *fragment -= otherfragment;
    CPPUNIT_ASSERT_EQUAL( othersize, otherfragment.nuclei.size() );
    CPPUNIT_ASSERT_EQUAL( size, fragment->nuclei.size() );
    {
      // tests positions for containment
      Fragment::nuclei_t validnuclei( createNucleiFromTriple(positions, atomicnumbers, charges) );
      BOOST_FOREACH( Fragment::nucleus_t nucleus, validnuclei) {
        CPPUNIT_ASSERT( fragment->containsNuclei(nucleus) );
      }
    }
    {
      // tests otherpositions for no containment
      Fragment::nuclei_t invalidnuclei( createNucleiFromTriple(otherpositions, otheratomicnumbers, othercharges) );
      BOOST_FOREACH( Fragment::nucleus_t nucleus, invalidnuclei) {
        CPPUNIT_ASSERT( !fragment->containsNuclei(nucleus) );
      }
    }
    {
      // tests positions for no containment in otherfragment
      Fragment::nuclei_t invalidnuclei( createNucleiFromTriple(positions, atomicnumbers, charges) );
      BOOST_FOREACH( Fragment::nucleus_t nucleus, invalidnuclei) {
        CPPUNIT_ASSERT( !otherfragment.containsNuclei(nucleus) );
      }
    }
    {
      // tests otherpositions for containment in otherfragment
      Fragment::nuclei_t validnuclei( createNucleiFromTriple(otherpositions, otheratomicnumbers, othercharges) );
      BOOST_FOREACH( Fragment::nucleus_t nucleus, validnuclei) {
        CPPUNIT_ASSERT( otherfragment.containsNuclei(nucleus) );
      }
    }
  }
}
/** UnitTest for operator-=()
 */
void FragmentTest::operatorMinusEqual_Test()
{
  {
    // create overlapping set (first overlaps although with different charge)
    Fragment::positions_t otherpositions;
    Fragment::position_t otherpos(3, 1.);
    otherpositions += otherpos;
    otherpos[0] = 2.;
    otherpositions += otherpos;
    otherpos[1] = 2.;
    otherpositions += otherpos;
    Fragment::atomicnumbers_t otheratomicnumbers;
    otheratomicnumbers += 1, 2, 3;
    Fragment::charges_t othercharges;
    othercharges += 1., 2., 3.;
    Fragment otherfragment(otherpositions, otheratomicnumbers, othercharges);
    const size_t othersize = otherfragment.nuclei.size();
    const size_t size = fragment->nuclei.size();
    CPPUNIT_ASSERT( Fragment::isPositionEqual(otherpositions[0],positions[3]) );
    *fragment -= otherfragment;
    CPPUNIT_ASSERT_EQUAL( othersize, otherfragment.nuclei.size() );
    CPPUNIT_ASSERT_EQUAL( size-1, fragment->nuclei.size() ); // just one overlaps
    {
      // tests all but last for containment
      Fragment::nuclei_t validnuclei( createNucleiFromTriple(positions, atomicnumbers, charges) );
      BOOST_FOREACH( Fragment::nucleus_t nucleus, validnuclei) {
        if (Fragment::isPositionEqual(nucleus.first, otherpositions[0])) // only test position
          CPPUNIT_ASSERT( !fragment->containsNuclei(nucleus) );
        else
          CPPUNIT_ASSERT( fragment->containsNuclei(nucleus) );
      }
    }
    {
      // tests positions for no containment in otherfragment
      Fragment::positions_t lesspositions(positions.begin(), --positions.end());
      Fragment::atomicnumbers_t lessatomicnumbers(atomicnumbers.begin(), --atomicnumbers.end());
      Fragment::charges_t lesscharges(charges.begin(), --charges.end());
      Fragment::nuclei_t invalidnuclei( createNucleiFromTriple(lesspositions, lessatomicnumbers, lesscharges) );
      BOOST_FOREACH( Fragment::nucleus_t nucleus, invalidnuclei) {
        CPPUNIT_ASSERT( !otherfragment.containsNuclei(nucleus) );
      }
    }
    {
      // tests otherpositions for containment in otherfragment
      Fragment::nuclei_t validnuclei( createNucleiFromTriple(otherpositions, otheratomicnumbers, othercharges) );
      BOOST_FOREACH( Fragment::nucleus_t nucleus, validnuclei) {
        CPPUNIT_ASSERT( otherfragment.containsNuclei(nucleus) );
      }
    }
  }
}
/** UnitTest for serialization
 */
void FragmentTest::serializeTest()
{
  // serialize
  std::stringstream outputstream;
  boost::archive::text_oarchive oa(outputstream);
  oa << fragment;
  // deserialize
  Fragment *samefragment = NULL;
  std::stringstream returnstream(outputstream.str());
  boost::archive::text_iarchive ia(returnstream);
  ia >> samefragment;
  CPPUNIT_ASSERT( samefragment != NULL );
  CPPUNIT_ASSERT( *fragment == *samefragment );
  delete samefragment;
}