/*
 * Project: MoleCuilder
 * Description: creates and alters molecular systems
 * Copyright (C)  2015 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 .
 */
/*
 * QtObservedAtom.cpp
 *
 *  Created on: Oct 28, 2015
 *      Author: heber
 */
// include config.h
#ifdef HAVE_CONFIG_H
#include 
#endif
#include "QtObservedAtom.hpp"
#include "UIElements/Qt4/InstanceBoard/QtObservedInstanceBoard.hpp"
//#include "CodePatterns/MemDebug.hpp"
#include 
#include "Atom/atom.hpp"
#include "Bond/bond.hpp"
#include "Descriptors/AtomIdDescriptor.hpp"
#include "Element/element.hpp"
#include "World.hpp"
#include "WorldTime.hpp"
#include "UIElements/Qt4/InstanceBoard/ObservedValue_wCallback.hpp"
using namespace boost::assign;
static const Observable::channels_t getAtomBondsChannels()
{
  Observable::channels_t channels;
  channels += AtomObservable::BondsAdded, AtomObservable::BondsRemoved;
  return channels;
}
static const Observable::channels_t getAllObservedChannels()
{
  Observable::channels_t channels;
  channels +=
      AtomObservable::IndexChanged,
      AtomObservable::BondsAdded,
      AtomObservable::BondsRemoved,
      AtomObservable::MoleculeChanged,
      AtomObservable::NameChanged,
      AtomObservable::ElementChanged,
      AtomObservable::PositionChanged,
      AtomObservable::SelectionChanged;
  return channels;
}
// static entities
const Observable::channels_t
QtObservedAtom::AtomIndexChannels(1, AtomObservable::IndexChanged);
const Observable::channels_t
QtObservedAtom::AtomBondsChannels(getAtomBondsChannels());
const Observable::channels_t
QtObservedAtom::AtomElementChannels(1, AtomObservable::ElementChanged);
const Observable::channels_t
QtObservedAtom::MoleculeChangedChannels(1, AtomObservable::MoleculeChanged);
const Observable::channels_t
QtObservedAtom::AtomNameChannels(1, AtomObservable::NameChanged);
const Observable::channels_t
QtObservedAtom::AtomPositionChannels(1, AtomObservable::PositionChanged);
const Observable::channels_t
QtObservedAtom::AtomVelocityChannels(1, AtomObservable::VelocityChanged);
const Observable::channels_t
QtObservedAtom::AtomForceChannels(1, AtomObservable::ForceChanged);
const Observable::channels_t
QtObservedAtom::AtomSelectedChannels(1, AtomObservable::SelectionChanged);
QtObservedAtom::QtObservedAtom(
    const atomId_t _id,
    const atom * const _atom,
    QtObservedInstanceBoard &_board,
    QWidget * _parent) :
  QWidget(_parent),
  Observer("QtObservedAtom"),
  subjectKilledCount(0),
  AllsignedOnChannels(getAllObservedChannels().size()),
  signedOffChannels(0),
  owner(NULL),
  index(static_cast(const_cast(this))),
  board(_board),
  BoardIsGone(false),
  ObservedValues(QtObservedAtom::MAX_ObservedTypes)
{
  boost::function atomSubjectKilled(
      boost::bind(&QtObservedAtom::countValuesSubjectKilled,
          boost::ref(*this),
          boost::bind(&QtObservedAtom::getIndex, boost::ref(*this))));
  initObservedValues( ObservedValues, _id, _atom, atomSubjectKilled);
  // activating Observer is done by ObservedValueContainer when it's inserted
}
QtObservedAtom::~QtObservedAtom()
{
  boost::any_cast *>(ObservedValues[AtomIndex])->noteCallBackIsGone();
  boost::any_cast *>(ObservedValues[AtomBonds])->noteCallBackIsGone();
  boost::any_cast *>(ObservedValues[AtomElement])->noteCallBackIsGone();
  boost::any_cast *>(ObservedValues[MoleculeRef])->noteCallBackIsGone();
  boost::any_cast *>(ObservedValues[AtomName])->noteCallBackIsGone();
  boost::any_cast *>(ObservedValues[AtomPosition])->noteCallBackIsGone();
  boost::any_cast *>(ObservedValues[AtomVelocity])->noteCallBackIsGone();
  boost::any_cast *>(ObservedValues[AtomForce])->noteCallBackIsGone();
  boost::any_cast *>(ObservedValues[AtomSelected])->noteCallBackIsGone();
  deactivateObserver();
  destroyObservedValues(ObservedValues);
}
const atom * const QtObservedAtom::getAtomConst(const atomId_t _id)
{
  const atom * const _atom = const_cast(World::getInstance()).
      getAtom(AtomById(_id));
  return _atom;
}
atom * const QtObservedAtom::getAtom(const atomId_t _id)
{
  atom * const _atom = World::getInstance().getAtom(AtomById(_id));
  return _atom;
}
#ifdef HAVE_INLINE
inline
#endif
atomId_t QtObservedAtom::updateIndex(const atom &_atomref)
{
  return _atomref.getId();
}
QtObservedAtom::ListOfBonds_t QtObservedAtom::updateBonds(
    const atom &_atom)
{
  ListOfBonds_t ListOfBonds;
  // make sure bonds is up-to-date
  const BondList ListBonds = _atom.getListOfBonds();
  for (BondList::const_iterator iter = ListBonds.begin();
      iter != ListBonds.end();
      ++iter)
    ListOfBonds.insert( ListOfBonds.end(), std::make_pair(
        (*iter)->leftatom->getId(),
        (*iter)->rightatom->getId()) );
  return ListOfBonds;
}
#ifdef HAVE_INLINE
inline
#endif
atomicNumber_t QtObservedAtom::updateElement(
    const atom &_atom)
{
  return _atom.getElementNo();
}
#ifdef HAVE_INLINE
inline
#endif
QtObservedMolecule* QtObservedAtom::updateMoleculeRef(
    const atom &_atom)
{
  if (_atom.getMolecule() != NULL) {
    const moleculeId_t molid = _atom.getMolecule()->getId();
    const QtObservedMolecule::ptr mol = board.getObservedMolecule(molid);
    ASSERT(mol,
        "QtObservedAtom::updateMoleculeRef() - could not update the ref, "
        +toString(molid)+" unknown to Instance board?");
    return mol.get();
  } else {
    return (QtObservedMolecule*)NULL;
  }
}
#ifdef HAVE_INLINE
inline
#endif
std::string QtObservedAtom::updateName(
    const atom &_atom)
{
  return _atom.getName();
}
#ifdef HAVE_INLINE
inline
#endif
Vector QtObservedAtom::updatePosition(
    const atom &_atom)
{
  return _atom.getPosition();
}
#ifdef HAVE_INLINE
inline
#endif
Vector QtObservedAtom::updateVelocity(
    const atom &_atom)
{
  return _atom.getAtomicVelocity();
}
#ifdef HAVE_INLINE
inline
#endif
Vector QtObservedAtom::updateForce(
    const atom &_atom)
{
  return _atom.getAtomicForce();
}
#ifdef HAVE_INLINE
inline
#endif
bool QtObservedAtom::updateSelected(
    const atom &_atom)
{
  return _atom.getSelected();
}
void QtObservedAtom::update(Observable *publisher)
{
  ASSERT(0, "QtObservedAtom::update() - we are not signed on for global updates.");
}
void QtObservedAtom::subjectKilled(Observable *publisher)
{
  ++signedOffChannels;
  checkForRemoval(getIndex());
}
void QtObservedAtom::countValuesSubjectKilled(ObservedValue_Index_t _id)
{
  ASSERT( _id == getIndex(),
      "QtObservedAtom::countValuesSubjectKilled() - atom "+toString(getIndex())
      +" received countValuesSubjectKilled for atom id "+toString(_id)+".");
  ++subjectKilledCount;
  checkForRemoval(_id);
}
#ifdef HAVE_INLINE
inline
#endif
void QtObservedAtom::checkForRemoval(ObservedValue_Index_t _id)
{
  if ((signedOffChannels == AllsignedOnChannels) && (subjectKilledCount == MAX_ObservedTypes)) {
    // remove owner: no more signOff needed
    owner = NULL;
    emit atomRemoved();
    if (!BoardIsGone) {
      board.markObservedAtomAsDisconnected(_id);
      board.markObservedAtomForErase(_id);
    }
  }
}
void QtObservedAtom::recieveNotification(Observable *publisher, Notification_ptr notification)
{
  // ObservedValues have been updated before, hence convert updates to Qt's signals
  if (publisher == WorldTime::getPointer()) {
    // force update of all changes that may occur from one time step to another
    boost::any_cast *>(
        ObservedValues[AtomPosition])->forceUpdate();
    boost::any_cast *>(
        ObservedValues[AtomVelocity])->forceUpdate();
    boost::any_cast *>(
        ObservedValues[AtomForce])->forceUpdate();
    boost::any_cast *>(
        ObservedValues[MoleculeRef])->forceUpdate();
    // then tell Qt part about updates
    emit positionChanged();
    emit velocityChanged();
    emit forceChanged();
    emit moleculeChanged();
  } else {
    switch (notification->getChannelNo()) {
      case AtomObservable::IndexChanged:
      {
        emit indexChanged();
        break;
      }
      case AtomObservable::BondsAdded:
      case AtomObservable::BondsRemoved:
        emit bondsChanged();
        break;
      case AtomObservable::ElementChanged:
        emit elementChanged();
        break;
      case AtomObservable::MoleculeChanged:
        emit moleculeChanged();
        break;
      case AtomObservable::NameChanged:
        emit nameChanged();
        break;
      case AtomObservable::PositionChanged:
        emit positionChanged();
        break;
      case AtomObservable::VelocityChanged:
        emit velocityChanged();
        break;
      case AtomObservable::ForceChanged:
        emit forceChanged();
        break;
      case AtomObservable::SelectionChanged:
        emit selectedChanged();
        break;
      default:
        ASSERT(0, "QtObservedAtom::recieveNotification() - we are not signed on to channel "
            +toString(notification->getChannelNo())+" of the atom.");
        break;
    }
  }
}
void QtObservedAtom::activateObserver()
{
  atom * atomref = getAtom(getAtomIndex());
  if (atomref != NULL) {
    Observable::channels_t channels = getAllObservedChannels();
    owner = static_cast(atomref);
    for (Observable::channels_t::const_iterator iter = channels.begin();
        iter != channels.end(); ++iter)
      owner->signOn(this, *iter);
    if (!BoardIsGone)
      board.markObservedAtomAsConnected(getIndex());
  } else
    signedOffChannels = AllsignedOnChannels;
  WorldTime::getInstance().signOn(this, WorldTime::TimeChanged);
}
void QtObservedAtom::deactivateObserver()
{
  // sign Off
  if (owner != NULL) {
    Observable::channels_t channels = getAllObservedChannels();
    for (Observable::channels_t::const_iterator iter = channels.begin();
        iter != channels.end(); ++iter)
      owner->signOff(this, *iter);
    owner = NULL;
    signedOffChannels = AllsignedOnChannels;
    if (!BoardIsGone)
      board.markObservedAtomAsDisconnected(getIndex());
  }
  WorldTime::getInstance().signOff(this, WorldTime::TimeChanged);
}
void QtObservedAtom::initObservedValues(
    ObservedValues_t &_ObservedValues,
    const atomId_t _id,
    const atom * const _atomref,
    const boost::function &_subjectKilled)
{
  ASSERT( _ObservedValues.size() == MAX_ObservedTypes,
      "QtObservedAtom::initObservedValues() - given ObservedValues has not correct size.");
  // fill ObservedValues: then all the other that need index
  const boost::function AtomIndexUpdater(
      boost::bind(&QtObservedAtom::updateIndex, boost::cref(*_atomref)));
  const boost::function AtomBondsUpdater(
      boost::bind(&QtObservedAtom::updateBonds, boost::cref(*_atomref)));
  const boost::function AtomElementUpdater(
      boost::bind(&QtObservedAtom::updateElement, boost::cref(*_atomref)));
  const boost::function AtomNameUpdater(
      boost::bind(&QtObservedAtom::updateName, boost::cref(*_atomref)));
  const boost::function AtomPositionUpdater(
      boost::bind(&QtObservedAtom::updatePosition, boost::cref(*_atomref)));
  const boost::function AtomVelocityUpdater(
      boost::bind(&QtObservedAtom::updateVelocity, boost::cref(*_atomref)));
  const boost::function AtomForceUpdater(
      boost::bind(&QtObservedAtom::updateForce, boost::cref(*_atomref)));
  const boost::function AtomSelectedUpdater(
      boost::bind(&QtObservedAtom::updateSelected, boost::cref(*_atomref)));
  const boost::function MoleculeRefUpdater(
      boost::bind(&QtObservedAtom::updateMoleculeRef, this, boost::cref(*_atomref)));
  _ObservedValues[AtomIndex] = new ObservedValue_wCallback(
      _atomref,
      AtomIndexUpdater,
      "AtomIndex_"+toString(_id),
      _id,
      AtomIndexChannels,
      _subjectKilled);
  _ObservedValues[AtomBonds] = new ObservedValue_wCallback(
      _atomref,
      AtomBondsUpdater,
      "AtomBonds_"+toString(_id),
      AtomBondsUpdater(),
      AtomBondsChannels,
      _subjectKilled);
  _ObservedValues[AtomElement] = new ObservedValue_wCallback(
      _atomref,
      AtomElementUpdater,
      "AtomElement"+toString(_id),
      AtomElementUpdater(),
      AtomElementChannels,
      _subjectKilled);
  _ObservedValues[AtomName] = new ObservedValue_wCallback(
      _atomref,
      AtomNameUpdater,
      "AtomName"+toString(_id),
      AtomNameUpdater(),
      AtomNameChannels,
      _subjectKilled);
  _ObservedValues[AtomPosition] = new ObservedValue_wCallback(
      _atomref,
      AtomPositionUpdater,
      "AtomPosition_"+toString(_id),
      AtomPositionUpdater(),
      AtomPositionChannels,
      _subjectKilled);
  _ObservedValues[AtomVelocity] = new ObservedValue_wCallback(
      _atomref,
      AtomVelocityUpdater,
      "AtomVelocity_"+toString(_id),
      AtomVelocityUpdater(),
      AtomVelocityChannels,
      _subjectKilled);
  _ObservedValues[AtomForce] = new ObservedValue_wCallback(
      _atomref,
      AtomForceUpdater,
      "AtomForce_"+toString(_id),
      AtomForceUpdater(),
      AtomForceChannels,
      _subjectKilled);
  _ObservedValues[AtomSelected] = new ObservedValue_wCallback(
      _atomref,
      AtomSelectedUpdater,
      "AtomSelected_"+toString(_id),
      AtomSelectedUpdater(),
      AtomSelectedChannels,
      _subjectKilled);
  _ObservedValues[MoleculeRef] = new ObservedValue_wCallback(
      _atomref,
      MoleculeRefUpdater,
      "AtomMoleculeIndex"+toString(_id),
      MoleculeRefUpdater(),
      MoleculeChangedChannels,
      _subjectKilled);
}
void QtObservedAtom::destroyObservedValues(
    std::vector &_ObservedValues)
{
  delete boost::any_cast *>(_ObservedValues[AtomIndex]);
  delete boost::any_cast *>(_ObservedValues[AtomBonds]);
  delete boost::any_cast *>(_ObservedValues[AtomElement]);
  delete boost::any_cast *>(_ObservedValues[AtomName]);
  delete boost::any_cast *>(_ObservedValues[AtomPosition]);
  delete boost::any_cast *>(_ObservedValues[AtomVelocity]);
  delete boost::any_cast *>(_ObservedValues[AtomForce]);
  delete boost::any_cast *>(_ObservedValues[AtomSelected]);
  delete boost::any_cast *>(_ObservedValues[MoleculeRef]);
  _ObservedValues.clear();
}
ObservedValue_Index_t QtObservedAtom::getIndex() const
{
  ASSERT( index != NULL,
      "QtObservedAtom::getIndex() - index is NULL");
  return index;
}
const atomId_t& QtObservedAtom::getAtomIndex() const
{
  return boost::any_cast *>(ObservedValues[AtomIndex])->get();
}
const QtObservedAtom::ListOfBonds_t& QtObservedAtom::getAtomBonds() const
{
  return boost::any_cast *>(ObservedValues[AtomBonds])->get();
}
const atomicNumber_t& QtObservedAtom::getAtomElement() const
{
  return boost::any_cast *>(ObservedValues[AtomElement])->get();
}
const std::string& QtObservedAtom::getAtomName() const
{
  return boost::any_cast *>(ObservedValues[AtomName])->get();
}
const Vector& QtObservedAtom::getAtomPosition() const
{
  return boost::any_cast *>(ObservedValues[AtomPosition])->get();
}
const Vector& QtObservedAtom::getAtomVelocity() const
{
  return boost::any_cast *>(ObservedValues[AtomVelocity])->get();
}
const Vector& QtObservedAtom::getAtomForce() const
{
  return boost::any_cast *>(ObservedValues[AtomForce])->get();
}
const bool QtObservedAtom::getAtomSelected() const
{
  return boost::any_cast *>(ObservedValues[AtomSelected])->get();
}
const QtObservedMolecule* const QtObservedAtom::getMoleculeRef() const
{
  return boost::any_cast *>(ObservedValues[MoleculeRef])->get();
}