/*
 * Project: MoleCuilder
 * Description: creates and alters molecular systems
 * Copyright (C)  2017 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 .
 */
/*
 * BoostGraphCreator.cpp
 *
 *  Created on: May 17, 2017
 *      Author: heber
 */
// include config.h
#ifdef HAVE_CONFIG_H
#include 
#endif
//#include "CodePatterns/MemDebug.hpp"
#include "BoostGraphCreator.hpp"
#include 
#include "CodePatterns/Assert.hpp"
#include "Atom/atom.hpp"
#include "Graph/Graph6Reader.hpp"
#include "molecule.hpp"
void BoostGraphCreator::createFromMolecule(
    const molecule &_mol,
    const predicate_t &_pred)
{
  createFromRange(_mol.begin(), _mol.end(), _mol.getAtomCount(), _pred);
}
static bool predicateAnd(
    const BoostGraphCreator::predicate_t &_pred1,
    const BoostGraphCreator::predicate_t &_pred2,
    const bond &_bond)
{ return (_pred1(_bond) && _pred2(_bond)); }
static atomId_t getAtomId(
    const atom *_atom
    )
{ return _atom->getId(); }
static bool inSetPredicate(
  const std::vector &_atomids,
  const bond &_bond)
{
  const atomId_t leftid = _bond.leftatom->getId();
  const atomId_t rightid = _bond.rightatom->getId();
  return std::binary_search(_atomids.begin(), _atomids.end(), leftid)
      && std::binary_search(_atomids.begin(), _atomids.end(), rightid);
}
void BoostGraphCreator::createFromAtoms(
    const std::vector &_atoms,
    const predicate_t &_pred)
{
  // sort makes binary_search possible
  std::vector atomids;
  std::transform(_atoms.begin(), _atoms.end(),
      std::back_inserter(atomids), getAtomId);
  ASSERT( _atoms.size() == atomids.size(),
      "BoostGraphCreator::createFromAtom() - atomids "
      +toString(atomids.size())+" and atoms "+toString(_atoms.size())
      +" differ in size?");
  std::sort(atomids.begin(), atomids.end());
  const predicate_t predicate = boost::bind(inSetPredicate, boost::ref(atomids), _1);
  createFromRange::const_iterator>(
      const_cast &>(_atoms).begin(),
      const_cast &>(_atoms).end(),
      _atoms.size(),
      boost::bind(predicateAnd, _pred, predicate, _1));
}
void BoostGraphCreator::createFromGraph6String(
    const std::string &_graph_string)
{
  Graph6Reader reader;
  {
    std::stringstream inputstream(_graph_string);
    reader(inputstream);
  }
  graph = UndirectedGraph();
  // add nodes
  for(int numbers = 0; numbers < reader.get_num_nodes(); ++numbers) {
    const atomId_t atomid = numbers;
    Vertex v = boost::add_vertex(atomid, graph);
    const atomId_t vertexname = boost::get(boost::get(boost::vertex_name, graph), v);
    const nodeId_t vertexindex = boost::get(boost::get(boost::vertex_index, graph), v);
    LOG(2, "DEBUG: Adding node " << vertexindex << " associated to atom #" << vertexname);
    ASSERT( vertexname == atomid,
        "BoostGraphCreator::createFromRange() - atomid "+toString(atomid)
        +" is not name of vertex "+toString(vertexname)+".");
    atomids_nodeids.insert( std::make_pair(vertexname, vertexindex) );
  }
  LOG(1, "INFO: Added " << reader.get_num_nodes() << " nodes.");
  // add edges
  const Graph6Reader::edges_t &edges = reader.get_edges();
  for(Graph6Reader::edges_t::const_iterator iter = edges.begin();
      iter != edges.end(); ++iter) {
    // graph6 contains only upper triangle of adjacency matrix, hence add only once
    const nodeId_t leftnodeid = iter->first;
    const nodeId_t rightnodeid = iter->second;
    boost::add_edge(leftnodeid, rightnodeid, graph);
  }
  LOG(1, "INFO: Added " << reader.get_edges().size() << " edges.");
}
BoostGraphCreator::nodeId_t BoostGraphCreator::getNodeId(
    const atomId_t &_atomid) const
{
  atomids_nodeids_t::const_iterator iter =
      atomids_nodeids.find(_atomid);
  return (iter == atomids_nodeids.end()) ? (nodeId_t)-1 : iter->second;
}
BoostGraphCreator::Edge BoostGraphCreator::findEdge(const atomId_t &_firstid, const atomId_t &_secondid)
{
  edge_iter i, end;
  const name_map_t name_map = boost::get(boost::vertex_name, graph);
  for (boost::tie(i, end) = boost::edges(graph); i != end; ++i) {
    const BoostGraphCreator::Edge &e = *i;
    const Vertex u = source(e, graph);
    const Vertex v = target(e, graph);
    const atomId_t atomid_u = boost::get(name_map, u);
    const atomId_t atomid_v = boost::get(name_map, v);
    if (atomid_u == _firstid) {
      if (atomid_v == _secondid)
        break;
    } else if (atomid_u == _secondid) {
      if (atomid_v == _firstid)
        break;
    }
  }
  if (i != end) {
    BoostGraphCreator::Edge e = *i;
    return e;
  }
  return BoostGraphCreator::Edge();
}
bool BoostGraphCreator::removeEdge(const atomId_t &_firstid, const atomId_t &_secondid)
{
  // look through all edges
  BoostGraphCreator::Edge e = findEdge(_firstid, _secondid);
  // edge was found
  if (e != BoostGraphCreator::Edge()) {
    const Vertex u = source(e, graph);
    const Vertex v = target(e, graph);
    if (DoLog(3)) {
      const name_map_t name_map = boost::get(boost::vertex_name, graph);
      LOG(3, "DEBUG: Found edge " << boost::get(name_map, u) <<  " <-> "
          << boost::get(name_map, v) << ", removing.");
    }
    boost::remove_edge(e, graph);
    return true;
  }
  return false;
}
bool BoostGraphCreator::addEdge(const atomId_t &_firstid, const atomId_t &_secondid)
{
  // look through all edges
  BoostGraphCreator::Edge e = findEdge(_firstid, _secondid);
  if (e != BoostGraphCreator::Edge()) {
    return false;
  } else {
    const nodeId_t leftnodeid = getNodeId(_firstid);
    const nodeId_t rightnodeid = getNodeId(_secondid);
    boost::add_edge(leftnodeid, rightnodeid, graph);
    return true;
  }
}