/*
 * 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 .
 */
/*
 * Filler.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 
#include 
#include "Filler.hpp"
#include "CodePatterns/Assert.hpp"
#include "CodePatterns/Log.hpp"
#include "Atom/atom.hpp"
#include "Box.hpp"
#include "ClusterInterface.hpp"
#include "Descriptors/AtomIdDescriptor.hpp"
#include "Inserter/Inserter.hpp"
#include "LinearAlgebra/RealSpaceMatrix.hpp"
#include "LinearAlgebra/Vector.hpp"
#include "molecule.hpp"
#include "NodeTypes.hpp"
#include "Predicates/FillPredicate.hpp"
#include "Predicates/Ops_FillPredicate.hpp"
#include "World.hpp"
Filler::Filler(const Mesh &_mesh, const FillPredicate &_predicate, const Inserter &_inserter) :
  mesh(_mesh),
  predicate(!_predicate),
  inserter(_inserter)
{}
Filler::~Filler()
{}
bool Filler::operator()(
    CopyAtomsInterface ©Method,
    ClusterInterface::Cluster_impl cluster,
    ClusterVector_t &ClonedClusters) const
{
  const NodeSet &nodes = mesh.getNodes();
  std::stringstream output;
  std::for_each( nodes.begin(), nodes.end(), output << boost::lambda::_1 << " ");
  LOG(3, "DEBUG: Listing nodes to check: " << output.str());
  if (nodes.size() == 0)
    return false;
  NodeSet FillNodes(nodes.size(), zeroVec);
  // evaluate predicates at each FillNode
  {
    // move filler cluster's atoms out of domain such that it does not disturb the predicate.
    // we only move the atoms as otherwise two translate ShapeOps will be on top of the Shape
    // which is subsequently copied to all other cloned Clusters ...
    Vector BoxDiagonal;
    {
      const RealSpaceMatrix &M = World::getInstance().getDomain().getM();
      BoxDiagonal = (M * Vector(1.,1.,1.));
      BoxDiagonal -= cluster->getShape().getCenter();
      BoxDiagonal *= 1. + 2.*cluster->getShape().getRadius()/BoxDiagonal.Norm(); // extend it a little further
      AtomIdSet atoms = cluster->getAtoms();
      for (AtomIdSet::iterator iter = atoms.begin(); iter != atoms.end(); ++iter)
        (*iter)->setPosition( (*iter)->getPosition() + BoxDiagonal );
      LOG(1, "INFO: Translating original cluster's atoms by " << BoxDiagonal << ".");
    }
    // evaluate predicate and gather into new set
    NodeSet::iterator transform_end  =
        std::remove_copy_if(nodes.begin(), nodes.end(), FillNodes.begin(), predicate );
    FillNodes.erase(transform_end, FillNodes.end());
    // shift cluster back to original place
    {
      AtomIdSet atoms = cluster->getAtoms();
      for (AtomIdSet::iterator iter = atoms.begin(); iter != atoms.end(); ++iter)
        (*iter)->setPosition( (*iter)->getPosition() - BoxDiagonal );
      LOG(1, "INFO: Translating original cluster's atoms back.");
    }
  }
  if (FillNodes.size() == 0) {
    ELOG(2, "For none of the nodes did the predicate return true.");
    return false;
  } else {
    LOG(1, "INFO: " << FillNodes.size() << " out of " << nodes.size() << " returned true from predicate.");
  }
  // clone clusters
  ClonedClusters.resize(FillNodes.size());
  {
    std::vector::iterator clusteriter = ClonedClusters.begin();
    *clusteriter = cluster;
    clusteriter++;
    std::generate_n(clusteriter, FillNodes.size()-1,
        boost::bind(&ClusterInterface::clone, boost::cref(cluster), boost::ref(copyMethod), zeroVec) );
  }
  // insert each cluster by abusing std::search a bit:
  {
    // we look for the subsequence of FillNodes inside clusters. If Inserter always
    // returns true, we'll have the iterator pointing at first cluster
    std::vector::const_iterator inserteriter =
      std::search(ClonedClusters.begin(), ClonedClusters.end(), FillNodes.begin(), FillNodes.end(),
          boost::bind(&Inserter::operator(), boost::cref(inserter), _1, _2));
    if( inserteriter != ClonedClusters.begin()) {
      ELOG(1, "Not all cloned clusters could be successfully inserted.");
      return false;
    }
  }
  // create molecules for each cluster and fill in atoms
  {
    std::vector molecules(ClonedClusters.size()-1, NULL);
    std::generate_n(molecules.begin(), FillNodes.size()-1,
        boost::bind(&World::createMolecule, World::getPointer()) );
    std::vector::const_iterator clusteriter = ClonedClusters.begin();
    ++clusteriter;
    std::vector::iterator moliter = molecules.begin();
    for (;moliter != molecules.end(); ++clusteriter, ++moliter) {
      AtomIdSet atoms = (*clusteriter)->getAtoms();
      for (AtomIdSet::iterator iter = atoms.begin(); iter != atoms.end(); ++iter)
        (*moliter)->AddAtom(*iter);
    }
  }
  // give final statment on whether at least \a single cluster has been placed
  return ( FillNodes.size() != 0);
}