/*
 * 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_Model.cpp
 *
 *  Created on: Nov 15, 2011
 *      Author: heber
 */
// include config.h
#ifdef HAVE_CONFIG_H
#include 
#endif
//#include "CodePatterns/MemDebug.hpp"
#include "LinkedCell_Model.hpp"
#include 
#include 
#include 
#include 
#include "Atom/AtomObserver.hpp"
#include "Atom/TesselPoint.hpp"
#include "Box.hpp"
#include "CodePatterns/Assert.hpp"
#include "CodePatterns/Info.hpp"
#include "CodePatterns/Log.hpp"
#include "CodePatterns/Observer/Observer.hpp"
#include "CodePatterns/Observer/Notification.hpp"
#include "CodePatterns/toString.hpp"
#include "LinearAlgebra/RealSpaceMatrix.hpp"
#include "LinearAlgebra/Vector.hpp"
#include "LinkedCell/IPointCloud.hpp"
#include "LinkedCell/LinkedCell.hpp"
#include "LinkedCell/LinkedCell_Model_changeModel.hpp"
#include "LinkedCell/LinkedCell_Model_LinkedCellArrayCache.hpp"
#include "World.hpp"
// initialize static entities
LinkedCell::tripleIndex LinkedCell::LinkedCell_Model::NearestNeighbors;
/** Constructor of LinkedCell_Model.
 *
 * @param radius desired maximum neighborhood distance
 * @param _domain Box instance with domain size and boundary conditions
 */
LinkedCell::LinkedCell_Model::LinkedCell_Model(const double radius, const Box &_domain) :
    ::Observer(std::string("LinkedCell_Model")+std::string("_")+toString(radius)),
    Changes( new changeModel(radius) ),
    internal_Sizes(NULL),
    N(new LinkedCellArrayCache(Changes, boost::bind(&changeModel::performUpdates, Changes), std::string("N_cached"))),
    domain(_domain)
{
  // set default argument
  NearestNeighbors[0] = NearestNeighbors[1] = NearestNeighbors[2] = 1;
  // get the partition of the domain
  setPartition(radius);
  // allocate linked cell structure
  AllocateCells();
  // sign in to AtomObserver
  startListening();
}
/** Constructor of LinkedCell_Model.
 *
 * @oaram set set of points to place into the linked cell structure
 * @param radius desired maximum neighborhood distance
 * @param _domain Box instance with domain size and boundary conditions
 */
LinkedCell::LinkedCell_Model::LinkedCell_Model(IPointCloud &set, const double radius, const Box &_domain) :
    ::Observer(std::string("LinkedCell_Model")+std::string("_")+toString(radius)),
    Changes( new changeModel(radius) ),
    internal_Sizes(NULL),
    N(new LinkedCellArrayCache(Changes, boost::bind(&changeModel::performUpdates, Changes), std::string("N_cached"))),
    domain(_domain)
{
  Info info(__func__);
  // get the partition of the domain
  setPartition(radius);
  // allocate linked cell structure
  AllocateCells();
  insertPointCloud(set);
  // sign in to AtomObserver
  startListening();
}
/** Destructor of class LinkedCell_Model.
 *
 */
LinkedCell::LinkedCell_Model::~LinkedCell_Model()
{
  // sign off from observables
  stopListening();
  // reset linked cell structure
  Reset();
  delete N;
  // delete change queue
  delete Changes;
}
/** Signs in to AtomObserver and World to known about all changes.
 *
 */
void LinkedCell::LinkedCell_Model::startListening()
{
  World::getInstance().signOn(this, World::AtomInserted);
  World::getInstance().signOn(this, World::AtomRemoved);
  AtomObserver::getInstance().signOn(this, AtomObservable::PositionChanged);
}
/** Signs off from AtomObserver and World.
 *
 */
void LinkedCell::LinkedCell_Model::stopListening()
{
  World::getInstance().signOff(this, World::AtomInserted);
  World::getInstance().signOff(this, World::AtomRemoved);
  AtomObserver::getInstance().signOff(this, AtomObservable::PositionChanged);
}
/** Allocates as much cells per axis as required by
 * LinkedCell_Model::BoxPartition.
 *
 */
