/*
 * 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 .
 */
/*
 * GLMoleculeObject_molecule.cpp
 *
 *  Created on: Mar 30, 2012
 *      Author: ankele
 */
// include config.h
#ifdef HAVE_CONFIG_H
#include 
#endif
#include "GLMoleculeObject_molecule.hpp"
#include 
#include 
#include "CodePatterns/MemDebug.hpp"
#include "CodePatterns/Assert.hpp"
#include "CodePatterns/Log.hpp"
#include "CodePatterns/Observer/Notification.hpp"
#include "CodePatterns/Observer/ObserverLog.hpp"
#include "Atom/atom.hpp"
#include "molecule.hpp"
#include "Descriptors/AtomIdDescriptor.hpp"
#include "Descriptors/MoleculeIdDescriptor.hpp"
#include "Element/element.hpp"
#include "LinearAlgebra/Vector.hpp"
#include "LinkedCell/PointCloudAdaptor.hpp"
#include "LinkedCell/linkedcell.hpp"
#include "Tesselation/tesselation.hpp"
#include "Tesselation/BoundaryLineSet.hpp"
#include "Tesselation/BoundaryTriangleSet.hpp"
#include "Tesselation/CandidateForTesselation.hpp"
#include "Atom/TesselPoint.hpp"
#include "World.hpp"
#include "GLMoleculeObject_atom.hpp"
static QGLSceneNode *createMoleculeMesh(const moleculeId_t molid, QObject *parent)
{
  const molecule *molref = const_cast(World::getInstance()).
      getMolecule(MoleculeById(molid));
  if (molref == NULL) {
    ELOG(1, "Could not createMoleculeMesh, molecule with id " << molid << " already gone.");
    return NULL;
  }
//  Shape shape = molref->getBoundingSphere();
  double minradius = 2.; // TODO: set to maximum bond length value
  LOG(3, "DEBUG: Molecule fits into sphere of radius " << minradius);
  // check minimum bond radius in molecule
  double minlength = std::numeric_limits::max(); 
  for (molecule::const_iterator iter = molref->begin();
      iter != molref->end(); ++iter) {
    const BondList &ListOfBonds = (*iter)->getListOfBonds();
    for (BondList::const_iterator bonditer = ListOfBonds.begin();
         bonditer != ListOfBonds.end(); ++bonditer) {
      const double bond_distance = (*bonditer)->GetDistance();
      minlength = std::min(bond_distance, minlength);
    }
  }
  minradius = std::max( std::max(minradius, minlength), 1.);
  QGeometryData geo;
  // we need at least three points for tesselation
  if (molref->getAtomCount() >= 3) {
    // Tesselate the points.
    Tesselation T;
    PointCloudAdaptor cloud(const_cast(molref), molref->getName());
    T(cloud, minradius);
    // Fill the points into a Qt geometry.
    LinkedCell_deprecated LinkedList(cloud, minradius);
    std::map indices;
    std::map normals;
    int index = 0;
    for (PointMap::const_iterator piter = T.PointsOnBoundary.begin();
        piter != T.PointsOnBoundary.end(); ++piter) {
      const Vector &point = piter->second->getPosition();
      // add data to the primitive
      geo.appendVertex(QVector3D(point[0], point[1], point[2]));
      Vector normalvector;
      for (LineMap::const_iterator lineiter = piter->second->lines.begin();
          lineiter != piter->second->lines.end(); ++lineiter)
        for (TriangleMap::const_iterator triangleiter = lineiter->second->triangles.begin();
            triangleiter != lineiter->second->triangles.end(); ++triangleiter)
          normalvector +=
              triangleiter->second->NormalVector;
      normalvector.Normalize();
      geo.appendNormal(QVector3D(normalvector[0], normalvector[1], normalvector[2]));
      geo.appendColor(QColor(1, 1, 1, 1));
      geo.appendTexCoord(QVector2D(0, 0));
      indices.insert( std::make_pair( piter->second->getNr(), index++));
    }
    // Fill the tesselated triangles into the geometry.
    for (TriangleMap::const_iterator runner = T.TrianglesOnBoundary.begin();
        runner != T.TrianglesOnBoundary.end(); runner++) {
      int v[3];
      for (size_t i=0; i<3; ++i)
        v[i] = runner->second->endpoints[i]->getNr();
      // Sort the vertices so the triangle is clockwise (relative to the normal vector).
      Vector cross = T.PointsOnBoundary[v[1]]->getPosition() - T.PointsOnBoundary[v[0]]->getPosition();
      cross.VectorProduct(T.PointsOnBoundary[v[2]]->getPosition() - T.PointsOnBoundary[v[0]]->getPosition());
      if (cross.ScalarProduct(runner->second->NormalVector) > 0)
        geo.appendIndices(indices[v[0]], indices[v[1]], indices[v[2]]);
      else
        geo.appendIndices(indices[v[0]], indices[v[2]], indices[v[1]]);
    }
  }
  // Build a mesh from the geometry.
  QGLBuilder builder;
  builder.addTriangles(geo);
  QGLSceneNode *mesh = builder.finalizedSceneNode();
  return mesh;
}
GLMoleculeObject_molecule::GLMoleculeObject_molecule(QObject *parent, const moleculeId_t molid) :
  GLMoleculeObject(createMoleculeMesh(molid, parent), parent),
  Observer(std::string("GLMoleculeObject_molecule")+toString(molid)),
  isBoundingBoxUptodate(true),
  isSignedOn(false),
  moleculeid(molid),
  TesselationHullUptodate(true),
  hoverAtomId(-1)
{
  setMaterial(getMaterial(1));
  updateBoundingBox();
  // initially, atoms and bonds should be visible
  m_visible = false;
  connect (this, SIGNAL(hoverChanged(GLMoleculeObject *)), this, SLOT(hoverChangedSignalled(GLMoleculeObject *)));
  connect (this, SIGNAL(hoverChanged(GLMoleculeObject *)), this, SIGNAL(changed()));
  connect( this, SIGNAL(clicked()), this, SLOT(wasClicked()));
}
GLMoleculeObject_molecule::GLMoleculeObject_molecule(QGLSceneNode *mesh[], QObject *parent, const moleculeId_t molid) :
  GLMoleculeObject(mesh, parent),
  Observer(std::string("GLMoleculeObject_molecule")+toString(molid)),
  isBoundingBoxUptodate(true),
  isSignedOn(false),
  moleculeid(molid),
  TesselationHullUptodate(true),
  hoverAtomId(-1)
{
  setMaterial(getMaterial(1));
  updateBoundingBox();
  // initially, atoms and bonds should be visible
  m_visible = false;
  connect (this, SIGNAL(hoverChanged(GLMoleculeObject *)), this, SLOT(hoverChangedSignalled(GLMoleculeObject *)));
  connect (this, SIGNAL(hoverChanged(GLMoleculeObject *)), this, SIGNAL(changed()));
  connect( this, SIGNAL(clicked()), this, SLOT(wasClicked()));
}
GLMoleculeObject_molecule::~GLMoleculeObject_molecule()
{
  if (isSignedOn) {
    const molecule *_molecule = const_cast(World::getInstance()).
        getMolecule(MoleculeById(moleculeid));
    if (_molecule != NULL) {
      _molecule->signOff(this, molecule::AtomInserted);
      _molecule->signOff(this, molecule::AtomRemoved);
      _molecule->signOff(this, molecule::AtomMoved);
    } else {
      ELOG(1, "GLMoleculeObject_molecule cannot sign off, molecule with "
          << moleculeid << " has disappeared.");
    }
  }
  /*_atom->signOff(this, AtomObservable::IndexChanged);
  _atom->signOff(this, AtomObservable::PositionChanged);
  _atom->signOff(this, AtomObservable::ElementChanged);
  _atom->signOff(this, AtomObservable::BondsAdded);*/
  World::getInstance().signOff(this, World::SelectionChanged);
}
void GLMoleculeObject_molecule::activateObserver()
{
  // sign on as observer (obtain non-const instance before)
  const molecule *_molecule = const_cast(World::getInstance()).
      getMolecule(MoleculeById(moleculeid));
  if (_molecule != NULL) {
    _molecule->signOn(this, molecule::AtomInserted);
    _molecule->signOn(this, molecule::AtomRemoved);
    _molecule->signOn(this, molecule::AtomMoved);
    isSignedOn = true;
  } else {
    ELOG(1, "GLMoleculeObject_molecule() - added null object for not present mol id " << moleculeid);
  }
  /*molref->signOn(this, AtomObservable::IndexChanged);
  molref->signOn(this, AtomObservable::PositionChanged);
  molref->signOn(this, AtomObservable::ElementChanged);
  molref->signOn(this, AtomObservable::BondsAdded);*/
  World::getInstance().signOn(this, World::SelectionChanged);
}
void GLMoleculeObject_molecule::addAtomBonds(
    const bond::ptr &_bond,
    const GLMoleculeObject_bond::SideOfBond _side
    )
{
  bool bond_present = false;
  const BondIds ids = getBondIds(_bond, _side);
  // check whether bond is not present already
  bond_present = BondsinSceneMap.count(ids);
  if (!bond_present)
    bondInserted(_bond, _side);
  else {
    BondsinSceneMap[ids]->resetPosition();
    BondsinSceneMap[ids]->resetWidth();
  }
}
void GLMoleculeObject_molecule::addAtomBonds(
    const atom *_atom)
{
  const bool atom_present = AtomsinSceneMap.count(_atom->getId());
  const BondList &bondlist = _atom->getListOfBonds();
  for (BondList::const_iterator bonditer = bondlist.begin();
      (bonditer != bondlist.end()) && atom_present;
      ++bonditer) {
    const bond::ptr _bond = *bonditer;
    // check if OtherAtom's sphere is already present
    const atom *OtherAtom = _bond->GetOtherAtom(_atom);
    const bool otheratom_present = AtomsinSceneMap.count(OtherAtom->getId());
    if (otheratom_present && atom_present) {
      const GLMoleculeObject_bond::SideOfBond side = (_bond->leftatom == _atom) ?
          GLMoleculeObject_bond::left : GLMoleculeObject_bond::right;
      const GLMoleculeObject_bond::SideOfBond otherside = (_bond->leftatom == _atom) ?
          GLMoleculeObject_bond::right : GLMoleculeObject_bond::left;
      addAtomBonds(_bond, side);
      addAtomBonds(_bond, otherside);
    }
  }
}
void GLMoleculeObject_molecule::updateBoundingBox()
{
  isBoundingBoxUptodate = true;
  const molecule * const _molecule = const_cast(World::getInstance()).
      getMolecule(MoleculeById(moleculeid));
  if (_molecule == NULL) {
    ELOG(1, "GLMoleculeObject_molecule cannot updateBoundingBox, molecule with "
        << moleculeid << " has disappeared.");
    return;
  }
  Shape shape = _molecule->getBoundingSphere();
  Vector v = shape.getCenter();
  setPosition(QVector3D(v[0], v[1], v[2]));
  setScale(shape.getRadius() + 0.3); // getBoundingShape() only sees atoms as points, so make the box a bit bigger
}
void GLMoleculeObject_molecule::update(Observable *publisher)
{
#ifdef LOG_OBSERVER
  const molecule *_mol = static_cast(publisher);
  observerLog().addMessage() << "++ Update of Observer " << observerLog().getName(static_cast(this)) << " from molecule "+toString(_mol->getId())+".";
#endif
}
void GLMoleculeObject_molecule::subjectKilled(Observable *publisher)
{
  isSignedOn = false;
}
void GLMoleculeObject_molecule::recieveNotification(Observable *publisher, Notification_ptr notification)
{
  const molecule * const _molecule = const_cast(World::getInstance()).
      getMolecule(MoleculeById(moleculeid));
  if (publisher == dynamic_cast(_molecule)){
    // notofication from atom
#ifdef LOG_OBSERVER
    observerLog().addMessage() << "++ Update of Observer "<< observerLog().getName(static_cast(this))
          << " received notification from molecule " << _molecule->getId() << " for channel "
          << notification->getChannelNo() << ".";
#endif
    switch (notification->getChannelNo()) {
      case molecule::AtomInserted:
      {
        const atomId_t _id = _molecule->lastChanged()->getId();
  #ifdef LOG_OBSERVER
        observerLog().addMessage() << "++ Observer " << observerLog().getName(static_cast(this)) << " received notification that atom "+toString(_id)+" has been inserted.";
  #endif
        QMetaObject::invokeMethod(this,        // pointer to a QObject
                                  "atomInserted",       // member name (no parameters here)
                                  Qt::QueuedConnection,     // connection type
                                  Q_ARG(atomId_t, _id));     // parameters
        break;
      }
      case World::AtomRemoved:
      {
        const atomId_t _id = _molecule->lastChanged()->getId();
  #ifdef LOG_OBSERVER
        observerLog().addMessage() << "++ Observer " << observerLog().getName(static_cast(this)) << " received notification that atom "+toString(_id)+" has been removed.";
  #endif
        QMetaObject::invokeMethod(this,        // pointer to a QObject
                                  "atomRemoved",       // member name (no parameters here)
                                  Qt::QueuedConnection,     // connection type
                                  Q_ARG(const atomId_t, _id));     // parameters
        break;
      }
      case molecule::AtomMoved:
      {
  #ifdef LOG_OBSERVER
        const atomId_t _id = _molecule->lastChanged()->getId();
        observerLog().addMessage() << "++ Observer " << observerLog().getName(static_cast(this)) << " received notification that atom "+toString(_id)+" has been inserted.";
  #endif
        TesselationHullUptodate = false;
        isBoundingBoxUptodate = false;
        break;
      }
      default:
        break;
    }
  }else{
    // notification from world
#ifdef LOG_OBSERVER
    observerLog().addMessage() << "++ Update of Observer "<< observerLog().getName(static_cast(this))
          << " received notification from world for channel "
          << notification->getChannelNo() << ".";
#endif
    switch (notification->getChannelNo()) {
      case World::SelectionChanged:
        if (_molecule != NULL) {
          setSelected(World::getInstance().isSelected(_molecule));
        } else {
          ELOG(1, "GLMoleculeObject_molecule cannot change selection, molecule with "
              << moleculeid << " has disappeared.");
        }
        break;
      default:
        break;
    }
  }
}
void GLMoleculeObject_molecule::initialize(QGLView *view, QGLPainter *painter)
{
  // Initialize all of the mesh objects that we have as children.
  if (m_visible) {
    GLMoleculeObject::initialize(view, painter);
  } else {
   foreach (QObject *obj, children()) {
     GLMoleculeObject *meshobj = qobject_cast(obj);
       if (meshobj)
         meshobj->initialize(view, painter);
   }
  }
}
void GLMoleculeObject_molecule::draw(QGLPainter *painter, const QVector4D &cameraPlane)
{
  // draw either molecule's mesh or all atoms and bonds
  if (m_visible) {
    updateTesselationHull();
    painter->modelViewMatrix().push();
    // Apply the material and effect to the painter.
    QGLMaterial *material;
    if (m_hovering)
        material = m_hoverMaterial;
    else if (m_selected)
        material = m_selectionMaterial;
    else
        material = m_material;
    ASSERT(material, "GLMoleculeObject::draw: chosen material is NULL");
    painter->setColor(material->diffuseColor());
    painter->setFaceMaterial(QGL::AllFaces, material);
    if (m_effect)
        painter->setUserEffect(m_effect);
    else
        painter->setStandardEffect(QGL::LitMaterial);
    // Mark the object for object picking purposes.
    int prevObjectId = painter->objectPickId();
    if (m_objectId != -1)
        painter->setObjectPickId(m_objectId);
    m_mesh[0]->draw(painter);
    // Turn off the user effect, if present.
    if (m_effect)
        painter->setStandardEffect(QGL::LitMaterial);
    // Revert to the previous object identifier.
    painter->setObjectPickId(prevObjectId);
    // Restore the modelview matrix.
    painter->modelViewMatrix().pop();
    //    GLMoleculeObject::draw(painter, cameraPlane);
  } else {
    // Draw all of the mesh objects that we have as children.
    foreach (QObject *obj, children()) {
      GLMoleculeObject *meshobj = qobject_cast(obj);
      if (meshobj)
        meshobj->draw(painter, cameraPlane);
    }
    // update bounding box prior to selection
    if (!isBoundingBoxUptodate)
      updateBoundingBox();
    painter->modelViewMatrix().push();
    painter->modelViewMatrix().translate(m_position);
    if (m_rotationAngle != 0.0f)
      painter->modelViewMatrix().rotate(m_rotationAngle, m_rotationVector);
    if ((m_scaleX != 1.0f) || (m_scaleY != 1.0f) || (m_scaleZ != 1.0f))
      painter->modelViewMatrix().scale(m_scaleX, m_scaleY, m_scaleZ);
    // Draw a box around the mesh, if selected.
    if (m_selected)
      drawSelectionBox(painter);
    // Restore the modelview matrix.
    painter->modelViewMatrix().pop();
  }
}
/** Adds an atom of this molecule to the scene.
 *
 * @param _atom atom to add
 */
