/*
 * 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_bond.cpp
 *
 *  Created on: Aug 17, 2011
 *      Author: heber
 */
// include config.h
#ifdef HAVE_CONFIG_H
#include 
#endif
#include "GLMoleculeObject_bond.hpp"
#include 
#include 
#include "UIElements/Qt4/InstanceBoard/QtObservedInstanceBoard.hpp"
#include "CodePatterns/MemDebug.hpp"
#include 
#include "CodePatterns/Assert.hpp"
#include "CodePatterns/Log.hpp"
#include "CodePatterns/Observer/Notification.hpp"
#include "CodePatterns/Observer/ObserverLog.hpp"
#include "Descriptors/AtomIdDescriptor.hpp"
#include "Atom/atom.hpp"
#include "Bond/bond.hpp"
#include "Element/element.hpp"
#include "Helpers/defs.hpp"
#include "LinearAlgebra/Line.hpp"
#include "LinearAlgebra/Vector.hpp"
#include "UIElements/Qt4/InstanceBoard/ObservedValue_wCallback.hpp"
#include "World.hpp"
// static entities
const Observable::channels_t
GLMoleculeObject_bond::IndexChannels(1, AtomObservable::IndexChanged);
const Observable::channels_t
GLMoleculeObject_bond::BondPositionChannels(1, AtomObservable::PositionChanged);
const Observable::channels_t
GLMoleculeObject_bond::BondDegreeChannels(1, BondObservable::DegreeChanged);
const Observable::channels_t
GLMoleculeObject_bond::BondElementChannels(1, AtomObservable::ElementChanged);
static GLMoleculeObject_bond::bondIds_t getBondIdsForIds(
    const boost::function &_left,
    const boost::function &_right)
{
  return std::make_pair(_left(), _right());
}
GLMoleculeObject_bond::GLMoleculeObject_bond(
    QGLSceneNode *mesh[],
    QObject *parent,
    const bondIds_t bondIds,
    const enum SideOfBond side) :
  GLMoleculeObject(mesh, parent),
  Observer(std::string("GLMoleculeObject_bond")
      +toString(bondIds.first)
      +std::string("-")
      +toString(bondIds.second)),
  leftowner(getAtomConst(bondIds.first)),
  rightowner(getAtomConst(bondIds.second)),
  bondowner(getAtomConst(bondIds.first)->getBond(getAtomConst(bondIds.second)).get()),
  BondSide(side),
  ObservedValues(MAX_ObservedTypes),
  subjectKilledCount(0),
  leftobservable_enabled(false),
  rightobservable_enabled(false),
  board_subjectKilled(
      boost::bind(
          static_cast(
              &GLMoleculeObject_bond::countsubjectKilled),
          this))
{
  boost::function leftsubjectKilled =
      boost::bind(
          static_cast(
              &GLMoleculeObject_bond::countsubjectKilled),
          this, _1);
  boost::function rightsubjectKilled =
      boost::bind(
          static_cast(
              &GLMoleculeObject_bond::countsubjectKilled)
              , this, _1);
  boost::function bondsubjectKilled =
      boost::bind(
          static_cast(
              &GLMoleculeObject_bond::countsubjectKilled)
              , this, _1);
  initObservedValues(
      ObservedValues,
      bondIds.first,
      bondIds.second,
      leftowner,
      rightowner,
      bondowner,
      leftsubjectKilled,
      rightsubjectKilled,
      bondsubjectKilled);
  init();
}
GLMoleculeObject_bond::GLMoleculeObject_bond(
    QGLSceneNode *mesh[],
    QObject *parent,
    const bondIds_t bondIds,
    const enum SideOfBond side,
    std::vector &_ObservedValues,
    const boost::function &_subjectKilled) :
  GLMoleculeObject(mesh, parent),
  Observer(std::string("GLMoleculeObject_bond")
      +toString(bondIds.first)
      +std::string("-")
      +toString(bondIds.second)),
  leftowner(getAtomConst(bondIds.first)),
  rightowner(getAtomConst(bondIds.second)),
  bondowner(getAtomConst(bondIds.first)->getBond(getAtomConst(bondIds.second)).get()),
  BondSide(side),
  ObservedValues(_ObservedValues),
  subjectKilledCount(0),
  leftobservable_enabled(false),
  rightobservable_enabled(false),
  board_subjectKilled(_subjectKilled)
{
  init();
}
void GLMoleculeObject_bond::init()
{
  // sign on as observer (obtain non-const instance before)
  bondowner->signOn(this, BondObservable::BondRemoved);
  bondowner->signOn(this, BondObservable::DegreeChanged);
  bond_enabled = true;
  leftowner->signOn(this, AtomObservable::PositionChanged);
  leftowner->signOn(this, AtomObservable::ElementChanged);
  leftobservable_enabled = true;
  rightowner->signOn(this, AtomObservable::PositionChanged);
  rightowner->signOn(this, AtomObservable::ElementChanged);
  rightobservable_enabled = true;
  resetElement();
  resetPosition();
  resetWidth();
  connect(this, SIGNAL(elementChanged()), this, SLOT(resetElement()), Qt::QueuedConnection);
  connect(this, SIGNAL(positionChanged()), this, SLOT(resetPosition()), Qt::QueuedConnection);
  connect(this, SIGNAL(degreeChanged()), this, SLOT(resetWidth()), Qt::QueuedConnection);
}
GLMoleculeObject_bond::~GLMoleculeObject_bond()
{
  LOG(3, "DEBUG: Destroying  GLMoleculeObject_bond to bond [" <<
      getleftIndex() << "," << getrightIndex() << "] and side " << BondSide << ".");
  // signOff() if not already done
  removeChannels();
  destroyObservedValues(ObservedValues);
}
void GLMoleculeObject_bond::removeChannels()
{
  // at this point both atoms should still be alive, hence we may safely sign off
  // from the AtomObservable itself
  if (bond_enabled) {
    if (bondowner != NULL) {
      bondowner->signOff(this, BondObservable::BondRemoved);
      bondowner->signOff(this, BondObservable::DegreeChanged);
    }
    bond_enabled = false;
  }
  if (leftobservable_enabled) {
    if (leftowner != NULL) {
      leftowner->signOff(this, AtomObservable::PositionChanged);
      leftowner->signOff(this, AtomObservable::ElementChanged);
    }
    leftobservable_enabled = false;
  }
  if (rightobservable_enabled) {
    if (rightowner != NULL) {
      rightowner->signOff(this, AtomObservable::PositionChanged);
      rightowner->signOff(this, AtomObservable::ElementChanged);
    }
    rightobservable_enabled = false;
  }
}
void GLMoleculeObject_bond::update(Observable *publisher)
{
  ASSERT(0, "GLMoleculeObject_bond::update() - we are not signed on for any global updates.");
}
void GLMoleculeObject_bond::subjectKilled(Observable *publisher)
{
  // we signOff from all other sources
  removeChannels();
  // check whether we should be removed
  board_subjectKilled(std::make_pair(getleftIndex(), getrightIndex()));
}
void GLMoleculeObject_bond::recieveNotification(Observable *publisher, Notification_ptr notification)
{
#ifdef LOG_OBSERVER
  if (publisher == static_cast(bondowner)) {
    observerLog().addMessage() << "++ Update of Observer "
        << observerLog().getName(static_cast(this))
        << " received notification from bond for channel "
        << notification->getChannelNo() << ".";
  } else if (publisher == static_cast(leftowner)) {
    observerLog().addMessage() << "++ Update of Observer "
        << observerLog().getName(static_cast(this))
        << " received notification from leftatom " << getleftIndex() << " for channel "
        << notification->getChannelNo() << ".";
  } else if (publisher == static_cast(rightowner)) {
    observerLog().addMessage() << "++ Update of Observer "
        << observerLog().getName(static_cast(this))
        << " received notification from rightatom " << getrightIndex() << " for channel "
        << notification->getChannelNo() << ".";
  }
#endif
  if (publisher == static_cast(bondowner)){
    switch (notification->getChannelNo()) {
    case BondObservable::BondRemoved:
//      removeMe();
      break;
    case BondObservable::DegreeChanged:
      emit degreeChanged();
      break;
    default:
      ASSERT(0, "GLMoleculeObject_bond::recieveNotification() - unknown signal.");
      break;
    }
  } else {
    // from an atom
    switch (notification->getChannelNo()) {
    case AtomObservable::PositionChanged:
      LOG(2, "INFO: Received notification of PositionChanged.");
      emit positionChanged();
      break;
    case AtomObservable::ElementChanged:
      LOG(2, "INFO: Received notification of ElementChanged.");
      emit elementChanged();
      break;
    default:
      break;
    }
  }
}
atomId_t GLMoleculeObject_bond::updateIndex()
{
  return const_cast(World::getInstance()).lastChangedAtomId();
}
Vector GLMoleculeObject_bond::updateLeftPosition(
    const boost::function &_getLeftAtomIndex)
{
  const atom * const _atom = getAtomConst(_getLeftAtomIndex());
  return _atom->getPosition();
}
Vector GLMoleculeObject_bond::updateRightPosition(
    const boost::function &_getRightAtomIndex)
{
  const atom * const _atom = getAtomConst(_getRightAtomIndex());
  return _atom->getPosition();
}
atomicNumber_t GLMoleculeObject_bond::updateLeftElement(
    const boost::function &_getLeftAtomIndex)
{
  const atom * const _atom = getAtomConst(_getLeftAtomIndex());
  return _atom->getElementNo();
}
atomicNumber_t GLMoleculeObject_bond::updateRightElement(
    const boost::function &_getRightAtomIndex)
{
  const atom * const _atom = getAtomConst(_getRightAtomIndex());
  return _atom->getElementNo();
}
int GLMoleculeObject_bond::updateDegree(
    const boost::function &_getLeftAtomIndex,
    const boost::function &_getRightAtomIndex)
{
  const atom * const _leftatom = const_cast(World::getInstance()).
      getAtom(AtomById(_getLeftAtomIndex()));
  const atom * const _rightatom = const_cast(World::getInstance()).
      getAtom(AtomById(_getRightAtomIndex()));
  if ((_leftatom != NULL) && (_rightatom != NULL)) {
    bond::ptr _bond = _leftatom->getBond(_rightatom);
    return _bond->getDegree();
  } else {
    return 1;
  }
}
void GLMoleculeObject_bond::resetElement()
{
  size_t elementno = getrightElement();
  QGLMaterial *elementmaterial = getMaterial(elementno);
  setMaterial(elementmaterial);
}
void GLMoleculeObject_bond::resetWidth()
{
  const double factor = 1.0f+.5f*(getDegree()-1);
  LOG(2, "DEBUG: GLMoleculeObject_bond::resetWidth() - setting bond's width to " << factor << ".");
  setScaleX(factor);
  setScaleY(factor);
  emit changed();
}
void GLMoleculeObject_bond::resetPosition()
{
  Vector Position = getleftPosition();
  Vector OtherPosition = getrightPosition();
  const double distance =
      Position.distance(OtherPosition)/2.;
  setScaleZ(distance);
  // calculate position
  Vector Z(unitVec[2]); // cylinder are initially aligned along the Z axis
  Vector zeroVec(0.,0.,0.);
  Vector a,b;
  Vector OtherAxis;
  double alpha;
  a = Position - OtherPosition;
  // construct rotation axis
  b = a;
  b.VectorProduct(Z);
  Line axis(zeroVec, b);
  // calculate rotation angle
  alpha = a.Angle(Z);
  // construct other axis to check right-hand rule
  OtherAxis = b;
  OtherAxis.VectorProduct(Z);
  // assure right-hand rule for the rotation
  if (a.ScalarProduct(OtherAxis) < MYEPSILON)
    alpha = M_PI-alpha;
  // check
  Vector a_rotated = axis.rotateVector(a, alpha);
  LOG(3, "INFO: Created cylinder from "// << Position << " to " << OtherPosition
      << a << " to " << a_rotated << " around " << b << " by " << alpha/M_PI*180. << ", respectively.");
  // set position (cylinder offset is in its barymetric center)
  Vector OneFourth(Position - 0.75 * a);
  setPosition(QVector3D(OneFourth[0], OneFourth[1], OneFourth[2]));
  setRotationVector(QVector3D(b[0], b[1], b[2]));
  setRotationAngle(alpha/M_PI*180.);
  emit changed();
}
atom * const GLMoleculeObject_bond::getAtom(const atomId_t _id)
{
  atom * const _atom = World::getInstance().getAtom(AtomById(_id));
  return _atom;
}
const atom * const GLMoleculeObject_bond::getAtomConst(const atomId_t _id)
{
  const atom * const _atom = const_cast(World::getInstance()).
      getAtom(AtomById(_id));
  return _atom;
}
void GLMoleculeObject_bond::countsubjectKilled()
{
  ++subjectKilledCount;
  if (subjectKilledCount > ObservedValues.size())
    emit BondRemoved(getleftIndex(), getrightIndex());
}
void GLMoleculeObject_bond::initObservedValues(
    std::vector &_ObservedValues,
    const atomId_t _leftatomId,
    const atomId_t _rightatomId,
    const Observable * const _leftowner,
    const Observable * const _rightowner,
    const Observable * const _bondowner,
    const boost::function &_leftsubjectKilled,
    const boost::function &_rightsubjectKilled,
    const boost::function &_bondsubjectKilled)
{
  /* This is an old note from when the code was still part of cstor's initializer body.
   * TODO: Probably does not apply anymore but has not yet been tested.
   *
   * We must not use boost::cref(this) as "this" has not been properly constructed and seemingly
   * boost::cref tries to do some magic to grasp the inheritance hierarchy which fails because
   * the class has not been fully constructed yet. "This" itself seems to be working fine.
   */
  ASSERT( _ObservedValues.size() == MAX_ObservedTypes,
      "GLMoleculeObject_bond::initObservedValues() - given ObservedValues has not correct size.");
  // fill ObservedValues: index first
  // Note that we only need one as the function just checks on the last changed id
  // and ids cannot be changed simultaneously
  const boost::function AtomIndexUpdater(
      boost::bind(&GLMoleculeObject_bond::updateIndex));
  ObservedValue_wCallback * const LeftIndexObservable =
      new ObservedValue_wCallback(
        _leftowner,
        AtomIndexUpdater,
        "Bonds_LeftAtomIndex_"+toString(_leftatomId),
        _leftatomId,
        IndexChannels,
        _leftsubjectKilled);
  _ObservedValues[leftIndex] = LeftIndexObservable;
  ObservedValue_wCallback * const RightIndexObservable =
      new ObservedValue_wCallback(
        _rightowner,
        AtomIndexUpdater,
        "Bonds_RightAtomIndex_"+toString(_rightatomId),
        _rightatomId,
        IndexChannels,
        _rightsubjectKilled);
  _ObservedValues[rightIndex] = RightIndexObservable;
  const boost::function LeftIndexGetter =
      boost::bind(&ObservedValue_wCallback::get,
          LeftIndexObservable);
  const boost::function RightIndexGetter =
      boost::bind(&ObservedValue_wCallback::get,
          RightIndexObservable);
  // fill ObservedValues: then all the other that need index
  const boost::function LeftPositionUpdater(
      boost::bind(&GLMoleculeObject_bond::updateLeftPosition, LeftIndexGetter));
  const boost::function RightPositionUpdater(
      boost::bind(&GLMoleculeObject_bond::updateRightPosition, RightIndexGetter));
  const boost::function LeftElementUpdater(
      boost::bind(&GLMoleculeObject_bond::updateLeftElement, LeftIndexGetter));
  const boost::function RightElementUpdater(
      boost::bind(&GLMoleculeObject_bond::updateRightElement, RightIndexGetter));
  const boost::function DegreeUpdater(
      boost::bind(&GLMoleculeObject_bond::updateDegree, LeftIndexGetter, RightIndexGetter));
  const boost::function BondIdGetter(
      boost::bind(&getBondIdsForIds, LeftIndexGetter, RightIndexGetter));
  _ObservedValues[leftPosition] = new ObservedValue_wCallback(
      _leftowner,
      LeftPositionUpdater,
      "BondleftPosition_"+toString(_leftatomId),
      LeftPositionUpdater(),
      BondPositionChannels,
      _leftsubjectKilled,
      LeftIndexGetter);
  _ObservedValues[rightPosition] = new ObservedValue_wCallback(
      _rightowner,
      RightPositionUpdater,
      "BondrightPosition_"+toString(_rightatomId),
      RightPositionUpdater(),
      BondPositionChannels,
      _rightsubjectKilled,
      RightIndexGetter);
  _ObservedValues[leftElement] = new ObservedValue_wCallback(
      _leftowner,
      LeftElementUpdater,
      "BondleftElement"+toString(_leftatomId),
      LeftElementUpdater(),
      BondElementChannels,
      _leftsubjectKilled,
      LeftIndexGetter);
  _ObservedValues[rightElement] = new ObservedValue_wCallback(
      _rightowner,
      RightElementUpdater,
      "BondrightElement"+toString(_rightatomId),
      RightElementUpdater(),
      BondElementChannels,
      _rightsubjectKilled,
      RightIndexGetter);
  _ObservedValues[Degree] = new ObservedValue_wCallback(
      _bondowner,
      DegreeUpdater,
      "BondDegree"+toString(_leftatomId)+"_"+toString(_rightatomId),
      DegreeUpdater(),
      BondDegreeChannels,
      _bondsubjectKilled,
      BondIdGetter);
}
void GLMoleculeObject_bond::destroyObservedValues(
    std::vector &_ObservedValues)
{
  delete boost::any_cast *>(_ObservedValues[leftIndex]);
  delete boost::any_cast *>(_ObservedValues[rightIndex]);
  delete boost::any_cast *>(_ObservedValues[leftPosition]);
  delete boost::any_cast *>(_ObservedValues[rightPosition]);
  delete boost::any_cast *>(_ObservedValues[leftElement]);
  delete boost::any_cast *>(_ObservedValues[rightElement]);
  delete boost::any_cast *>(_ObservedValues[Degree]);
  _ObservedValues.clear();
}
const atomId_t& GLMoleculeObject_bond::getleftIndex() const
{
  return boost::any_cast *>(ObservedValues[leftIndex])->get();
}
const atomId_t& GLMoleculeObject_bond::getrightIndex() const
{
  return boost::any_cast *>(ObservedValues[rightIndex])->get();
}
const Vector& GLMoleculeObject_bond::getleftPosition() const
{
  return boost::any_cast *>(ObservedValues[leftPosition])->get();
}
const Vector& GLMoleculeObject_bond::getrightPosition() const
{
  return boost::any_cast *>(ObservedValues[rightPosition])->get();
}
const atomicNumber_t& GLMoleculeObject_bond::getleftElement() const
{
  return boost::any_cast *>(ObservedValues[leftElement])->get();
}
const atomicNumber_t& GLMoleculeObject_bond::getrightElement() const
{
  return boost::any_cast *>(ObservedValues[rightElement])->get();
}
const int& GLMoleculeObject_bond::getDegree() const
{
  return boost::any_cast *>(ObservedValues[Degree])->get();
}