void LinkedCell::LinkedCell_Model::AllocateCells()
{
  // resize array
  tripleIndex index;
  for (int i=0;i(Dimensions.at(i,i));
  N->setN().resize(index);
  ASSERT(getSize(0)*getSize(1)*getSize(2) < MAX_LINKEDCELLNODES,
      "LinkedCell_Model::AllocateCells() - Number linked of linked cell nodes exceeded hard-coded limit, use greater edge length!");
  LOG(1, "INFO: Allocating array ("
      << getSize(0) << ","
      << getSize(1) << ","
      << getSize(2) << ") for a new LinkedCell_Model.");
  // allocate LinkedCell instances
  for(index[0] = 0; index[0] != static_cast(Dimensions.at(0,0)); ++index[0]) {
    for(index[1] = 0; index[1] != static_cast(Dimensions.at(1,1)); ++index[1]) {
      for(index[2] = 0; index[2] != static_cast(Dimensions.at(2,2)); ++index[2]) {
        LOG(5, "INFO: Creating cell at " << index[0] << " " << index[1] << " " << index[2] << ".");
        N->setN()(index) = new LinkedCell(index);
      }
    }
  }
}
/** Frees all Linked Cell instances and sets array dimensions to (0,0,0).
 *
 */
void LinkedCell::LinkedCell_Model::Reset()
{
  // free all LinkedCell instances
  for(iterator3 iter3 = N->setN().begin(); iter3 != N->setN().end(); ++iter3) {
    for(iterator2 iter2 = (*iter3).begin(); iter2 != (*iter3).end(); ++iter2) {
      for(iterator1 iter1 = (*iter2).begin(); iter1 != (*iter2).end(); ++iter1) {
        delete *iter1;
      }
    }
  }
  // set dimensions to zero
  N->setN().resize(boost::extents[0][0][0]);
}
/** Inserts all points contained in \a set.
 *
 * @param set set with points to insert into linked cell structure
 */
void LinkedCell::LinkedCell_Model::insertPointCloud(IPointCloud &set)
{
  if (set.IsEmpty()) {
    ELOG(1, "set is NULL or contains no linked cell nodes!");
    return;
  }
  // put each atom into its respective cell
  set.GoToFirst();
  while (!set.IsEnd()) {
    TesselPoint *Walker = set.GetPoint();
    addNode(Walker);
    set.GoToNext();
  }
}
/** Calculates the required edge length for the given desired distance.
 *
 * We need to make some matrix transformations in order to obtain the required
 * edge lengths per axis. Goal is guarantee that whatever the shape of the
 * domain that always all points at least up to \a distance away are contained
 * in the nearest neighboring cells.
 *
 * @param distance distance of this linked cell array
 */
void LinkedCell::LinkedCell_Model::setPartition(double distance)
{
  // generate box matrix of desired edge length
  RealSpaceMatrix neighborhood;
  neighborhood.setIdentity();
  neighborhood *= distance;
  // obtain refs to both domain matrix transformations
  //const RealSpaceMatrix &M = domain.getM();
  const RealSpaceMatrix &Minv = domain.getMinv();
  RealSpaceMatrix output = Minv * neighborhood;
  std::cout << Minv << " * " << neighborhood << " = " << output << std::endl;
  Dimensions = output.invert();
  std::cout << "Dimensions are then " << Dimensions << std::endl;
  // now dimensions is floating-point, but we need it to be integer (for allocation)
  for (size_t col = 0; col < NDIM; ++col) {
    for (size_t row = 0; row < NDIM; ++row) {
      ASSERT(fabs(Dimensions.at(row,col) - Dimensions.at(col,row)) < 1.e+3*std::numeric_limits::epsilon(),
          "LinkedCell_Model::setPartition() - Dimensions is not symmetric by "
          +toString(fabs(Dimensions.at(row,col) - Dimensions.at(col,row)))+ ".");
      if (col != row) {
        ASSERT(fabs(Dimensions.at(row,col)) < 1.e+3*std::numeric_limits::epsilon(),
            "LinkedCell_Model::setPartition() - Dimensions is not diagonal by "
            +toString(fabs(Dimensions.at(row,col)))+".");
      } else {
        Dimensions.set(row,col, ceil(Dimensions.at(row,col)));
      }
    }
  }
  Partition = Minv*Dimensions; //
  std::cout << "Partition matrix is then " << Partition << std::endl;
}
/** Returns the number of required neighbor-shells to get all neighboring points
 * in the given \a distance.
 *
 * @param distance radius of neighborhood sphere
 * @return number of LinkedCell's per dimension to get all neighbors
 */
const LinkedCell::tripleIndex LinkedCell::LinkedCell_Model::getStep(const double distance) const
{
  tripleIndex index;
  index[0] = index[1] = index[2] = 0;
  if (fabs(distance) < std::numeric_limits::min())
    return index;
  // generate box matrix of desired edge length
  RealSpaceMatrix neighborhood;
  neighborhood.setIdentity();
  neighborhood *= distance;
  const RealSpaceMatrix output = Partition * neighborhood;
  //std::cout << "GetSteps: " << Partition << " * " << neighborhood << " = " << output << std::endl;
  const RealSpaceMatrix steps = output;
  for (size_t i =0; i(floor(x[i]));
  }
  return index;
}
/** Adds an update to the list of lazy changes to add a node.
 *
 * @param Walker node to add
 */
void LinkedCell::LinkedCell_Model::addNode(const TesselPoint *Walker)
{
  LOG(2, "INFO: Requesting update to add node " << *Walker << ".");
  Changes->addUpdate(Walker, 0, boost::bind(&LinkedCell_Model::addNode_internal, this, _1), "add");
}
/** Adds an update to the list of lazy changes to add remove a node.
 *
 * We do nothing of Walker is not found
 *
 * @param Walker node to remove
 */
void LinkedCell::LinkedCell_Model::deleteNode(const TesselPoint *Walker)
{
  LOG(2, "INFO: Deleting node " << *Walker << ".");
  deleteNode_internal(Walker);
  Changes->removeUpdates(Walker);
}
/** Adds an update to the list of lazy changes to move a node.
 *
 * @param Walker node who has moved.
 */
void LinkedCell::LinkedCell_Model::moveNode(const TesselPoint *Walker)
{
  LOG(2, "INFO: Requesting update to move node " << *Walker << " to position "
      << Walker->getPosition() << ".");
  Changes->addUpdate(Walker, 10, boost::bind(&LinkedCell_Model::moveNode_internal, this, _1), "move");
}
/** Internal function to add a node to the linked cell structure
 *
 * @param Walker node to add
 */
void LinkedCell::LinkedCell_Model::addNode_internal(const TesselPoint *Walker)
{
  // find index
  tripleIndex index = getIndexToVector(Walker->getPosition());
  LOG(2, "INFO: " << *Walker << " goes into cell " << index[0] << ", " << index[1] << ", " << index[2] << ".");
  LOG(2, "INFO: Cell's indices are "
      << (N->getN())(index)->getIndex(0) << " "
      << (N->getN())(index)->getIndex(1) << " "
      << (N->getN())(index)->getIndex(2) << ".");
  // add to cell
  (N->setN())(index)->addPoint(Walker);
  // add to index with check for presence
  std::pair inserter = CellLookup.insert( std::make_pair(Walker, (N->setN())(index)) );
  ASSERT( inserter.second,
      "LinkedCell_Model::addNode() - Walker "
      +toString(*Walker)+" is already present in cell "
      +toString((inserter.first)->second->getIndex(0))+" "
      +toString((inserter.first)->second->getIndex(1))+" "
      +toString((inserter.first)->second->getIndex(2))+".");
}
/** Internal function to remove a node to the linked cell structure
 *
 * We do nothing of Walker is not found
 *
 * @param Walker node to remove
 */
void LinkedCell::LinkedCell_Model::deleteNode_internal(const TesselPoint *Walker)
{
  MapPointToCell::iterator iter = CellLookup.find(Walker);
  // must not ASSERT presence as addNode might have been overrided by this
  // deleteNode update and hence the node will not be present
  if (iter != CellLookup.end()) {
    // remove from lookup
    CellLookup.erase(iter);
    // remove from cell
    iter->second->deletePoint(Walker);
  }
}
/** Internal function to move node from current cell to another on position change.
 *
 * @param Walker node who has moved.
 */
void LinkedCell::LinkedCell_Model::moveNode_internal(const TesselPoint *Walker)
{
  MapPointToCell::iterator iter = CellLookup.find(Walker);
  ASSERT(iter != CellLookup.end(),
      "LinkedCell_Model::deleteNode() - Walker not present in cell stored under CellLookup.");
  if (iter != CellLookup.end()) {
    tripleIndex index = getIndexToVector(Walker->getPosition());
    if (index != iter->second->getIndices()) {
      // remove in old cell
      iter->second->deletePoint(Walker);
      // add to new cell
      N->setN()(index)->addPoint(Walker);
      // update lookup
      iter->second = N->setN()(index);
    }
  }
}
/** Checks whether cell indicated by \a relative relative to LinkedCell_Model::internal_index
 * is out of bounds.
 *
 * \note We do not check for boundary conditions of LinkedCell_Model::domain,
 * we only look at the array sizes
 *
 * @param relative index relative to LinkedCell_Model::internal_index.
 * @return true - relative index is still inside bounds, false - outside
 */
bool LinkedCell::LinkedCell_Model::checkArrayBounds(const tripleIndex &index) const
{
  bool status = true;
  for (size_t i=0;i= 0) &&
        (index[i] < getSize(i))
        );
  }
  return status;
}
/** Corrects \a index according to boundary conditions of LinkedCell_Model::domain .
 *
 * @param index index to correct according to boundary conditions
 */