void GLMoleculeObject_molecule::atomInserted(const atomId_t _id)
{
  LOG(3, "INFO: GLMoleculeObject_molecule: Received signal atomInserted for atom "+toString(_id)+".");
  TesselationHullUptodate = false;
  isBoundingBoxUptodate = false;
  GLMoleculeObject_atom *atomObject = new GLMoleculeObject_atom(GLMoleculeObject::meshSphere, this, _id);
  ASSERT( atomObject != NULL,
      "GLMoleculeObject_molecule::atomInserted - could not create atom object for "+toString(_id));
  AtomNodeMap::iterator iter = AtomsinSceneMap.find(_id);
  ASSERT(iter == AtomsinSceneMap.end(),
      "GLMoleculeObject_molecule::atomInserted - same atom with id "+toString(_id)+" added again.");
  AtomsinSceneMap.insert( make_pair(_id, atomObject) );
  qRegisterMetaType("atomId_t");
  qRegisterMetaType("bond::ptr");
  qRegisterMetaType("GLMoleculeObject_bond::SideOfBond");
  connect (atomObject, SIGNAL(clicked(atomId_t)), this, SIGNAL(atomClicked(atomId_t)));
  connect (atomObject, SIGNAL(changed()), this, SIGNAL(changed()));
  connect (atomObject, SIGNAL(hoverChanged(GLMoleculeObject *)), this, SIGNAL(changed()));
  connect (atomObject, SIGNAL(hoverChanged(GLMoleculeObject *)), this, SLOT(hoverChangedSignalled(GLMoleculeObject *)));
  connect (atomObject, SIGNAL(selectionChanged()), this, SIGNAL(changed()));
  connect (atomObject, SIGNAL(BondsInserted(const bond::ptr , const GLMoleculeObject_bond::SideOfBond)), this, SLOT(bondInserted(const bond::ptr , const GLMoleculeObject_bond::SideOfBond)));
  connect (atomObject, SIGNAL(indexChanged(GLMoleculeObject_atom*, int, int)), this, SLOT(changeAtomId(GLMoleculeObject_atom*, int, int)));
  isBoundingBoxUptodate = false;
  if (m_objectId  == -1)
    setObjectId(_id);
  // add all bonds
  const atom * const Walker = const_cast(World::getInstance()).
      getAtom(AtomById(_id));
  if (Walker != NULL)
    addAtomBonds(Walker);
  else
    ELOG(1, "GLMoleculeObject_atom disappeared while about to add bonds.");
  emit changeOccured();
}
/** Removes an atom of this molecule from the scene.
 *
 * We just the id as the atom might have already been destroyed.
 *
 * @param _id id of atom to remove
 */
