/*
 * 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 .
 */
/*
 * Cluster.cpp
 *
 *  Created on: Jan 16, 2012
 *      Author: heber
 */
// include config.h
#ifdef HAVE_CONFIG_H
#include 
#endif
//#include "CodePatterns/MemDebug.hpp"
#include 
#include 
#include 
#include "Cluster.hpp"
#include "CodePatterns/Assert.hpp"
#include "CodePatterns/Log.hpp"
#include "Atom/atom.hpp"
#include "Descriptors/AtomIdDescriptor.hpp"
#include "LinearAlgebra/RealSpaceMatrix.hpp"
#include "LinearAlgebra/Vector.hpp"
#include "Shapes/ShapeOps.hpp"
#include "World.hpp"
/** Constructor for class Cluster.
 *
 * @param _s Shape of this Cluster
 */
Cluster::Cluster(const Shape & _s) :
    s(_s)
{}
/** Copy Constructor for class Cluster.
 *
 * Here, we do not check whether we atomds reside in the Shape or not, as
 * this should have been validated in the instance to copy \a _cluster.
 *
 * @param _cluster instance to copy
 */
Cluster::Cluster(const Cluster & _cluster) :
    atoms(_cluster.atoms),
    s(_cluster.s)
{}
/** Constructor for class Cluster.
 *
 * @param _atoms list of atoms to place in this cluster
 * @param _s Shape of this Cluster
 */
Cluster::Cluster(const atomIdSet & _atoms, const Shape & _s) :
    s(_s)
{
  // make sure only those atoms are in Cluster that are also inside its Shape
  std::vector tempIds(_atoms.size(), (size_t)-1);
  std::vector::iterator iter =
      std::remove_copy_if( _atoms.begin(), _atoms.end(), tempIds.begin(),
          !boost::bind(&Cluster::IsInShape, this, _1) );
  tempIds.erase( iter, tempIds.end() );
  ASSERT( tempIds.size() == _atoms.size(),
      "Cluster::Cluster() - at least one atom is not inside the Shape.");
  atoms.insert( tempIds.begin(), tempIds.end() );
  LOG(1, "INFO: New cluster has " << atoms.size() << " atoms.");
}
/** Destructor for class Cluster.
 *
 */
Cluster::~Cluster()
{}
/** Inserts an atomic by its \a id into the Cluster.
 *
 * We check whether the atom is inside the given Shape \a s.
 *
 * @param id id to insert
 */
void Cluster::insert(const atomId_t id)
{
  const bool status = IsInShape(id);
  ASSERT(status,
      "Cluster::insert() - atomic id "+toString(id)+" is not contained in Shape.");
  if (status) {
#ifndef NDEBUG
    std::pair inserter = 
#endif
	atoms.insert(id);
    ASSERT(inserter.second,
        "Cluster::insert() - atomic id "+toString(id)+" is already present.");
  }
}
/** Remove atom by its \a id from the cluster.
 *
 * @param id atom to remove
 */
void Cluster::erase(const atomId_t id)
{
  atomIdSet::iterator iter = atoms.find(id);
  ASSERT(iter != atoms.end(),
      "Cluster::erase() - atomic id "+toString(id)+" unknown in this Cluster.");
  if (iter != atoms.end())
    atoms.erase(iter);
}
/** Checks whether a given atom is within the shape \a s.
 *
 * @param id atomic id to check
 * @return true - is in Shape, false - is not contained (or does not exist)
 */
bool Cluster::IsInShape(const atomId_t id) const
{
  const atom * const _atom = getAtomById(id);
  if (_atom != NULL)
    return s.isInside(_atom->getPosition());
  else
    return false;
}
/** Helper function for looking up atomic reference by its id.
 *
 * @param id id to look up
 * @return reference to atom with this id
 */
atom * const Cluster::getAtomById(const atomId_t id) const
{
  atom * const _atom = World::getInstance().getAtom(AtomById(id));
  ASSERT(_atom != NULL,
      "Cluster::getAtomById() - id "+toString(id)+" is unknown to World.");
  return _atom;
}
bool isNullAtom(const atom* _atom) {
  return _atom == NULL;
}
/** Getter for the underlying true atoms refs.
 *
 * @return AtomVector filled with looked-up atom references
 */
Cluster::AtomVector Cluster::getAtomRefs() const
{
  AtomVector atomVector;
  atomVector.reserve(atoms.size());
  BOOST_FOREACH(atomId_t _id, atoms) {
    atom * const _atom = World::getInstance().getAtom(AtomById(_id));
    if (_atom != NULL)
      atomVector.push_back( _atom );
    else
      ASSERT( false, "Cluster::getAtomRefs() - unknown id "+toString(_id)+".");
  }
  return atomVector;
}
/** Clone function for this instance.
 *
 * @param copyMethod functor that knows how to copy atoms
 * @param offset Vector to translate new cluster relative to old one
 * @return another instance with newly allocated atoms
 */
ClusterInterface::Cluster_impl Cluster::clone(
    CopyAtomsInterface& copyMethod,
    const Vector &offset) const
{
  LOG(2, "INFO: Clone this cluster with " << atoms.size() << " atoms.");
  /// get another cluster instance
  Cluster * clonedInstance = new Cluster(::translate(getShape(), offset));
  /// copy and move atoms
  copyMethod(getAtomRefs());
  AtomVector CopiedAtoms = copyMethod.getCopiedAtoms();
  BOOST_FOREACH( atom *_atom, CopiedAtoms) {
    _atom->setPosition( _atom->getPosition() + offset );
  }
  /// fill copied atoms into new instance
  // dont use a set here, makes life hard with STL algos
  std::vector Copies(CopiedAtoms.size(), (size_t)-1);
  std::transform(CopiedAtoms.begin(), CopiedAtoms.end(), Copies.begin(),
      boost::bind(&atom::getId, _1) );
  clonedInstance->atoms.insert(Copies.begin(), Copies.end());
  return ClusterInterface::Cluster_impl(clonedInstance);
}
/** Translate atoms inside Cluster and Shape.
 *
 * @param offset offset to translate by
 */
void Cluster::translate(const Vector &offset)
{
  // move atoms
  AtomVector atomVector = getAtomRefs();
  BOOST_FOREACH(atom *_atom, atomVector) {
    _atom->setPosition(_atom->getPosition()+offset);
  }
  // translate shape
  s = ::translate(s, offset);
}
/** Transform atoms inside Cluster and Shape.
 *
 * @param M transformation matrix
 */
void Cluster::transform(const RealSpaceMatrix &M)
{
  // transform atoms
  AtomVector atomVector = getAtomRefs();
  BOOST_FOREACH(atom *_atom, atomVector) {
    _atom->setPosition( M * _atom->getPosition() );
  }
  // translate shape
  s = ::transform(s, M);
}