/*
 * Project: MoleCuilder
 * Description: creates and alters molecular systems
 * Copyright (C)  2010-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 .
 */
/*
 * bondgraph.cpp
 *
 *  Created on: Oct 29, 2009
 *      Author: heber
 */
// include config.h
#ifdef HAVE_CONFIG_H
#include 
#endif
// boost::graph uses placement new
#include 
#include "CodePatterns/MemDebug.hpp"
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include "Atom/atom.hpp"
#include "Bond/bond.hpp"
#include "Graph/BondGraph.hpp"
#include "Box.hpp"
#include "Element/element.hpp"
#include "CodePatterns/Info.hpp"
#include "CodePatterns/Log.hpp"
#include "CodePatterns/Range.hpp"
#include "CodePatterns/Verbose.hpp"
#include "molecule.hpp"
#include "Element/periodentafel.hpp"
#include "Fragmentation/MatrixContainer.hpp"
#include "Graph/DepthFirstSearchAnalysis.hpp"
#include "LinearAlgebra/Vector.hpp"
#include "World.hpp"
#include "WorldTime.hpp"
const double BondGraph::BondThreshold = 0.4;   //!< CSD threshold in bond check which is the width of the interval whose center is the sum of the covalent radii
BondGraph::BondGraph() :
    BondLengthMatrix(NULL),
    IsAngstroem(true)
{}
BondGraph::BondGraph(bool IsA) :
    BondLengthMatrix(NULL),
    IsAngstroem(IsA)
{}
BondGraph::~BondGraph()
{
  CleanupBondLengthTable();
}
void BondGraph::CleanupBondLengthTable()
{
  if (BondLengthMatrix != NULL) {
    delete(BondLengthMatrix);
  }
}
bool BondGraph::LoadBondLengthTable(
    std::istream &input)
{
  Info FunctionInfo(__func__);
  bool status = true;
  MatrixContainer *TempContainer = NULL;
  // allocate MatrixContainer
  if (BondLengthMatrix != NULL) {
    LOG(1, "MatrixContainer for Bond length already present, removing.");
    delete(BondLengthMatrix);
    BondLengthMatrix = NULL;
  }
  TempContainer = new MatrixContainer;
  // parse in matrix
  if ((input.good()) && (status = TempContainer->ParseMatrix(input, 0, 1, 0))) {
    LOG(1, "Parsing bond length matrix successful.");
  } else {
    ELOG(1, "Parsing bond length matrix failed.");
    status = false;
  }
  if (status) // set to not NULL only if matrix was parsed
    BondLengthMatrix = TempContainer;
  else {
    BondLengthMatrix = NULL;
    delete(TempContainer);
  }
  return status;
}
double BondGraph::GetBondLength(
    int firstZ,
    int secondZ) const
{
  double return_length;
  if ((firstZ < 0) || (firstZ >= (int)BondLengthMatrix->Matrix[0].size()))
    return -1.;
  if ((secondZ < 0) || (secondZ >= (int)BondLengthMatrix->Matrix[0][firstZ].size()))
    return -1.;
  if (BondLengthMatrix == NULL) {
    return_length = -1.;
  } else {
    return_length = BondLengthMatrix->Matrix[0][firstZ][secondZ];
  }
  LOG(4, "INFO: Request for length between " << firstZ << " and "
      << secondZ << ": " << return_length << ".");
  return return_length;
}
range BondGraph::CovalentMinMaxDistance(
    const element * const Walker,
    const element * const OtherWalker) const
{
  range MinMaxDistance(0.,0.);
  MinMaxDistance.first = OtherWalker->getCovalentRadius() + Walker->getCovalentRadius();
  MinMaxDistance.first *= (IsAngstroem) ? 1. : 1. / AtomicLengthToAngstroem;
  MinMaxDistance.last = MinMaxDistance.first + BondThreshold;
  MinMaxDistance.first -= BondThreshold;
  return MinMaxDistance;
}
range BondGraph::BondLengthMatrixMinMaxDistance(
    const element * const Walker,
    const element * const OtherWalker) const
{
  ASSERT(BondLengthMatrix, "BondGraph::BondLengthMatrixMinMaxDistance() called without NULL BondLengthMatrix.");
  ASSERT(Walker, "BondGraph::BondLengthMatrixMinMaxDistance() - illegal element given.");
  ASSERT(OtherWalker, "BondGraph::BondLengthMatrixMinMaxDistance() - illegal other element given.");
  range MinMaxDistance(0.,0.);
  MinMaxDistance.first = GetBondLength(Walker->getAtomicNumber()-1, OtherWalker->getAtomicNumber()-1);
  MinMaxDistance.first *= (IsAngstroem) ? 1. : 1. / AtomicLengthToAngstroem;
  MinMaxDistance.last = MinMaxDistance.first + BondThreshold;
  MinMaxDistance.first -= BondThreshold;
  return MinMaxDistance;
}
range BondGraph::getMinMaxDistance(
    const element * const Walker,
    const element * const OtherWalker) const
{
  range MinMaxDistance(0.,0.);
  if (BondLengthMatrix == NULL) {// safety measure if no matrix has been parsed yet
    LOG(5, "DEBUG: Using Covalent radii criterion for [min,max) distances.");
    MinMaxDistance = CovalentMinMaxDistance(Walker, OtherWalker);
  } else {
    LOG(5, "DEBUG: Using tabulated bond table criterion for [min,max) distances.");
    MinMaxDistance = BondLengthMatrixMinMaxDistance(Walker, OtherWalker);
  }
  return MinMaxDistance;
}
range BondGraph::getMinMaxDistance(
    const BondedParticle * const Walker,
    const BondedParticle * const OtherWalker) const
{
  return getMinMaxDistance(Walker->getType(), OtherWalker->getType());
}
range BondGraph::getMinMaxDistanceSquared(
    const BondedParticle * const Walker,
    const BondedParticle * const OtherWalker) const
{
  // use non-squared version
  range MinMaxDistance(getMinMaxDistance(Walker, OtherWalker));
  // and square
  MinMaxDistance.first *= MinMaxDistance.first;
  MinMaxDistance.last *= MinMaxDistance.last;
  return MinMaxDistance;
}
LinkedCell::LinkedCell_View BondGraph::getLinkedCell(const double max_distance) const
{
  return World::getInstance().getLinkedCell(max_distance);
}
std::set< const element *> BondGraph::getElementSetFromNumbers(const std::set &Set) const
{
  std::set< const element *> PresentElements;
  for(std::set::const_iterator iter = Set.begin(); iter != Set.end(); ++iter)
    PresentElements.insert( World::getInstance().getPeriode()->FindElement(*iter) );
  return PresentElements;
}
Box &BondGraph::getDomain() const
{
  return World::getInstance().getDomain();
}
unsigned int BondGraph::getTime() const
{
  return WorldTime::getTime();
}
bool BondGraph::operator==(const BondGraph &other) const
{
  if (IsAngstroem != other.IsAngstroem)
    return false;
  if (((BondLengthMatrix != NULL) && (other.BondLengthMatrix == NULL))
      || ((BondLengthMatrix == NULL) && (other.BondLengthMatrix != NULL)))
    return false;
  if ((BondLengthMatrix != NULL) && (other.BondLengthMatrix != NULL))
    if (*BondLengthMatrix != *other.BondLengthMatrix)
      return false;
  return true;
}
/** Corrects the bond degree by one at most if necessary.
 *
 * We are constraint to bonds in \a egdes, if the candidate bond is in edges, we
 * just don't increment its bond degree. We do not choose an alternative for this
 * atom.
 *
 * \param edges only these edges can be updated
 * \return number of corrections done
 */