void GLMoleculeObject_molecule::atomRemoved(const atomId_t _id)
{
  LOG(3, "INFO: GLMoleculeObject_molecule: Received signal atomRemoved for atom "+toString(_id)+".");
  // bonds are removed by signal coming from ~bond
  TesselationHullUptodate = false;
  isBoundingBoxUptodate = false;
  if ((unsigned int)m_objectId == _id)
    setObjectId(-1);
  // remove atoms
  AtomNodeMap::iterator iter = AtomsinSceneMap.find(_id);
  ASSERT(iter != AtomsinSceneMap.end(),
      "GLMoleculeObject_molecule::atomRemoved() - atom "+toString(_id)+" not on display.");
  GLMoleculeObject_atom *atomObject = iter->second;
  AtomsinSceneMap.erase(iter);
  atomObject->disconnect();
  delete atomObject;
  isBoundingBoxUptodate = false;
  emit changeOccured();
}
void GLMoleculeObject_molecule::hoverChangedSignalled(GLMoleculeObject *ob)
{
  // Find the atom, ob corresponds to.
  hoverAtomId = -1;
  GLMoleculeObject_atom *atomObject = dynamic_cast(ob);
  if (atomObject){
    for (AtomNodeMap::iterator iter = AtomsinSceneMap.begin();iter != AtomsinSceneMap.end(); ++ iter){
      if (iter->second == atomObject)
        hoverAtomId = iter->first;
    }
    // Propagate signal.
    emit hoverChanged(hoverAtomId);
  } else {
    // Find the atom, ob corresponds to.
    GLMoleculeObject_molecule *moleculeObject = dynamic_cast(ob);
    if (moleculeObject == this){
      // Propagate signal.
      emit hoverChanged(moleculeid, 0);
    }
  }
}
/** Helper function to get bond ids in the correct order for BondNodeMap.
 *
 * \return pair of ids in correct order.
 */