void LinkedCell::LinkedCell_Model::applyBoundaryConditions(tripleIndex &index) const
{
  for (size_t i=0;i= getSize(i)))
        index[i] = (index[i] % getSize(i));
      break;
    case BoundaryConditions::Bounce:
      if (index[i] < 0)
        index[i] = 0;
      if (index[i] >= getSize(i))
        index[i] = getSize(i)-1;
      break;
    case BoundaryConditions::Ignore:
      if (index[i] < 0)
        index[i] = 0;
      if (index[i] >= getSize(i))
        index[i] = getSize(i)-1;
      break;
    default:
      ASSERT(0, "LinkedCell_Model::checkBounds() - unknown boundary conditions.");
      break;
    }
  }
}
/** Calculates the interval bounds of the linked cell grid.
 *
 *  The neighborhood bounds works as follows: We give the lower, left, front
 *  corner of the box and the number of boxes to go in each direction (i.e. the
 *  relative upper, right, behind corner). We assure that the first corner is
 *  within LinkedCell_Model::N, whether the relative second corner is within the
 *  domain must be assured via applying its boundary conditions, see
 *  LinkedCell_Model::applyBoundaryConditions()
 *
 * \note we check whether \a index is valid, i.e. within the range of LinkedCell_Model::N.
 *
 * \param index index to give relative bounds to
 * \param step how deep to check the neighbouring cells (i.e. number of layers to check)
 * \return pair of tripleIndex indicating lower and upper bounds
 */
