/*
 * 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_atom.cpp
 *
 *  Created on: Aug 17, 2011
 *      Author: heber
 */
// include config.h
#ifdef HAVE_CONFIG_H
#include 
#endif
#include "GLMoleculeObject_atom.hpp"
#include 
#include "CodePatterns/MemDebug.hpp"
#include "CodePatterns/Assert.hpp"
#include "CodePatterns/Log.hpp"
#include "CodePatterns/Observer/Notification.hpp"
#include "Atom/atom.hpp"
#include "Bond/bond.hpp"
#include "Descriptors/AtomIdDescriptor.hpp"
#include "Element/element.hpp"
#include "LinearAlgebra/Vector.hpp"
#include "GLMoleculeObject_bond.hpp"
#include "World.hpp"
#include "WorldTime.hpp"
GLMoleculeObject_atom::GLMoleculeObject_atom(QGLSceneNode *mesh[], QObject *parent, const atomId_t _id) :
  GLMoleculeObject(mesh, parent),
  Observer(std::string("GLMoleculeObject_atom")+toString(_id)),
  atomicid(_id),
  uptodatePosition(false),
  uptodateElement(false)
{
  // sign on as observer (obtain non-const instance before)
  const atom *_atom = World::getInstance().getAtom(AtomById(atomicid));
  if (_atom != NULL) {
    _atom->signOn(this, AtomObservable::IndexChanged);
    _atom->signOn(this, AtomObservable::PositionChanged);
    _atom->signOn(this, AtomObservable::ElementChanged);
    _atom->signOn(this, AtomObservable::BondsAdded);
  } else {
    ELOG(2, "Atom with id "+toString(atomicid)+" is already gone.");
  }
  World::getInstance().signOn(this, World::SelectionChanged);
  // set the object's id
  resetProperties();
  LOG(2, "INFO: Created sphere for atom " << atomicid << ".");
  connect( this, SIGNAL(clicked()), this, SLOT(wasClicked()));
}
GLMoleculeObject_atom::~GLMoleculeObject_atom()
{
  const atom *_atom = World::getInstance().getAtom(AtomById(atomicid));
  if (_atom != NULL){
    _atom->signOff(this, AtomObservable::IndexChanged);
    _atom->signOff(this, AtomObservable::PositionChanged);
    _atom->signOff(this, AtomObservable::ElementChanged);
    _atom->signOff(this, AtomObservable::BondsAdded);
  }
  World::getInstance().signOff(this, World::SelectionChanged);
}
void GLMoleculeObject_atom::update(Observable *publisher)
{
#ifdef LOG_OBSERVER
  observerLog().addMessage() << "++ Update of Observer " << observerLog().getName(static_cast(this)) << " from atom "+toString(atomicid)+".";
#endif
  resetProperties();
}
void GLMoleculeObject_atom::resetPosition()
{
  const atom *_atom = World::getInstance().getAtom(AtomById(atomicid));
  if (_atom != NULL) {
    const Vector Position = _atom->getPosition();
    LOG(4, "INFO: GLMoleculeObject_atom::resetIndex() - new position is "+toString(Position)+".");
    setPosition(QVector3D(Position[0], Position[1], Position[2]));
  } else {
    ELOG(2, "Atom with id "+toString(atomicid)+" is already gone.");
  }
  uptodatePosition = true;
}
void GLMoleculeObject_atom::resetElement()
{
  size_t elementno = 0;
  const atom *_atom = World::getInstance().getAtom(AtomById(atomicid));
  const element *_type = NULL;
  if (_atom != NULL) {
    _type = _atom->getType();
  } else {
    ELOG(2, "Atom with id "+toString(atomicid)+" is already gone.");
  }
  if (_type != NULL) {
    elementno = _type->getAtomicNumber();
  } else { // if no element yet, set to hydrogen
    elementno = 1;
  }
  LOG(4, "INFO: GLMoleculeObject_atom::resetIndex() - new element number is "+toString(elementno)+".");
  // set materials
  QGLMaterial *elementmaterial = getMaterial(elementno);
  ASSERT(elementmaterial != NULL,
      "GLMoleculeObject_atom::GLMoleculeObject_atom() - QGLMaterial ref from getter function is NULL.");
  setMaterial(elementmaterial);
  // set scale
  double radius = 0.;
  if (_type != NULL) {
    radius = _type->getVanDerWaalsRadius();
  } else {
    radius = 0.5;
  }
  setScale( radius / 4. );
  uptodateElement = true;
}
void GLMoleculeObject_atom::resetIndex()
{
  int oldId = objectId();
  LOG(4, "INFO: GLMoleculeObject_atom::resetIndex() - new index is "+toString(atomicid)+".");
  setObjectId(atomicid);
  emit indexChanged(this, oldId, atomicid);
}
void GLMoleculeObject_atom::resetProperties()
{
  // set position
  resetPosition();
  // set element
  resetElement();
  // set the object's id
  resetIndex();
  // selected?
  setSelected(World::getInstance().isAtomSelected(atomicid));
}
void GLMoleculeObject_atom::draw(QGLPainter *painter, const QVector4D &cameraPlane)
{
  // hook to update prior to painting
  if (!uptodatePosition)
    resetPosition();
  if (!uptodateElement)
    resetElement();
  // call old hook to do the actual paining
  GLMoleculeObject::draw(painter, cameraPlane);
}
void GLMoleculeObject_atom::subjectKilled(Observable *publisher)
{
  // remove id such that we don't sign off accidentally from a different atom
  const_cast(atomicid) = -1;
}
void GLMoleculeObject_atom::recieveNotification(Observable *publisher, Notification_ptr notification)
{
  const atom *_atom = World::getInstance().getAtom(AtomById(atomicid));
  if (publisher == dynamic_cast(_atom)){
    // notofication from atom
#ifdef LOG_OBSERVER
    observerLog().addMessage() << "++ Update of Observer "<< observerLog().getName(static_cast(this))
          << " received notification from atom " << _atom->getId() << " for channel "
          << notification->getChannelNo() << ".";
#endif
    switch (notification->getChannelNo()) {
      case AtomObservable::ElementChanged:
        uptodateElement = false;
        break;
      case AtomObservable::IndexChanged:
        resetIndex();
        break;
      case AtomObservable::PositionChanged:
        uptodatePosition = false;
        break;
      case AtomObservable::BondsAdded:
      {
        const atom *_atom = World::getInstance().getAtom(AtomById(atomicid));
        if (_atom != NULL) {
          // make sure position is up-to-date
          if (!uptodatePosition)
            resetPosition();
          ASSERT(!_atom->getListOfBonds().empty(),
              "GLMoleculeObject_atom::recieveNotification() - received BondsAdded but ListOfBonds is empty.");
          const bond::ptr _bond = *(_atom->getListOfBonds().rbegin());
          const GLMoleculeObject_bond::SideOfBond side = (_bond->leftatom == _atom) ?
              GLMoleculeObject_bond::left : GLMoleculeObject_bond::right;
          emit BondsInserted(_bond, side);
        } else {
          ELOG(2, "Atom with id "+toString(atomicid)+" is already gone.");
        }
        break;
      }
      default:
        //setProperties();
        break;
    }
  }else if (static_cast(publisher) == World::getPointer()) {
    // notification from world
#ifdef LOG_OBSERVER
    observerLog().addMessage() << "++ Update of Observer "<< observerLog().getName(static_cast(this))
          << " received notification from world for channel "
          << notification->getChannelNo() << ".";
#endif
    switch (notification->getChannelNo()) {
      case World::SelectionChanged:
        setSelected(World::getInstance().isSelected(_atom));
        break;
      default:
        break;
    }
  }
}
void GLMoleculeObject_atom::wasClicked()
{
  LOG(4, "INFO: GLMoleculeObject_atom: atom " << atomicid << " has been clicked");
  emit clicked(atomicid);
}