GLMoleculeObject_molecule::BondIds GLMoleculeObject_molecule::getBondIds(
    const bond::ptr _bond,
    const enum GLMoleculeObject_bond::SideOfBond _side)
{
  BondIds ids;
  switch (_side) {
    case GLMoleculeObject_bond::left:
      ids = std::make_pair(_bond->leftatom->getId(), _bond->rightatom->getId());
      break;
    case GLMoleculeObject_bond::right:
      ids = std::make_pair(_bond->rightatom->getId(), _bond->leftatom->getId());
      break;
  }
  return ids;
}
/** Adds a bond to the scene.
 *
 * @param _bond bond to add
 * @param side which side of the bond (left or right)
 */
void GLMoleculeObject_molecule::bondInserted(const bond::ptr _bond, const enum GLMoleculeObject_bond::SideOfBond _side)
{
  LOG(3, "INFO: GLWorldScene::bondInserted() - Adding bond "+toString(*_bond)+".");
  //LOG(4, "INFO: Currently present bonds " << BondsinSceneMap << ".");
  const BondIds ids = getBondIds(_bond, _side);
  BondNodeMap::iterator iter = BondsinSceneMap.find(ids);
  if (iter == BondsinSceneMap.end()) {
    GLMoleculeObject_bond * bondObject =
        new GLMoleculeObject_bond(GLMoleculeObject::meshCylinder, this, ids, _side);
    connect (
        bondObject, SIGNAL(BondRemoved(const atomId_t, const atomId_t)),
        this, SLOT(bondRemoved(const atomId_t, const atomId_t)));
    connect (bondObject, SIGNAL(changed()), this, SIGNAL(changed()));
    BondsinSceneMap.insert( make_pair(ids, bondObject) );
  //    BondIdsinSceneMap.insert( Leftids );
  } else {
    iter->second->resetPosition();
    iter->second->resetWidth();
  }
  emit changeOccured();
}
/** Removes a bond from the scene.
 *
 * @param _bond bond to remove
 */
