/*
 * Project: MoleCuilder
 * Description: creates and alters molecular systems
 * Copyright (C)  2016 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 .
 */
/*
 * QtObservedBond.cpp
 *
 *  Created on: Mar 03, 2016
 *      Author: heber
 */
// include config.h
#ifdef HAVE_CONFIG_H
#include 
#endif
#include "QtObservedBond.hpp"
#include 
#include "UIElements/Qt4/InstanceBoard/QtObservedInstanceBoard.hpp"
//#include "CodePatterns/MemDebug.hpp"
#include 
#include 
#include "Atom/atom.hpp"
#include "Bond/bond.hpp"
#include "World.hpp"
#include "UIElements/Qt4/InstanceBoard/ObservedValue_wCallback.hpp"
using namespace boost::assign;
template 
Observable * const getObservable(const T * _ptr)
{
  return static_cast(
      const_cast(_ptr));
}
static QtObservedBond::ObservableCount_t initAllsignedOnChannels(const bond::ptr _bond)
{
  const QtObservedBond::ObservableCount_t returnlist =
      boost::assign::list_of< QtObservedBond::ObservableCount_t::value_type >
            ( getObservable(_bond.get()), 1);
  return returnlist;
}
// static entities
const Observable::channels_t
QtObservedBond::BondDegreeChannels(1, BondObservable::DegreeChanged);
QtObservedBond::QtObservedBond(
    const bondId_t _id,
    const bond::ptr _bond,
    const QtObservedAtom::ptr &_leftatom,
    const QtObservedAtom::ptr &_rightatom,
    QtObservedInstanceBoard &_board,
    QWidget * _parent) :
  QWidget(_parent),
  Observer("QtObservedBond"),
  AllsignedOnChannels(initAllsignedOnChannels(_bond)),
  bondowner(NULL),
  oldbondId(_id),
  leftatom(_leftatom),
  rightatom(_rightatom),
  index(static_cast(const_cast(this))),
  board(_board),
  BoardIsGone(false),
  ObservedValues(QtObservedBond::MAX_ObservedTypes)
{
  qRegisterMetaType("bondId_t");
  typedef boost::function map_accessor_t;
  const map_accessor_t accessor =
      boost::bind(
          static_cast(&ObservableCount_t::at),
          boost::ref(subjectKilledCount), _1);
  const boost::function bondSubjectKilled(
      boost::bind(&QtObservedBond::countValuesSubjectKilled,
          boost::ref(*this),
          boost::bind(&QtObservedBond::getIndex, boost::cref(*this)),
          boost::bind(accessor,
              getObservable(_bond.get()))));
  initObservedValues( ObservedValues, _id, _bond, bondSubjectKilled);
  // activating Observer is done by ObservedValueContainer when it's inserted
}
QtObservedBond::~QtObservedBond()
{
  boost::any_cast *>(ObservedValues[BondDegree])->noteCallBackIsGone();
  deactivateObserver();
  destroyObservedValues(ObservedValues);
}
#ifdef HAVE_INLINE
inline
#endif
int QtObservedBond::updateDegree(const bond &_bond)
{
  return _bond.getDegree();
}
void QtObservedBond::update(Observable *publisher)
{
  ASSERT(0, "QtObservedBond::update() - we are not signed on for global updates.");
}
void QtObservedBond::subjectKilled(Observable *publisher)
{
  ++(signedOffChannels[publisher]);
  checkForRemoval(getIndex());
}
void QtObservedBond::countValuesSubjectKilled(
    ObservedValue_Index_t _id,
    unsigned int &_counter)
{
  ASSERT( _id == getIndex(),
      "QtObservedBond::countValuesSubjectKilled() - bond "+toString(getIndex())
      +" received countValuesSubjectKilled for bond id "+toString(_id)+".");
  ++_counter;
  checkForRemoval(_id);
}
void QtObservedBond::checkForRemoval(ObservedValue_Index_t _id)
{
  if (bondowner != NULL) {
    // only bond needs to be destroyed to signal removal
    const ObservableCount_t::const_iterator subjectkillediter =
        subjectKilledCount.find(const_cast(bondowner));
    const ObservableCount_t::const_iterator allsignediter =
        AllsignedOnChannels.find(const_cast(bondowner));
    const ObservableCount_t::const_iterator signedoffiter =
        signedOffChannels.find(const_cast(bondowner));
    ASSERT( (subjectkillediter != subjectKilledCount.end())
        && (allsignediter != AllsignedOnChannels.end())
        && (signedoffiter != signedOffChannels.end()),
        "QtObservedBond::checkForRemoval() - something is wrong here.");
    if ((signedoffiter->second == allsignediter->second)
        && (subjectkillediter->second == allsignediter->second)) {
      // remove owner: no more signOff needed
      bondowner = NULL;
      emit bondRemoved();
      if (!BoardIsGone) {
        board.markObservedBondAsDisconnected(_id);
        board.markObservedBondForErase(_id);
      }
    }
  }
}
void QtObservedBond::recieveNotification(Observable *publisher, Notification_ptr notification)
{
  // ObservedValues have been updated before, hence convert updates to Qt's signals
  if (publisher == bondowner) {
    switch (notification->getChannelNo()) {
      case BondObservable::DegreeChanged:
        emit degreeChanged();
        break;
      default:
        ASSERT(0, "QtObservedBond::recieveNotification() - we are not signed on to channel "
            +toString(notification->getChannelNo())+" of the bond "
            +toString(getBondIndex())+".");
        break;
    }
  } else
    ASSERT(0,
        "QtObservedBond::recieveNotification() - received signal from unknown source.");
}
static QtObservedBond::ObservableCount_t::mapped_type getObservableCountValue(
    const QtObservedBond::ObservableCount_t &_map,
    const Observable *_obs)
{
  Observable * const obs_const = const_cast(_obs);
  QtObservedBond::ObservableCount_t::const_iterator iter = _map.find(obs_const);
  ASSERT( iter != _map.end(),
      "getObservableCount_tValue");
  return iter->second;
}
static void assignObservableCountValue(
    QtObservedBond::ObservableCount_t &_map,
    const Observable *_obs,
    const QtObservedBond::ObservableCount_t::mapped_type &_value)
{
  Observable * const obs_const = const_cast(_obs);
  QtObservedBond::ObservableCount_t::iterator iter = _map.find(obs_const);
  ASSERT( iter != _map.end(),
      "getObservableCount_tValue");
  iter->second = _value;
}
void QtObservedBond::activateObserver()
{
  signedOffChannels.clear();
  subjectKilledCount.clear();
  atom * leftatomref = getAtom(getLeftAtomIndex());
  atom * rightatomref = getAtom(getRightAtomIndex());
  bond::ptr bondref = leftatomref->getBond(rightatomref);
  if (bondref != NULL) {
    // bond
    {
      bondowner = static_cast(bondref.get());
      bondowner->signOn(this, BondObservable::DegreeChanged);
      subjectKilledCount.insert( std::make_pair(const_cast(bondowner), 0));
      signedOffChannels.insert( std::make_pair(const_cast(bondowner), 0));
    }
    // and mark as connected
    if (!BoardIsGone)
      board.markObservedBondAsConnected(getIndex());
  } else {
    subjectKilledCount.insert( std::make_pair(const_cast(bondowner), 1));
    assignObservableCountValue(signedOffChannels, bondowner,
        getObservableCountValue(AllsignedOnChannels, bondowner));
  }
  // pass thru to signals from both atoms
  connect( leftatom.get(), SIGNAL(indexChanged()), this, SIGNAL(leftAtomIndexChanged()));
  connect( leftatom.get(), SIGNAL(elementChanged()), this, SIGNAL(leftAtomElementChanged()));
  connect( leftatom.get(), SIGNAL(positionChanged()), this, SIGNAL(leftAtomPositionChanged()));
  connect( leftatom.get(), SIGNAL(moleculeChanged()), this, SIGNAL(leftmoleculeChanged()));
  connect( rightatom.get(), SIGNAL(indexChanged()), this, SIGNAL(rightAtomIndexChanged()));
  connect( rightatom.get(), SIGNAL(elementChanged()), this, SIGNAL(rightAtomElementChanged()));
  connect( rightatom.get(), SIGNAL(positionChanged()), this, SIGNAL(rightAtomPositionChanged()));
  connect( rightatom.get(), SIGNAL(moleculeChanged()), this, SIGNAL(rightmoleculeChanged()));
}
void QtObservedBond::deactivateObserver()
{
  if (bondowner != NULL) {
    const ObservableCount_t::iterator subjectkilledbonditer =
        subjectKilledCount.find(const_cast(bondowner));
    ASSERT( (subjectkilledbonditer != subjectKilledCount.end()),
        "QtObservedBond::deactivateObserver() - no entry in subjectKilledCount for bond"
        +toString(bondowner)+","
        +" has activateObserver() been called?");
    if (subjectkilledbonditer->second == 0)
      bondowner->signOff(this, BondObservable::DegreeChanged);
    subjectkilledbonditer->second = 1;
    bondowner = NULL;
    signedOffChannels.clear();
    signedOffChannels.insert(AllsignedOnChannels.begin(), AllsignedOnChannels.end());
    if (!BoardIsGone)
      board.markObservedBondAsDisconnected(getIndex());
  }
}
const atom * const QtObservedBond::getAtomConst(const atomId_t _id)
{
  const atom * const _atom = const_cast(World::getInstance()).
      getAtom(AtomById(_id));
  return _atom;
}
atom * const QtObservedBond::getAtom(const atomId_t _id)
{
  atom * const _atom = World::getInstance().getAtom(AtomById(_id));
  return _atom;
}
void QtObservedBond::initObservedValues(
    ObservedValues_t &_ObservedValues,
    const bondId_t _id,
    const bond::ptr _bondref,
    const boost::function &_bondsubjectKilled)
{
  // fill ObservedValues: index first
  const boost::function BondIndexGetter =
      boost::bind(&QtObservedBond::getIndex,
          boost::cref(*this));
  // fill ObservedValues: then all the other that need index
  const boost::function BondDegreeUpdater(
      boost::bind(&QtObservedBond::updateDegree, boost::cref(*_bondref)));
  _ObservedValues[BondDegree] = new ObservedValue_wCallback(
      _bondref.get(),
      BondDegreeUpdater,
      "BondDegree_bond"+toString(_id),
      BondDegreeUpdater(),
      BondDegreeChannels,
      _bondsubjectKilled);
}
void QtObservedBond::destroyObservedValues(
    std::vector &_ObservedValues)
{
  delete boost::any_cast *>(_ObservedValues[BondDegree]);
  _ObservedValues.clear();
}
ObservedValue_Index_t QtObservedBond::getIndex() const
{
  ASSERT( index != NULL,
      "QtObservedBond::getIndex() - index is NULL");
  return index;
}
const QtObservedBond::bondId_t QtObservedBond::getBondIndex() const
{
  return QtObservedBond::bondId_t(getLeftAtomIndex(), getRightAtomIndex());
}
const int& QtObservedBond::getBondDegree() const
{
  return boost::any_cast *>(ObservedValues[BondDegree])->get();
}
const atomId_t& QtObservedBond::getLeftAtomIndex() const
{
  return leftatom->getAtomIndex();
}
const atomicNumber_t& QtObservedBond::getLeftAtomElement() const
{
  return leftatom->getAtomElement();
}
const Vector& QtObservedBond::getLeftAtomPosition() const
{
  return leftatom->getAtomPosition();
}
const moleculeId_t QtObservedBond::getLeftMoleculeIndex() const
{
  if (leftatom->getMoleculeRef() != NULL)
    return leftatom->getMoleculeRef()->getMolIndex();
  else
    return (moleculeId_t)-1;
}
const atomId_t& QtObservedBond::getRightAtomIndex() const
{
  return rightatom->getAtomIndex();
}
const atomicNumber_t& QtObservedBond::getRightAtomElement() const
{
  return rightatom->getAtomElement();
}
const Vector& QtObservedBond::getRightAtomPosition() const
{
  return rightatom->getAtomPosition();
}
const moleculeId_t QtObservedBond::getRightMoleculeIndex() const
{
  if (rightatom->getMoleculeRef() != NULL)
    return rightatom->getMoleculeRef()->getMolIndex();
  else
    return (moleculeId_t)-1;
}