/*
 * Project: MoleCuilder
 * Description: creates and alters molecular systems
 * Copyright (C)  2021 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 .
 */
/*
 * StabilityEvaluator.cpp
 *
 *  Created on: Apr 18, 2021
 *      Author: heber
 */
// include config.h
#ifdef HAVE_CONFIG_H
#include 
#endif
//#include "CodePatterns/MemDebug.hpp"
#include "CodePatterns/Log.hpp"
#include "StabilityEvaluator.hpp"
#include 
#include "Atom/atom.hpp"
#include "Bond/bond.hpp"
#include "Fragmentation/Homology/HomologyContainer.hpp"
#include "Fragmentation/Homology/HomologyGraph.hpp"
#include "Fragmentation/KeySet.hpp"
#include "molecule.hpp"
#include "World.hpp"
StabilityEvaluator::StabilityEvaluator(const molecule * _mol):
  mol(_mol),
  container(World::getInstance().getHomologies())
{
}
/**
 * Graph discovery to find the keyset if we exclude a bond from a connected
 * graph.
 */
KeySet discoverKeySetsFromAtomOnExcludedBond(
    const atom *_startatom,
    const bond::ptr &_bond) {
  KeySet result;
  std::stack atoms_to_visit;
  atoms_to_visit.push(_startatom);
  result.insert(_startatom->getId());
  while (!atoms_to_visit.empty()) {
    const atom * Walker = atoms_to_visit.top();
    atoms_to_visit.pop();
    const BondList& ListOfBonds = Walker->getListOfBonds();
    for (BondList::const_iterator bonditer = ListOfBonds.begin();
            bonditer != ListOfBonds.end(); ++bonditer) {
      // exclude the given bond
      if (*bonditer != _bond) {
        const atom * OtherAtom = (*bonditer)->GetOtherAtom(Walker);
        // have we seen the atom already_
        if (result.count(OtherAtom->getId()) == 0) {
          result.insert(OtherAtom->getId());
          atoms_to_visit.push(OtherAtom);
        }
      }
    }
  }
  return result;
}
template 
void insertInto(const N &item, G &_list) {
  std::pair inserter = _list.insert(std::make_pair(item, (size_t)1));
  if (!inserter.second) {
    ++(inserter.first->second);
  }
}
std::string getFormulaFrom(const HomologyGraph &graph) {
  Formula formula;
  for (HomologyGraph::nodes_t::const_iterator iter = graph.getNodes().begin();
      iter != graph.getNodes().end(); ++iter)
    for (size_t i=0;i<(*iter).second;++i)
      formula += (*iter).first.getAtomicNumber();
  return formula.toString();
}
HomologyGraph createHydrogenMoleculeHomologyGraph() {
  HomologyGraph::nodes_t nodes;
  // two hydrogen atoms
  const FragmentNode node = FragmentNode((atomicNumber_t)1, 1);
  insertInto(node, nodes);
  insertInto(node, nodes);
  // one hydrogen bond/edge
  HomologyGraph::edges_t edges;
  const FragmentEdge edge = FragmentEdge((atomicNumber_t)1, (atomicNumber_t)1);
  insertInto(edge, edges);
  // and return
  return HomologyGraph(nodes, edges);
}
StabilityEvaluator::stabilities_t StabilityEvaluator::operator()() const {
  stabilities_t stabilities;
  // gather all non-hydrogen bonds of the molecule
  typedef std::set bondset_t;
  bondset_t non_hydrogen_bonds;
  for (molecule::const_iterator iter = mol->begin(); iter != mol->end(); ++iter) {
    const BondList& ListOfBonds = (*iter)->getListOfBonds();
    for (BondList::const_iterator bonditer = ListOfBonds.begin();
        bonditer != ListOfBonds.end(); ++bonditer) {
      if ((*bonditer)->HydrogenBond == 0)
        non_hydrogen_bonds.insert(*bonditer );
    }
  }
  stabilities.reserve(non_hydrogen_bonds.size());
  // create homology graph for full molecule
  const HomologyGraph fullgraph(mol->getAtomIds());
  const std::string fullgraph_formula = mol->getFormula().toString();
  const HomologyGraph hydrogengraph = createHydrogenMoleculeHomologyGraph();
  // get the energy of the educts
  const double energy_fullgraph = getLowestEnergyFromHomologyContainer(fullgraph);
  const double energy_hydrogen_molecule = getLowestEnergyFromHomologyContainer(hydrogengraph);
  // go through every discovered non-hydrogen bond
  for (bondset_t::iterator bonditer = non_hydrogen_bonds.begin();
      bonditer != non_hydrogen_bonds.end(); ++bonditer) {
    const bond::ptr &bondptr = (*bonditer);
    // create stability criterion structure to fill
    StabilityCriterion criterion;
    criterion.energy_educts = energy_fullgraph + bondptr->getDegree() * energy_hydrogen_molecule;
    criterion.formula_educt1 = fullgraph_formula;
    criterion.formula_educt2 = "H"+toString(bondptr->getDegree()*2);
    // create graphs for each set
    const HomologyGraph leftgraph(
        discoverKeySetsFromAtomOnExcludedBond(bondptr->leftatom, *bonditer)
        );
    const HomologyGraph rightgraph(
        discoverKeySetsFromAtomOnExcludedBond(bondptr->rightatom, *bonditer)
        );
    // look up energy for each graph in the homology container
    criterion.energy_products =
        getLowestEnergyFromHomologyContainer(leftgraph)
        + getLowestEnergyFromHomologyContainer(rightgraph);
    criterion.formula_product1 = getFormulaFrom(leftgraph);
    criterion.formula_product2 = getFormulaFrom(rightgraph);
    criterion.isStable = criterion.energy_educts < criterion.energy_products;
    stabilities.push_back(criterion);
  }
  return stabilities;
}
double StabilityEvaluator::getLowestEnergyFromHomologyContainer(const HomologyGraph &nodes_graph) const {
  HomologyContainer::range_t range = container.getHomologousGraphs(nodes_graph);
  if (range.first == range.second) {
    // range is empty, the fragment is unknown
    ELOG(1, "Cannot find fragment graph " << nodes_graph << " .");
    return 0.;
  } else {
    // list lowest energy
    const HomologyContainer::const_iterator lowest_contribution_graph =
        std::min_element(range.first, range.second, HomologyContainer::compareEnergy);
    const HomologyContainer::const_iterator highest_contribution_graph =
        std::max_element(range.first, range.second, HomologyContainer::compareEnergy);
    LOG(2, "INFO: Fragment graph " << nodes_graph << " has energy contributions from "
        << lowest_contribution_graph->second.fragmentenergy << " Ht till "
        << highest_contribution_graph->second.fragmentenergy << " Ht, picking lowest.");
    return lowest_contribution_graph->second.fragmentenergy;
  }
}
void createHomologyGraphFromMolecule() {
  HomologyGraph::nodes_t graph_nodes;
  HomologyGraph::edges_t graph_edges;
}
std::ostream& operator<<(std::ostream &ost, const StabilityEvaluator::StabilityCriterion &_stability) {
  ost << _stability.formula_educt1 << " + " << _stability.formula_educt2
      << " (" << _stability.energy_educts << " Ht) < "
     << _stability.formula_product1 << " + " << _stability.formula_product2
      << " (" << _stability.energy_products << " Ht) ? "
      << _stability.isStable;
  return ost;
}