void GLMoleculeObject_molecule::bondRemoved(const atomId_t leftnr, const atomId_t rightnr)
{
  LOG(3, "INFO: GLWorldScene::bondRemoved() - Removing bond between "+toString(leftnr)+" and "+toString(rightnr)+".");
  {
    // left bond
    const BondIds Leftids( make_pair(leftnr, rightnr) );
    BondNodeMap::iterator leftiter = BondsinSceneMap.find( Leftids );
    ASSERT(leftiter != BondsinSceneMap.end(),
        "GLWorldScene::bondRemoved() - bond "+toString(leftnr)+"-"
        +toString(rightnr)+" not on display.");
    GLMoleculeObject_bond *bondObject = leftiter->second;
    bondObject->disconnect();
    BondsinSceneMap.erase(leftiter);
    delete bondObject; // is done by signal from bond itself
    //LOG(4, "INFO: Still present bonds " << BondsinSceneMap << ".");
  }
  emit changeOccured();
}
void GLMoleculeObject_molecule::setVisible(bool value)
{
  // first update the mesh if we are going to be visible now
  if (value)
    updateTesselationHull();
  // then emit onward
  GLMoleculeObject::setVisible(value);
}
void GLMoleculeObject_molecule::updateTesselationHull()
{
  if (!TesselationHullUptodate) {
    updateMesh(createMoleculeMesh(moleculeid, parent()));
    TesselationHullUptodate = true;
  }
}
std::ostream &operator<<(std::ostream &ost, const GLMoleculeObject_molecule::BondIds &t)
{
  ost << t.first << "," << t.second;
  return ost;
}
void GLMoleculeObject_molecule::wasClicked()
{
  LOG(4, "INFO: GLMoleculeObject_molecule: atom " << moleculeid << " has been clicked");
  emit moleculeClicked(moleculeid);
}
void GLMoleculeObject_molecule::changeAtomId(GLMoleculeObject_atom *ob, int oldId, int newId)
{
  LOG(3, "INFO: GLMoleculeObject_molecule - change atom id " << oldId << " to " << newId << ".");
  // Remove from map.
  AtomNodeMap::iterator iter = AtomsinSceneMap.find(oldId);
  ASSERT(iter != AtomsinSceneMap.end(),
      "GLMoleculeObject_molecule::changeAtomId() - atom with old id "+toString(oldId)+" not on display.");
  ASSERT(iter->second == ob,
      "GLMoleculeObject_molecule::changeAtomId() - atom with id "
      +toString(oldId)+" does not match with object in AtomsinSceneMap.");
  AtomsinSceneMap.erase(iter);
  // Reinsert with new id.
  {
    AtomNodeMap::iterator iter = AtomsinSceneMap.find(newId);
    ASSERT(iter == AtomsinSceneMap.end(),
          "GLMoleculeObject_molecule::changeAtomId() - atom with new id "+toString(newId)+" already known.");
  }
  AtomsinSceneMap.insert( make_pair(newId, ob) );
}