/*
 * 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 .
 */
/*
 * QtObservedMolecule.cpp
 *
 *  Created on: Oct 28, 2015
 *      Author: heber
 */
// include config.h
#ifdef HAVE_CONFIG_H
#include 
#endif
#include "QtObservedMolecule.hpp"
#include "UIElements/Qt4/InstanceBoard/QtObservedInstanceBoard.hpp"
//#include "CodePatterns/MemDebug.hpp"
#include "CodePatterns/Assert.hpp"
#include "CodePatterns/Log.hpp"
#include 
#include 
#include "UIElements/Qt4/InstanceBoard/ObservedValue_wCallback.hpp"
#include "Atom/atom.hpp"
#include "Descriptors/MoleculeIdDescriptor.hpp"
#include "World.hpp"
using namespace boost::assign;
static const Observable::channels_t getAllObservedChannels()
{
  Observable::channels_t channels;
  channels +=
      molecule::AtomInserted,
      molecule::AtomMoved,
      molecule::AtomRemoved,
      molecule::FormulaChanged,
      molecule::IndexChanged,
      molecule::MoleculeNameChanged,
      molecule::BoundingBoxChanged,
      molecule::SelectionChanged;
  return channels;
}
static const Observable::channels_t getAllAtomCountChannels()
{
  Observable::channels_t channels;
  channels +=
      molecule::AtomInserted,
      molecule::AtomRemoved;
  return channels;
}
static const Observable::channels_t getAllCenterChannels()
{
  Observable::channels_t channels;
  channels +=
      molecule::AtomInserted,
      molecule::AtomMoved,
      molecule::AtomRemoved;
  return channels;
}
// static instances
const Observable::channels_t QtObservedMolecule::AtomCountChannels(getAllAtomCountChannels());
const Observable::channels_t QtObservedMolecule::BondCountChannels(getAllAtomCountChannels());
const Observable::channels_t QtObservedMolecule::BoundingBoxChannels(1, molecule::BoundingBoxChanged);
const Observable::channels_t QtObservedMolecule::FormulaStringChannels(1, molecule::FormulaChanged);
const Observable::channels_t QtObservedMolecule::CenterChannels(getAllCenterChannels());
const Observable::channels_t QtObservedMolecule::IndexChannels(1, molecule::IndexChanged);
const Observable::channels_t QtObservedMolecule::NameChannels(1, molecule::MoleculeNameChanged);
const Observable::channels_t QtObservedMolecule::NonHydrogenCountChannels(1, molecule::FormulaChanged);
const Observable::channels_t QtObservedMolecule::SelectedChannels(1, molecule::SelectionChanged);
QtObservedMolecule::QtObservedMolecule(
    const moleculeId_t _id,
    const molecule * const _mol,
    QtObservedInstanceBoard &_board,
    QWidget * _parent) :
  QWidget(_parent),
  Observer("QtObservedMolecule"),
  subjectKilledCount(0),
  AllsignedOnChannels(getAllObservedChannels().size()),
  signedOffChannels(0),
  owner(NULL),
  index(static_cast(const_cast(this))),
  board(_board),
  BoardIsGone(false),
  ObservedValues(QtObservedMolecule::MAX_ObservedTypes)
{
  boost::function moleculeSubjectKilled(
      boost::bind(&QtObservedMolecule::countValuesSubjectKilled,
          boost::ref(*this),
          boost::bind(&QtObservedMolecule::getIndex, boost::ref(*this))));
  initObservedValues(ObservedValues, _id, _mol, moleculeSubjectKilled);
  // activating Observer is done by ObservedValueContainer when it's prepared
}
QtObservedMolecule::~QtObservedMolecule()
{
  boost::any_cast *>(ObservedValues[MolIndex])->noteCallBackIsGone();
  boost::any_cast *>(ObservedValues[AtomCount])->noteCallBackIsGone();
  boost::any_cast *>(ObservedValues[BondCount])->noteCallBackIsGone();
  boost::any_cast *>(ObservedValues[BoundingBox])->noteCallBackIsGone();
  boost::any_cast *>(ObservedValues[FormulaString])->noteCallBackIsGone();
  boost::any_cast *>(ObservedValues[MolCenter])->noteCallBackIsGone();
  boost::any_cast *>(ObservedValues[MolName])->noteCallBackIsGone();
  boost::any_cast *>(ObservedValues[NonHydrogenCount])->noteCallBackIsGone();
  boost::any_cast *>(ObservedValues[MolSelected])->noteCallBackIsGone();
  deactivateObserver();
  destroyObservedValues(ObservedValues);
}
void QtObservedMolecule::deactivateObserver()
{
  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.markObservedMoleculeAsDisconnected(getIndex());
  }
}
void QtObservedMolecule::activateObserver()
{
  // sign on as observer (obtain non-const instance before)
  const molecule * const _molecule = getMolecule(getMolIndex());
  if (_molecule != NULL) {
    Observable::channels_t channels = getAllObservedChannels();
    owner = static_cast(_molecule);
    for (Observable::channels_t::const_iterator iter = channels.begin();
        iter != channels.end(); ++iter)
      owner->signOn(this, *iter);
    if (!BoardIsGone)
      board.markObservedMoleculeAsConnected(getIndex());
  } else
    signedOffChannels = AllsignedOnChannels;
}
void QtObservedMolecule::update(Observable *publisher)
{
  ASSERT(0,
      "QtObservedMolecule::update() - general update from unexpected source.");
}
void QtObservedMolecule::subjectKilled(Observable *publisher)
{
  ++signedOffChannels;
  checkForRemoval(getIndex());
}
void QtObservedMolecule::countValuesSubjectKilled(ObservedValue_Index_t _id)
{
  ASSERT( _id == getIndex(),
      "QtObservedAtom::countValuesSubjectKilled() - molecule "+toString(getIndex())
      +" received countValuesSubjectKilled for molecule id "+toString(_id)+".");
  ++subjectKilledCount;
  checkForRemoval(_id);
}
void QtObservedMolecule::checkForRemoval(ObservedValue_Index_t _id)
{
  if ((signedOffChannels == AllsignedOnChannels) && (subjectKilledCount == MAX_ObservedTypes)) {
    // remove owner: no more signOff needed
    owner = NULL;
    emit moleculeRemoved();
    if (!BoardIsGone) {
      board.markObservedMoleculeAsDisconnected(_id);
      board.markObservedMoleculeForErase(_id);
    }
  }
}
void QtObservedMolecule::recieveNotification(Observable *publisher, Notification_ptr notification)
{
  const molecule * const _molecule = getMolecule(getMolIndex());
  // when molecule is NULL we will soon get destroyed anyway
  if (_molecule == NULL)
    return;
  if (publisher == dynamic_cast(_molecule)){
    // notification from atom
#ifdef LOG_OBSERVER
    observerLog().addMessage() << "++ Update of Observer "<< observerLog().getName(static_cast(this))
          << " received notification from molecule " << getMolIndex() << " for channel "
          << notification->getChannelNo() << ".";
#endif
    switch (notification->getChannelNo()) {
      case molecule::AtomInserted:
      {
        const atomId_t _id = _molecule->lastChangedAtomId();
        QtObservedAtom::ptr _atom = board.getObservedAtom(_id);
        emit atomcountChanged();
        emit atomInserted(_atom, this);
        emit bondcountChanged();
        emit boundingboxChanged();
        emit centerChanged();
        emit tesselationhullChanged();
        break;
      }
      case molecule::AtomMoved:
      {
        emit boundingboxChanged();
        emit centerChanged();
        emit tesselationhullChanged();
        break;
      }
      case molecule::AtomRemoved:
      {
        const atomId_t _id = _molecule->lastChangedAtomId();
        QtObservedAtom::ptr _atom = board.getObservedAtom(_id);
        emit atomcountChanged();
        emit atomRemoved(_atom->getIndex(), this);
        emit bondcountChanged();
        emit boundingboxChanged();
        emit centerChanged();
        emit tesselationhullChanged();
        break;
      }
      case molecule::BoundingBoxChanged:
      {
  #ifdef LOG_OBSERVER
        observerLog().addMessage() << "++ Observer " << observerLog().getName(static_cast(this)) << " received notification that bounding box has changed.";
  #endif
        emit tesselationhullChanged();
        emit boundingboxChanged();
        break;
      }
      case molecule::FormulaChanged:
      {
        emit formulaChanged();
        emit nononhydrogenChanged();
        break;
      }
      case molecule::IndexChanged:
      {
  #ifdef LOG_OBSERVER
        const atomId_t _id = _molecule->lastChangedAtomId();
        observerLog().addMessage() << "++ Observer " << observerLog().getName(static_cast(this)) << " received notification that atom "+toString(_id)+"'s index has changed.";
  #endif
        emit indexChanged();
        break;
      }
      case molecule::MoleculeNameChanged:
      {
  #ifdef LOG_OBSERVER
        observerLog().addMessage() << "++ Observer " << observerLog().getName(static_cast(this)) << " received notification that name has changed.";
  #endif
        emit nameChanged();
        break;
      }
      case molecule::SelectionChanged:
      {
  #ifdef LOG_OBSERVER
        observerLog().addMessage() << "++ Observer " << observerLog().getName(static_cast(this)) << " received notification that selected has changed.";
  #endif
        emit selectedChanged();
        break;
      }
      default:
        break;
    }
  }
}
const molecule * const QtObservedMolecule::getMolecule(const moleculeId_t _id)
{
  const molecule * const mol = const_cast(World::getInstance()).
      getMolecule(MoleculeById(_id));
  return mol;
}
static molecule::BoundingBoxInfo initBoundingBox()
{
  molecule::BoundingBoxInfo info;
  info.position = zeroVec;
  info.radius = 0.;
  return info;
}
void QtObservedMolecule::initObservedValues(
    ObservedValues_t &_ObservedValues,
    const moleculeId_t _molid,
    const molecule * const _molref,
    const boost::function &_subjectKilled)
{
  ASSERT( _ObservedValues.size() == MAX_ObservedTypes,
      "QtObservedMolecule::initObservedValues() - given ObservedValues has not correct size.");
  // fill ObservedValues: then all the other that need index
  const boost::function MolIndexUpdater(
      boost::bind(&QtObservedMolecule::updateIndex, boost::cref(*_molref)));
  const boost::function AtomCountUpdater(
      boost::bind(&QtObservedMolecule::updateAtomCount, boost::cref(*_molref)));
  const boost::function BondCountUpdater(
      boost::bind(&QtObservedMolecule::updateBondCount, boost::cref(*_molref)));
  const boost::function MolCenterUpdater(
      boost::bind(&QtObservedMolecule::updateCenter, boost::cref(*_molref)));
  const boost::function MolFormulaUpdater(
      boost::bind(&QtObservedMolecule::updateFormulaString, boost::cref(*_molref)));
  const boost::function MolNameUpdater(
      boost::bind(&QtObservedMolecule::updateName, boost::cref(*_molref)));
  const boost::function NonHydrogenCountUpdater(
      boost::bind(&QtObservedMolecule::updateNonHydrogenCount, boost::cref(*_molref)));
  const boost::function BoundingBoxUpdater(
      boost::bind(&QtObservedMolecule::updateBoundingBox, boost::cref(*_molref)));
  const boost::function SelectedUpdater(
      boost::bind(&QtObservedMolecule::updateSelected, boost::cref(*_molref)));
  _ObservedValues[MolIndex] = new ObservedValue_wCallback(
        _molref,
        MolIndexUpdater,
        "MoleculeIndex_"+toString(_molid),
        _molid,
        IndexChannels,
        _subjectKilled);
  _ObservedValues[AtomCount] = new ObservedValue_wCallback(
      _molref,
      AtomCountUpdater,
      "MoleculeAtomCount_"+toString(_molid),
      AtomCountUpdater(),
      AtomCountChannels,
      _subjectKilled);
  _ObservedValues[BondCount] = new ObservedValue_wCallback(
      _molref,
      BondCountUpdater,
      "MoleculeBondCount_"+toString(_molid),
      BondCountUpdater(),
      BondCountChannels,
      _subjectKilled);
  _ObservedValues[BoundingBox] = new ObservedValue_wCallback(
      _molref,
      BoundingBoxUpdater,
      "MoleculeBoundingBox_"+toString(_molid),
      initBoundingBox(),
      BoundingBoxChannels,
      _subjectKilled);
  _ObservedValues[FormulaString] = new ObservedValue_wCallback(
      _molref,
      MolFormulaUpdater,
      "MoleculeFormula_"+toString(_molid),
      MolFormulaUpdater(),
      FormulaStringChannels,
      _subjectKilled);
  _ObservedValues[MolCenter] = new ObservedValue_wCallback(
      _molref,
      MolCenterUpdater,
      "MoleculeCenter_"+toString(_molid),
      MolCenterUpdater(),
      CenterChannels,
      _subjectKilled);
  _ObservedValues[MolName] = new ObservedValue_wCallback(
      _molref,
      MolNameUpdater,
      "MoleculeName_"+toString(_molid),
      MolNameUpdater(),
      NameChannels,
      _subjectKilled);
  _ObservedValues[NonHydrogenCount] = new ObservedValue_wCallback(
      _molref,
      NonHydrogenCountUpdater,
      "MoleculeNonHydrogenCount_"+toString(_molid),
      NonHydrogenCountUpdater(),
      NonHydrogenCountChannels,
      _subjectKilled);
  _ObservedValues[MolSelected] = new ObservedValue_wCallback(
      _molref,
      SelectedUpdater,
      "MoleculeSelected_"+toString(_molid),
      SelectedUpdater(),
      SelectedChannels,
      _subjectKilled);
}
void QtObservedMolecule::destroyObservedValues(
    std::vector &_ObservedValues)
{
  delete boost::any_cast *>(_ObservedValues[MolIndex]);
  delete boost::any_cast *>(_ObservedValues[AtomCount]);
  delete boost::any_cast *>(_ObservedValues[BondCount]);
  delete boost::any_cast *>(_ObservedValues[BoundingBox]);
  delete boost::any_cast *>(_ObservedValues[FormulaString]);
  delete boost::any_cast *>(_ObservedValues[MolCenter]);
  delete boost::any_cast *>(_ObservedValues[MolName]);
  delete boost::any_cast *>(_ObservedValues[NonHydrogenCount]);
  delete boost::any_cast *>(_ObservedValues[MolSelected]);
  _ObservedValues.clear();
}
#ifdef HAVE_INLINE
inline
#endif
int QtObservedMolecule::updateAtomCount(const molecule &mol)
{
  return mol.getAtomCount();
}
#ifdef HAVE_INLINE
inline
#endif
int QtObservedMolecule::updateBondCount(const molecule &mol)
{
  return mol.getBondCount();
}
#ifdef HAVE_INLINE
inline
#endif
molecule::BoundingBoxInfo QtObservedMolecule::updateBoundingBox(const molecule &mol)
{
  return mol.getBoundingBox();
}
#ifdef HAVE_INLINE
inline
#endif
std::string QtObservedMolecule::updateFormulaString(const molecule &mol)
{
  return mol.getFormula().toString();
}
#ifdef HAVE_INLINE
inline
#endif
Vector QtObservedMolecule::updateCenter(const molecule &mol)
{
  return mol.DetermineCenterOfAll();
}
#ifdef HAVE_INLINE
inline
#endif
moleculeId_t QtObservedMolecule::updateIndex(const molecule &mol)
{
  return mol.getId();
}
#ifdef HAVE_INLINE
inline
#endif
std::string QtObservedMolecule::updateName(const molecule &mol)
{
  return mol.getName();
}
#ifdef HAVE_INLINE
inline
#endif
int QtObservedMolecule::updateNonHydrogenCount(const molecule &mol)
{
  return mol.getNoNonHydrogen();
}
#ifdef HAVE_INLINE
inline
#endif
bool QtObservedMolecule::updateSelected(const molecule &mol)
{
  return mol.getSelected();
}
ObservedValue_Index_t QtObservedMolecule::getIndex() const
{
  ASSERT( index != NULL,
      "QtObservedMolecule::getIndex() - index is NULL");
  return index;
}
const int& QtObservedMolecule::getAtomCount() const
{
  return boost::any_cast *>(ObservedValues[AtomCount])->get();
}
const int& QtObservedMolecule::getBondCount() const
{
  return boost::any_cast *>(ObservedValues[BondCount])->get();
}
const std::string& QtObservedMolecule::getMolFormula() const
{
  return boost::any_cast *>(ObservedValues[FormulaString])->get();
}
const Vector& QtObservedMolecule::getMolCenter() const
{
  return boost::any_cast *>(ObservedValues[MolCenter])->get();
}
const moleculeId_t& QtObservedMolecule::getMolIndex() const
{
  return boost::any_cast *>(ObservedValues[MolIndex])->get();
}
const std::string& QtObservedMolecule::getMolName() const
{
  return boost::any_cast *>(ObservedValues[MolName])->get();
}
const int& QtObservedMolecule::getNonHydrogenCount() const
{
  return boost::any_cast *>(ObservedValues[NonHydrogenCount])->get();
}
const molecule::BoundingBoxInfo& QtObservedMolecule::getBoundingBox() const
{
  return boost::any_cast *>(ObservedValues[BoundingBox])->get();
}
const bool& QtObservedMolecule::getMolSelected() const
{
  return boost::any_cast *>(ObservedValues[MolSelected])->get();
}