/*
 * Project: MoleCuilder
 * Description: creates and alters molecular systems
 * Copyright (C)  2010-2012 University of Bonn. 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"
GLMoleculeObject_atom::GLMoleculeObject_atom(QGLSceneNode *mesh[], QObject *parent, const atom *atomref) :
  GLMoleculeObject(mesh, parent),
  Observer(std::string("GLMoleculeObject_atom")+toString(atomref->getId())),
  _atom(atomref)
{
  // sign on as observer (obtain non-const instance before)
  atomref->signOn(this, AtomObservable::IndexChanged);
  atomref->signOn(this, AtomObservable::PositionChanged);
  atomref->signOn(this, AtomObservable::ElementChanged);
  atomref->signOn(this, AtomObservable::BondsAdded);
  World::getInstance().signOn(this, World::SelectionChanged);
  // set the object's id
  resetProperties();
  LOG(2, "INFO: Created sphere for atom " << _atom->getId() << ".");
  connect( this, SIGNAL(clicked()), this, SLOT(wasClicked()));
}
GLMoleculeObject_atom::~GLMoleculeObject_atom()
{
  if (_atom){
    _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(_atom->getId())+".";
#endif
  resetProperties();
}
void GLMoleculeObject_atom::resetPosition()
{
  const Vector Position = _atom->getPosition();
  LOG(4, "INFO: GLMoleculeObject_atom::resetIndex() - new position is "+toString(Position)+".");
  setPosition(QVector3D(Position[0], Position[1], Position[2]));
}
void GLMoleculeObject_atom::resetElement()
{
  size_t elementno = 0;
  if (_atom->getType() != NULL) {
    elementno = _atom->getType()->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 (_atom->getType() != NULL) {
    radius = _atom->getType()->getVanDerWaalsRadius();
  } else {
    radius = 0.5;
  }
  setScale( radius / 4. );
}
void GLMoleculeObject_atom::resetIndex()
{
  int oldId = objectId();
  const size_t atomno = _atom->getId();
  LOG(4, "INFO: GLMoleculeObject_atom::resetIndex() - new index is "+toString(atomno)+".");
  setObjectId(atomno);
  emit indexChanged(this, oldId, atomno);
}
void GLMoleculeObject_atom::resetProperties()
{
  // set position
  resetPosition();
  // set element
  resetElement();
  // set the object's id
  resetIndex();
  // selected?
  setSelected(World::getInstance().isSelected(_atom));
}
void GLMoleculeObject_atom::subjectKilled(Observable *publisher)
{
  _atom = NULL;
}
void GLMoleculeObject_atom::recieveNotification(Observable *publisher, Notification_ptr notification)
{
  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:
        resetElement();
        emit changed();
        break;
      case AtomObservable::IndexChanged:
        resetIndex();
        break;
      case AtomObservable::PositionChanged:
        resetPosition();
        emit changed();
        break;
      case AtomObservable::BondsAdded:
      {
          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);
          break;
        }
      default:
        //setProperties();
        break;
    }
  }else{
    // 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 " << _atom->getId() << " has been clicked");
  emit clicked(_atom->getId());
}