const LinkedCell::LinkedCell_Model::LinkedCellNeighborhoodBounds LinkedCell::LinkedCell_Model::getNeighborhoodBounds(
    const tripleIndex &index,
    const tripleIndex &step
    ) const
{
  LinkedCellNeighborhoodBounds neighbors;
  // calculate bounds
  for (size_t i=0;i= 0,
        "LinkedCell_Model::getNeighborhoodBounds() - index "
        +toString(index)+" out of lower bounds.");
    ASSERT (index[i] < getSize(i),
        "LinkedCell_Model::getNeighborhoodBounds() - index "
        +toString(index)+" out of upper bounds.");
    switch (domain.getConditions()[i]) {
      case BoundaryConditions::Wrap:
        if ((index[i] - step[i]) < 0)
          neighbors.first[i] = getSize(i) + (index[i] - step[i]);
        else if ((index[i] - step[i]) >= getSize(i))
          neighbors.first[i] = (index[i] - step[i]) - getSize(i);
        else
          neighbors.first[i] = index[i] - step[i];
        neighbors.second[i] = 2*step[i]+1;
        break;
      case BoundaryConditions::Bounce:
        neighbors.second[i] = 2*step[i]+1;
        if (index[i] - step[i] >= 0) {
          neighbors.first[i] = index[i] - step[i];
        } else {
          neighbors.first[i] = 0;
          neighbors.second[i] = index[i] + step[i]+1;
        }
        if (index[i] + step[i] >= getSize(i)) {
          neighbors.second[i] = getSize(i) - (index[i] - step[i]);
        }
        break;
      case BoundaryConditions::Ignore:
        if (index[i] - step[i] < 0)
          neighbors.first[i] = 0;
        else
          neighbors.first[i] = index[i] - step[i];
        if ((neighbors.first[i] + 2*step[i]+1) >= getSize(i))
          neighbors.second[i] = getSize(i) - neighbors.first[i];
        else
          neighbors.second[i] = 2*step[i]+1;
        break;
      default:
        ASSERT(0, "LinkedCell_Model::getNeighborhoodBounds() - unknown boundary conditions.");
        break;
    }
    // check afterwards whether we now have correct
    ASSERT((neighbors.first[i] >= 0) && (neighbors.first[i] < getSize(i)),
        "LinkedCell_Model::getNeighborhoodBounds() - lower border "
        +toString(neighbors.first)+" is out of bounds.");
  }
  LOG(3, "INFO: Resulting neighborhood bounds are [" << neighbors.first << "]<->[" << neighbors.second << "].");
  return neighbors;
}
/** Returns a reference to the cell indicated by LinkedCell_Model::internal_index.
 *
 * \return LinkedCell ref to current cell
 */