int BondGraph::CorrectBondDegree(atom *_atom, const std::set& edges) const
{
  int NoBonds = 0;
  int OtherNoBonds = 0;
  int FalseBondDegree = 0;
  int CandidateDeltaNoBonds = -1;
  atom *OtherWalker = NULL;
  bond::ptr CandidateBond;
  NoBonds = _atom->CountBonds();
  LOG(3, "Walker " << *_atom << ": " << (int)_atom->getType()->getNoValenceOrbitals() << " > " << NoBonds << "?");
  const int DeltaNoBonds = (int)(_atom->getType()->getNoValenceOrbitals()) - NoBonds;
  if (DeltaNoBonds > 0) { // we have a mismatch, check all bonding partners for mismatch
    const BondList& ListOfBonds = _atom->getListOfBonds();
    for (BondList::const_iterator Runner = ListOfBonds.begin(); Runner != ListOfBonds.end(); (++Runner)) {
      OtherWalker = (*Runner)->GetOtherAtom(_atom);
      OtherNoBonds = OtherWalker->CountBonds();
      const int OtherDeltaNoBonds = (int)(OtherWalker->getType()->getNoValenceOrbitals()) - OtherNoBonds;
      LOG(3, "OtherWalker " << *OtherWalker << ": " << (int)OtherWalker->getType()->getNoValenceOrbitals() << " > " << OtherNoBonds << "?");
      if (OtherDeltaNoBonds > 0) { // check if possible candidate
        const BondList& OtherListOfBonds = OtherWalker->getListOfBonds();
        if ((CandidateBond == NULL)
            || (ListOfBonds.size() > OtherListOfBonds.size()) // pick the one with fewer number of bonds first
            || ((CandidateDeltaNoBonds < 0) || (CandidateDeltaNoBonds > OtherDeltaNoBonds)) ) // pick the one closer to fulfilling its valence first
        {
          CandidateDeltaNoBonds = OtherDeltaNoBonds;
          CandidateBond = (*Runner);
          LOG(3, "New candidate is " << *CandidateBond << ".");
        }
      }
    }
    if ((CandidateBond != NULL)) {
      if (edges.find(CandidateBond) != edges.end()) {
        CandidateBond->setDegree(CandidateBond->getDegree()+1);
        LOG(2, "Increased bond degree for bond " << *CandidateBond << ".");
      } else
        LOG(2, "Bond " << *CandidateBond << " is not in edges.");
    } else {
      ELOG(2, "Could not find correct degree for atom " << *_atom << ".");
      FalseBondDegree++;
    }
  }
  return FalseBondDegree;
}
/** Sets the weight of all connected bonds to one.
 */
