/*
 * 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 .
 */
/*
 * QtObservedInstanceBoard.cpp
 *
 *  Created on: Oct 17, 2015
 *      Author: heber
 */
// include config.h
#ifdef HAVE_CONFIG_H
#include 
#endif
#include "QtObservedInstanceBoard.hpp"
#include 
#include "UIElements/Qt4/InstanceBoard/QtObservedAtom.hpp"
#include "UIElements/Qt4/InstanceBoard/QtObservedMolecule.hpp"
//#include "CodePatterns/MemDebug.hpp"
#include 
#include "CodePatterns/Log.hpp"
#include "Atom/atom.hpp"
#include "Descriptors/AtomIdDescriptor.hpp"
#include "Descriptors/MoleculeIdDescriptor.hpp"
#include "molecule.hpp"
#include "UIElements/Qt4/InstanceBoard/ObservedValuesContainer_impl.hpp"
#include "World.hpp"
QtObservedInstanceBoard::QtObservedInstanceBoard(QWidget * _parent) :
  QWidget(_parent),
  Observer("QtObservedInstanceBoard"),
  WorldSignedOn(false),
  atomObservedValues(
      "atom",
      *this,
      boost::bind(&QtObservedInstanceBoard::atomcountsubjectKilled, this, _1)),
  bondObservedValues(
      "bond",
      *this,
      boost::bind(&QtObservedInstanceBoard::bondcountsubjectKilled, this, _1)),
  moleculeObservedValues(
      "molecule",
      *this,
      boost::bind(&QtObservedInstanceBoard::moleculecountsubjectKilled, this, _1))
{
  qRegisterMetaType("ObservedValue_Index_t");
  qRegisterMetaType("QtObservedAtom::ptr");
  qRegisterMetaType("QtObservedBond::ptr");
  qRegisterMetaType("QtObservedMolecule::ptr");
  // be first (besides ObservedValues to know about new insertions)
  World::getInstance().signOn(this, World::AtomInserted, GlobalObservableInfo::PriorityLevel(int(-10)));
  World::getInstance().signOn(this, World::MoleculeInserted, GlobalObservableInfo::PriorityLevel(int(-10)));
  WorldSignedOn = true;
}
QtObservedInstanceBoard::~QtObservedInstanceBoard()
{
  if (WorldSignedOn) {
    World::getInstance().signOff(this, World::AtomInserted);
    World::getInstance().signOff(this, World::MoleculeInserted);
  }
  // sign off from all remaining molecules, bonds, and atoms
  for (SignedOn_t::iterator iter = AtomSignedOn.begin(); !AtomSignedOn.empty();
      iter = AtomSignedOn.begin()) {
    (*iter)->signOff(this, atom::IndexChanged);
    (*iter)->signOff(this, atom::BondsAdded);
    AtomSignedOn.erase(*iter);
  }
  for (SignedOn_t::iterator iter = MoleculeSignedOn.begin(); !MoleculeSignedOn.empty();
      iter = MoleculeSignedOn.begin()) {
    (*iter)->signOff(this, molecule::IndexChanged);
    MoleculeSignedOn.erase(iter);
  }
}
void QtObservedInstanceBoard::update(Observable *publisher)
{
  ASSERT(0,
      "QtObservedInstanceBoard::update() - we are not signed on to general updates.");
}
void QtObservedInstanceBoard::subjectKilled(Observable *publisher)
{
  SignedOn_t::iterator iter = AtomSignedOn.find(publisher);
  if ( iter != AtomSignedOn.end()) {
    LOG(3, "DEBUG: InstanceBoard got subjectKilled() from atom " << publisher);
    AtomSignedOn.erase(iter); // only remove one
  } else {
    iter = MoleculeSignedOn.find(publisher);
    if ( iter != MoleculeSignedOn.end()) {
      LOG(3, "DEBUG: InstanceBoard got subjectKilled() from molecule " << publisher);
      MoleculeSignedOn.erase(iter);
    } else {
      ASSERT(0,
          "QtObservedInstanceBoard::subjectKilled() - could not find signedOn for atom/molecule "+toString(publisher));
    }
  }
}
void QtObservedInstanceBoard::recieveNotification(Observable *publisher, Notification_ptr notification)
{
  if (static_cast(publisher) == World::getPointer()) {
    switch (notification->getChannelNo()) {
      case World::MoleculeInserted:
      {
        const moleculeId_t _id = const_cast(World::getInstance()).lastChangedMolId();
  #ifdef LOG_OBSERVER
        observerLog().addMessage() << "++ Observer " << observerLog().getName(static_cast(this)) << " received notification that molecule "+toString(_id)+" has been inserted.";
  #endif
        LOG(3, "DEBUG: InformationBoard got moleculeInserted signal for molecule " << _id);
        const molecule * const _molecule = const_cast(World::getInstance()).
            getMolecule(MoleculeById(_id));
        if (_molecule != NULL) {
          LOG(3, "DEBUG: InformationBoard initializes QtObservedMolecule for " << _id);
          QtObservedMolecule::ptr observedmolecule(
              new QtObservedMolecule(
                  _id,
                  _molecule,
                  *this));
          observedmolecule->setSelfRef(observedmolecule);
          const ObservedValue_Index_t index = observedmolecule->getIndex();
          moleculeObservedValues.insert(index, observedmolecule);
          moleculeids_lookup.left.insert( std::make_pair(_id, index) );
          // we need to check for index changes
          LOG(3, "DEBUG: InformationBoard signOn()s to molecule " << _id);
          _molecule->signOn(this, molecule::IndexChanged);
          MoleculeSignedOn.insert( static_cast(const_cast(_molecule)) );
          emit moleculeInserted(observedmolecule);
        } else {
          ELOG(1, "QtObservedInstanceBoard got MoleculeInserted for unknown molecule id " << _id);
        }
        break;
      }
      case World::AtomInserted:
      {
        const atomId_t _id = const_cast(World::getInstance()).lastChangedAtomId();
  #ifdef LOG_OBSERVER
        observerLog().addMessage() << "++ Observer " << observerLog().getName(static_cast(this)) << " received notification that atom "+toString(_id)+" has been inserted.";
  #endif
        LOG(3, "DEBUG: InformationBoard got atomInserted signal for atom " << _id);
        const atom * const _atom = const_cast(World::getInstance()).
            getAtom(AtomById(_id));
        if (_atom!= NULL) {
          LOG(3, "DEBUG: InformationBoard initializes QtObservedAtom for " << _id);
          QtObservedAtom::ptr observedatom(
              new QtObservedAtom(_id, _atom, *this));
          observedatom->setSelfRef(observedatom);
          const ObservedValue_Index_t index = observedatom->getIndex();
          atomObservedValues.insert(index, observedatom);
          atomids_lookup.left.insert( std::make_pair(_id, index) );
          // we need to check for index changes
          LOG(3, "DEBUG: InformationBoard signOn()s to atom " << _id);
          _atom->signOn(this, atom::IndexChanged);
          _atom->signOn(this, atom::BondsAdded);
          AtomSignedOn.insert( static_cast(const_cast(_atom)) );
          AtomSignedOn.insert( static_cast(const_cast(_atom)) );
          emit atomInserted(observedatom);
        } else {
          ELOG(1, "QtObservedInstanceBoard got AtomInserted for unknown atom id " << _id);
        }
        break;
      }
      default:
        ASSERT(0, "QtObservedInstanceBoard::recieveNotification() - we cannot get here for World.");
        break;
    }
  } else if (dynamic_cast(publisher) != NULL) {
    const moleculeId_t molid = const_cast(World::getInstance()).lastChangedMolId();
    switch (notification->getChannelNo()) {
      case molecule::IndexChanged:
      {
        // molecule has changed its index
        const moleculeId_t newmoleculeId = dynamic_cast(publisher)->getId();
        LOG(3, "DEBUG: InformationBoard got IndexChanged from molecule " << molid << " to " << newmoleculeId);
        {
          typename IdtoIndex_t::left_iterator iter = moleculeids_lookup.left.find(molid);
          ASSERT( iter != moleculeids_lookup.left.end(),
              "QtObservedInstanceBoard::recieveNotification() - mol id "
              +toString(molid)+" unknown to lookup.");
          const ObservedValue_Index_t index = iter->second;
          moleculeids_lookup.left.erase(iter);
          moleculeids_lookup.left.insert( std::pair(newmoleculeId, index) );
        }
        // no need update SignedOn, ref does not change
        emit moleculeIndexChanged(molid, newmoleculeId);
        break;
      }
      default:
        ASSERT(0, "QtObservedInstanceBoard::recieveNotification() - we cannot get here.");
        break;
    }
  } else if (dynamic_cast(publisher) != NULL) {
    const atomId_t oldatomId = const_cast(World::getInstance()).lastChangedAtomId();
    switch (notification->getChannelNo()) {
      case AtomObservable::IndexChanged:
      {
        const atomId_t newatomId = dynamic_cast(publisher)->getId();
        LOG(3, "DEBUG: InformationBoard got IndexChanged from atom " << oldatomId << " to " << newatomId);
        {
          typename IdtoIndex_t::left_iterator iter = atomids_lookup.left.find(oldatomId);
          ASSERT( iter != atomids_lookup.left.end(),
              "QtObservedInstanceBoard::recieveNotification() - atom id "
              +toString(oldatomId)+" unknown to lookup.");
          const ObservedValue_Index_t index = iter->second;
          atomids_lookup.left.erase(iter);
          atomids_lookup.left.insert( std::pair(newatomId, index) );
        }
        // no need update SignedOn, ref does not change
        emit atomIndexChanged(oldatomId, newatomId);
        break;
      }
      case AtomObservable::BondsAdded:
      {
        const atom * _atom = dynamic_cast(publisher);
        const atomId_t _id = _atom->getId();
#ifdef LOG_OBSERVER
      observerLog().addMessage() << "++ Observer " << observerLog().getName(static_cast(this)) << " received notification that atom "+toString(_id)+" has been inserted.";
#endif
        LOG(3, "DEBUG: InformationBoard got bondInserted signal for atom " << _id);
        if (_atom != NULL) {
          // find the new bond by simply taking the last added to the list
          const BondList &ListOfBonds = _atom->getListOfBonds();
          const bond::ptr bondref = ListOfBonds.back();
          // check whether bond is already present, cause we get BondsAdded from both atoms
          const bondId_t bondid =
              std::make_pair( bondref->leftatom->getId(), bondref->rightatom->getId());
          if (!bondids_lookup.left.count(bondid)) {
            const QtObservedAtom::ptr leftatom = getObservedAtom(bondref->leftatom->getId());
            ASSERT( leftatom,
                "QtObservedInstanceBoard::recieveNotification() - QtObservedAtom leftatom to bond must exist.");
            const QtObservedAtom::ptr rightatom = getObservedAtom(bondref->rightatom->getId());
            ASSERT( rightatom,
                "QtObservedInstanceBoard::recieveNotification() - QtObservedAtom leftatom to bond must exist.");
            LOG(3, "DEBUG: InformationBoard initializes QtObservedBond for " << bondid);
            QtObservedBond::ptr observedbond(
                new QtObservedBond(bondid, bondref, leftatom, rightatom, *this));
            const ObservedValue_Index_t index = observedbond->getIndex();
            bondObservedValues.insert(index, observedbond);
            bondids_lookup.left.insert( std::make_pair(bondid, index) );
            emit bondInserted(observedbond);
          }
        } else {
          ELOG(1, "QtObservedInstanceBoard got BondsAdded for unknown atom id " << _id);
        }
        break;
      }
      default:
        ASSERT(0, "QtObservedInstanceBoard::recieveNotification() - we cannot get here.");
        break;
    }
  } else {
    ASSERT(0, "QtObservedInstanceBoard::recieveNotification() - notification from unknown source.");
  }
}
const atomId_t QtObservedInstanceBoard::getAtomIdToIndex(ObservedValue_Index_t _id) const
{
  typename IdtoIndex_t::right_const_iterator iter = atomids_lookup.right.find(_id);
  if (iter != atomids_lookup.right.end())
    return iter->second;
  else
    return (atomId_t)-1;
}
const QtObservedInstanceBoard::bondId_t
QtObservedInstanceBoard::getBondIdToIndex(ObservedValue_Index_t _id) const
{
  typename IdtoIndex_t::right_const_iterator iter = bondids_lookup.right.find(_id);
  if (iter != bondids_lookup.right.end())
    return iter->second;
  else
    return QtObservedInstanceBoard::bondId_t(std::make_pair( (atomId_t)-1, (atomId_t)-1) );
}
const moleculeId_t QtObservedInstanceBoard::getMoleculeIdToIndex(ObservedValue_Index_t _id) const
{
  typename IdtoIndex_t::right_const_iterator iter = moleculeids_lookup.right.find(_id);
  if (iter != moleculeids_lookup.right.end())
    return iter->second;
  else
    return (moleculeId_t)-1;
}
void QtObservedInstanceBoard::atomcountsubjectKilled(ObservedValue_Index_t _id)
{
  LOG(3, "DEBUG: InstanceBoard emits atomRemoved for " << getAtomIdToIndex(_id));
  emit atomRemoved(_id);
}
void QtObservedInstanceBoard::bondcountsubjectKilled(ObservedValue_Index_t _id)
{
  LOG(3, "DEBUG: InstanceBoard emits bondRemoved for " << getBondIdToIndex(_id));
  emit bondRemoved(_id);
}
void QtObservedInstanceBoard::moleculecountsubjectKilled(ObservedValue_Index_t _id)
{
  LOG(3, "DEBUG: InstanceBoard emits moleculeRemoved for " << getMoleculeIdToIndex(_id));
  emit moleculeRemoved(_id);
}
QtObservedAtom::ptr QtObservedInstanceBoard::getObservedAtom(const atomId_t _id)
{
  typename IdtoIndex_t::left_iterator iter = atomids_lookup.left.find(_id);
  if (iter == atomids_lookup.left.end())
    return QtObservedAtom::ptr();
  else
    return atomObservedValues.get(iter->second);
}
QtObservedAtom::ptr QtObservedInstanceBoard::getObservedAtom(ObservedValue_Index_t _id)
{
  return atomObservedValues.get(_id);
}
QtObservedBond::ptr QtObservedInstanceBoard::getObservedBond(const bondId_t _id)
{
  typename IdtoIndex_t::left_iterator iter = bondids_lookup.left.find(_id);
  if (iter == bondids_lookup.left.end())
    return QtObservedBond::ptr();
  else
    return bondObservedValues.get(iter->second);
}
QtObservedBond::ptr QtObservedInstanceBoard::getObservedBond(ObservedValue_Index_t _id)
{
  return bondObservedValues.get(_id);
}
QtObservedMolecule::ptr QtObservedInstanceBoard::getObservedMolecule(const moleculeId_t _id)
{
  typename IdtoIndex_t::left_iterator iter = moleculeids_lookup.left.find(_id);
  if (iter == moleculeids_lookup.left.end())
    return QtObservedMolecule::ptr();
  else
    return moleculeObservedValues.get(iter->second);
}
QtObservedMolecule::ptr QtObservedInstanceBoard::getObservedMolecule(ObservedValue_Index_t _id)
{
  return moleculeObservedValues.get(_id);
}
void QtObservedInstanceBoard::markObservedAtomAsConnected(ObservedValue_Index_t _id)
{
  atomObservedValues.markObservedValuesAsConnected(_id);
}
void QtObservedInstanceBoard::markObservedAtomAsDisconnected(ObservedValue_Index_t _id)
{
  atomObservedValues.markObservedValuesAsDisconnected(_id);
}
void QtObservedInstanceBoard::markObservedAtomForErase(ObservedValue_Index_t _id)
{
  atomObservedValues.eraseObservedValues(_id);
  atomids_lookup.right.erase(_id);
}
void QtObservedInstanceBoard::markObservedBondAsConnected(ObservedValue_Index_t _id)
{
  bondObservedValues.markObservedValuesAsConnected(_id);
}
void QtObservedInstanceBoard::markObservedBondAsDisconnected(ObservedValue_Index_t _id)
{
  bondObservedValues.markObservedValuesAsDisconnected(_id);
}
void QtObservedInstanceBoard::markObservedBondForErase(ObservedValue_Index_t _id)
{
  bondObservedValues.eraseObservedValues(_id);
  bondids_lookup.right.erase(_id);
}
void QtObservedInstanceBoard::markObservedMoleculeAsConnected(ObservedValue_Index_t _id)
{
  moleculeObservedValues.markObservedValuesAsConnected(_id);
}
void QtObservedInstanceBoard::markObservedMoleculeAsDisconnected(ObservedValue_Index_t _id)
{
  moleculeObservedValues.markObservedValuesAsDisconnected(_id);
}
void QtObservedInstanceBoard::markObservedMoleculeForErase(ObservedValue_Index_t _id)
{
  moleculeObservedValues.eraseObservedValues(_id);
  moleculeids_lookup.right.erase(_id);
}