/*
 * 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 
#include "Atom/atom.hpp"
#include "Bond/bond.hpp"
#include "Element/element.hpp"
#include "Element/periodentafel.hpp"
#include "LinearAlgebra/Vector.hpp"
#include "UIElements/Views/Qt4/Qt3D/GLMoleculeObject_bond.hpp"
#include "UIElements/Qt4/InstanceBoard/QtObservedInstanceBoard.hpp"
#include "World.hpp"
GLMoleculeObject_atom::GLMoleculeObject_atom(
    QGLSceneNode *mesh[],
    QObject *parent,
    QtObservedAtom::ptr &_ObservedAtom) :
  GLMoleculeObject(mesh, parent),
  ObservedAtom(_ObservedAtom)
{
  init(ObservedAtom->getAtomIndex());
}
void GLMoleculeObject_atom::init(const atomId_t _id)
{
  setObjectId(_id);
  resetPosition();
  resetElement();
  m_selected = const_cast(World::getInstance()).isAtomSelected(_id);
  connect( this, SIGNAL(clicked()), this, SLOT(wasClicked()));
  connect( ObservedAtom.get(), SIGNAL(indexChanged(const atomId_t, const atomId_t)), this, SLOT(resetIndex(const atomId_t, const atomId_t)));
  connect( ObservedAtom.get(), SIGNAL(elementChanged()), this, SLOT(resetElement()));
  connect( ObservedAtom.get(), SIGNAL(positionChanged()), this, SLOT(resetPosition()));
  connect( ObservedAtom.get(), SIGNAL(bondsChanged()), this, SLOT(resetBonds()));
  connect( ObservedAtom.get(), SIGNAL(selectedChanged()), this, SLOT(resetSelected()));
  // use that ObservedValues::AtomBonds is always up-to-date
  // don't call here, is done by GLMoleculeObject_molecule
//  resetBonds();
}
GLMoleculeObject_atom::~GLMoleculeObject_atom()
{}
void GLMoleculeObject_atom::resetIndex(const atomId_t, const atomId_t)
{
  const atomId_t newId = ObservedAtom->getAtomIndex();
  const size_t oldId = objectId();
  ASSERT( newId != oldId,
      "GLMoleculeObject_atom::updateIndex() - index "+toString(newId)+" did not change.");
  LOG(4, "INFO: GLMoleculeObject_atom::resetIndex() - new index is "+toString(newId)+".");
  setObjectId(newId);
  emit indexChanged(this, oldId, newId);
}
void GLMoleculeObject_atom::resetPosition()
{
  const Vector Position = ObservedAtom->getAtomPosition();
  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;
  const element * const _type = World::getInstance().
      getPeriode()->FindElement(ObservedAtom->getAtomElement());
  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. );
}
void GLMoleculeObject_atom::resetBonds()
{
  QtObservedAtom::ListOfBonds_t ListOfBonds_new = ObservedAtom->getAtomBonds();
  std::sort(ListOfBonds_new.begin(), ListOfBonds_new.end());
  QtObservedAtom::ListOfBonds_t BondsToAdd;
  std::set_difference(
      ListOfBonds_new.begin(), ListOfBonds_new.end(),
      ListOfBonds.begin(), ListOfBonds.end(),
      std::back_inserter(BondsToAdd));
  QtObservedAtom::ListOfBonds_t BondsToRemove;
  std::set_difference(
      ListOfBonds.begin(), ListOfBonds.end(),
      ListOfBonds_new.begin(), ListOfBonds_new.end(),
      std::back_inserter(BondsToRemove));
  for (QtObservedAtom::ListOfBonds_t::const_iterator iter = BondsToAdd.begin();
      iter != BondsToAdd.end();
      ++iter) {
    const GLMoleculeObject_bond::SideOfBond side = (iter->first == ObservedAtom->getAtomIndex()) ?
        GLMoleculeObject_bond::left : GLMoleculeObject_bond::right;
    emit BondsAdded(iter->first, iter->second, side);
  }
  for (QtObservedAtom::ListOfBonds_t::const_iterator iter = BondsToRemove.begin();
      iter != BondsToRemove.end();
      ++iter) {
    emit BondsRemoved(iter->first, iter->second);
  }
  ListOfBonds = ListOfBonds_new;
}
void GLMoleculeObject_atom::resetSelected()
{
  // we ascertain check that selection state changed as the Qt signal might be executed
  // at a later stage when the state has changed yet again
  const bool new_selected = ObservedAtom->getAtomSelected();
  m_selected = new_selected;
  emit changed();
}
void GLMoleculeObject_atom::draw(QGLPainter *painter, const QVector4D &cameraPlane)
{
  // call old hook to do the actual paining
  GLMoleculeObject::draw(painter, cameraPlane);
}
void GLMoleculeObject_atom::wasClicked()
{
  LOG(4, "INFO: GLMoleculeObject_atom: atom "
      << ObservedAtom->getAtomIndex() << " has been clicked");
  emit clicked(ObservedAtom->getAtomIndex());
}