void BondGraph::resetBondDegree(atom *_atom) const
{
  const BondList &ListOfBonds = _atom->getListOfBonds();
  for (BondList::const_iterator BondRunner = ListOfBonds.begin();
      BondRunner != ListOfBonds.end();
      ++BondRunner)
    (*BondRunner)->setDegree(1);
}
std::set BondGraph::getBackEdges() const
{
  DepthFirstSearchAnalysis DFS;
  DFS();
  const std::deque& backedgestack = DFS.getBackEdgeStack();
  std::set backedges(backedgestack.begin(), backedgestack.end());
  return backedges;
}
std::set BondGraph::getTreeEdges() const
{
  const std::set backedges = getBackEdges();
  std::set alledges;
  const World& world = World::getInstance();
  for(World::AtomConstIterator iter = world.getAtomIter();
      iter != world.atomEnd(); ++iter) {
    const BondList &ListOfBonds = (*iter)->getListOfBonds();
    alledges.insert(ListOfBonds.begin(), ListOfBonds.end());
  }
  std::set treeedges;
  std::set_difference(
      alledges.begin(), alledges.end(),
      backedges.begin(), backedges.end(),
      std::inserter(treeedges, treeedges.begin()));
  return treeedges;
}
struct hasDelta {
  bool operator()(atom *_atom) {
    const double delta =
        _atom->getType()->getNoValenceOrbitals() - _atom->CountBonds();
    return (fabs(delta) > 0);
  }
};
typedef std::set deltaatoms_t;
typedef std::set deltaedges_t;
static int gatherAllDeltaAtoms(
    const deltaatoms_t &allatoms,
    deltaatoms_t &deltaatoms)
{
  static hasDelta delta;
  deltaatoms.clear();
  for (deltaatoms_t::const_iterator iter = allatoms.begin();
      iter != allatoms.end(); ++iter) {
    if (delta(*iter))
      deltaatoms.insert(*iter);
  }
  return deltaatoms.size();
}
typedef boost::bimap AtomIndexLookup_t;
static AtomIndexLookup_t createAtomIndexLookup(
    const deltaedges_t &edges)
{
  AtomIndexLookup_t AtomIndexLookup;
  size_t index = 0;
  for (deltaedges_t::const_iterator edgeiter = edges.begin();
      edgeiter != edges.end(); ++edgeiter) {
    const bond::ptr & _bond = *edgeiter;
    // insert left into lookup
    std::pair< AtomIndexLookup_t::iterator, bool > inserter =
        AtomIndexLookup.insert( AtomIndexLookup_t::value_type(index, _bond->leftatom) );
    if (inserter.second)
      ++index;
    // insert right into lookup
    inserter = AtomIndexLookup.insert( AtomIndexLookup_t::value_type(index, _bond->rightatom) );
    if (inserter.second)
      ++index;
  }
  return AtomIndexLookup;
}
typedef std::map< std::pair, bond::ptr> BondLookup_t;
static BondLookup_t createBondLookup(
    const deltaedges_t &edges)
{
  BondLookup_t BondLookup;
  for (deltaedges_t::const_iterator edgeiter = edges.begin();
      edgeiter != edges.end(); ++edgeiter) {
    const bond::ptr & _bond = *edgeiter;
    // insert bond into pair lookup
    BondLookup.insert(
        std::make_pair(
            std::make_pair(_bond->leftatom, _bond->rightatom),
            _bond)
    );
  }
  return BondLookup;
}
typedef boost::adjacency_list graph_t;
typedef std::vector::vertex_descriptor> mate_t;
static void fillEdgesIntoMatching(
    graph_t &g,
    mate_t &mate,
    const AtomIndexLookup_t &AtomIndexLookup,
    const BondLookup_t &BondLookup,
    deltaedges_t &matching
    )
{
  matching.clear();
  boost::graph_traits::vertex_iterator vi, vi_end;
  for(tie(vi,vi_end) = boost::vertices(g); vi != vi_end; ++vi)
    if (mate[*vi] != boost::graph_traits::null_vertex() && *vi < mate[*vi]) {
      atom * leftatom = AtomIndexLookup.left.at(*vi);
      atom * rightatom = AtomIndexLookup.left.at(mate[*vi]);
      std::pair AtomPair(leftatom, rightatom);
      std::pair reverseAtomPair(rightatom, leftatom);
      BondLookup_t::const_iterator iter = BondLookup.find(AtomPair);
      if (iter != BondLookup.end()) {
        matching.insert(iter->second);
      } else {
        iter = BondLookup.find(reverseAtomPair);
        if (iter != BondLookup.end()) {
          matching.insert(iter->second);
        } else
          ASSERT(0, "fillEdgesIntoMatching() - cannot find ("+toString(*vi)+","
              +toString(mate[*vi])+") in BondLookup.");
      }
    }
}
static bool createMatching(
    deltaedges_t &deltaedges,
    deltaedges_t &matching
    )
{
  // gather all vertices for graph in a lookup structure
  // to translate the graphs indices to atoms and also to associated bonds
  AtomIndexLookup_t AtomIndexLookup = createAtomIndexLookup(deltaedges);
  BondLookup_t BondLookup = createBondLookup(deltaedges);
  const int n_vertices = AtomIndexLookup.size();
  // construct graph
  graph_t g(n_vertices);
  // add edges to graph
  for (deltaedges_t::const_iterator edgeiter = deltaedges.begin();
      edgeiter != deltaedges.end(); ++edgeiter) {
    const bond::ptr & _bond = *edgeiter;
    boost::add_edge(
        AtomIndexLookup.right.at(_bond->leftatom),
        AtomIndexLookup.right.at(_bond->rightatom),
        g);
  }
  // mate structure contains unique edge partner to every vertex in matching
  mate_t mate(n_vertices);
  // get maximum matching over given edges
  bool success = boost::checked_edmonds_maximum_cardinality_matching(g, &mate[0]);
  if (success) {
    LOG(1, "STATUS: Found a matching of size " << matching_size(g, &mate[0]) << ".");
    fillEdgesIntoMatching(g, mate, AtomIndexLookup, BondLookup, matching);
  } else {
    ELOG(2, "Failed to find maximum matching.");
  }
  return success;
}
bool BondGraph::checkBondDegree(const deltaatoms_t &allatoms) const
{
  deltaatoms_t deltaatoms;
  return (gatherAllDeltaAtoms(allatoms, deltaatoms) == 0);
}
int BondGraph::calculateBondDegree(const deltaatoms_t &allatoms) const
{
  deltaatoms_t deltaatoms;
  deltaedges_t deltaedges;
  deltaedges_t matching;
  LOG(1, "STATUS: Calculating bond degrees.");
  if (DoLog(2)) {
    std::stringstream output;
    output << "INFO: All atoms are: ";
    BOOST_FOREACH( atom *_atom, allatoms) {
      output << *_atom << " ";
    }
    LOG(2, output.str());
  }
  size_t IterCount = 0;
  // 1. gather all atoms with delta > 0
  while ((gatherAllDeltaAtoms(allatoms, deltaatoms) != 0) && (IterCount < 100)) {
    if (DoLog(3)) {
      std::stringstream output;
      output << "DEBUG: Current delta atoms are: ";
      BOOST_FOREACH( atom *_atom, deltaatoms) {
        output << *_atom << " ";
      }
      LOG(3, output.str());
    }
    // 2. gather all edges that connect atoms with both(!) having delta > 0
    deltaedges.clear();
    for (deltaatoms_t::const_iterator atomiter = deltaatoms.begin();
        atomiter != deltaatoms.end(); ++atomiter) {
      const atom * const _atom = *atomiter;
      const BondList& ListOfBonds = (*atomiter)->getListOfBonds();
      for (BondList::const_iterator bonditer = ListOfBonds.begin();
          bonditer != ListOfBonds.end();++bonditer) {
        const bond::ptr _bond = *bonditer;
        if ((_bond->leftatom == _atom) && (deltaatoms.find(_bond->rightatom) != deltaatoms.end()))
          deltaedges.insert(_bond);
        else if ((_bond->rightatom == _atom) && (deltaatoms.find(_bond->leftatom) != deltaatoms.end()))
          deltaedges.insert(_bond);
      }
    }
    if (DoLog(3)) {
      std::stringstream output;
      output << "DEBUG: Current delta bonds are: ";
      BOOST_FOREACH( bond::ptr _bond, deltaedges) {
        output << *_bond << " ";
      }
      LOG(3, output.str());
    }
    if (deltaedges.empty())
      break;
    // 3. build matching over these edges
    if (!createMatching(deltaedges, matching))
      break;
    if (DoLog(2)) {
      std::stringstream output;
      output << "DEBUG: Resulting matching is: ";
      BOOST_FOREACH( bond::ptr _bond, matching) {
        output << *_bond << " ";
      }
      LOG(2, output.str());
    }
    // 4. increase degree for each edge of the matching
    LOG(2, "DEBUG: Increasing bond degrees by one.");
    for (deltaedges_t::iterator edgeiter = matching.begin();
        edgeiter != matching.end(); ++edgeiter) {
      (*edgeiter)->setDegree( (*edgeiter)->getDegree()+1 );
    }
    // 6. stop after a given number of iterations or when all atoms are happy.
    ++IterCount;
  };
  // check whether we still have deltaatoms
  {
    hasDelta delta;
    for (deltaatoms_t::const_iterator iter = allatoms.begin();
        iter != allatoms.end(); ++iter)
      if (delta(*iter))
        ELOG(2, "Could not find satisfy charge neutrality for atom " << **iter << ".");
  }
  return deltaedges.size();
}