const LinkedCell::LinkedCell& LinkedCell::LinkedCell_Model::getCell(const tripleIndex &index) const
{
  return *(N->getN()(index));
}
/** Returns size of array for given \a dim.
 *
 * @param dim desired dimension
 * @return size of array along dimension
 */
LinkedCell::LinkedCellArray::index LinkedCell::LinkedCell_Model::getSize(const size_t dim) const
{
  ASSERT((dim >= 0) && (dim < NDIM),
      "LinkedCell_Model::getSize() - dimension "
      +toString(dim)+" is out of bounds.");
  return N->getN().shape()[dim];
}
/** Callback function for Observer mechanism.
 *
 * @param publisher reference to the Observable that calls
 */
void LinkedCell::LinkedCell_Model::update(Observable *publisher)
{
  ELOG(2, "LinkedCell_Model received inconclusive general update from "
      << publisher << ".");
}
/** Callback function for the Notifications mechanism.
 *
 * @param publisher reference to the Observable that calls
 * @param notification specific notification as cause of the call
 */
void LinkedCell::LinkedCell_Model::recieveNotification(Observable *publisher, Notification_ptr notification)
{
  // either it's the World or from the atom (through relay) itself
  if (publisher == World::getPointer()) {
    switch(notification->getChannelNo()) {
      case World::AtomInserted:
        addNode(World::getInstance().lastChanged());
        break;
      case World::AtomRemoved:
        deleteNode(World::getInstance().lastChanged());
        break;
    }
  } else {
    switch(notification->getChannelNo()) {
      case AtomObservable::PositionChanged:
      {
        moveNode(dynamic_cast(publisher));
        break;
      }
      default:
        LOG(2, "LinkedCell_Model received unwanted notification from AtomObserver's channel "
            << notification->getChannelNo() << ".");
        break;
    }
  }
}
/** Callback function when an Observer dies.
 *
 * @param publisher reference to the Observable that calls
 */
void LinkedCell::LinkedCell_Model::subjectKilled(Observable *publisher)
{}