/*
 * 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 .
 */
/*
 * GLWorldScene.cpp
 *
 *  This is based on the Qt3D example "teaservice", specifically parts of teaservice.cpp.
 *
 *  Created on: Aug 17, 2011
 *      Author: heber
 */
// include config.h
#ifdef HAVE_CONFIG_H
#include 
#endif
#include "GLWorldScene.hpp"
#include 
#include 
#include 
#include 
#include 
#include "GLMoleculeObject.hpp"
#include "GLMoleculeObject_atom.hpp"
#include "GLMoleculeObject_bond.hpp"
#include "GLMoleculeObject_molecule.hpp"
#include "GLMoleculeObject_shape.hpp"
#include "CodePatterns/MemDebug.hpp"
#include "CodePatterns/Log.hpp"
#include "Actions/SelectionAction/Atoms/AtomByIdAction.hpp"
#include "Actions/SelectionAction/Atoms/NotAtomByIdAction.hpp"
#include "Actions/SelectionAction/Molecules/MoleculeByIdAction.hpp"
#include "Actions/SelectionAction/Molecules/NotMoleculeByIdAction.hpp"
#include "Atom/atom.hpp"
#include "Bond/bond.hpp"
#include "Descriptors/AtomIdDescriptor.hpp"
#include "Helpers/helpers.hpp"
#include "Shapes/ShapeRegistry.hpp"
#include "molecule.hpp"
#include "World.hpp"
#include 
using namespace MoleCuilder;
std::ostream &operator<<(std::ostream &ost, const GLWorldScene::BondIds &t)
{
  ost << t.first << "," << t.second;
  return ost;
}
GLWorldScene::GLWorldScene(QObject *parent)
   : QObject(parent),
     hoverAtom(NULL)
{
  int sphereDetails[] = {5, 3, 2, 0};
  int cylinderDetails[] = {16, 8, 6, 3};
  for (int i=0;isetOption(QGLSceneNode::CullBoundingBox, true);
    QGLBuilder cylinderBuilder;
    cylinderBuilder << QGLCylinder(.25,.25,1.0,cylinderDetails[i]);
    meshCylinder[i] = cylinderBuilder.finalizedSceneNode();
    meshCylinder[i]->setOption(QGLSceneNode::CullBoundingBox, true);
  }
  setSelectionMode(SelectAtom);
  init();
}
GLWorldScene::~GLWorldScene()
{
  // remove all elements
  GLMoleculeObject::cleanMaterialMap();
}
/** Initialise the WorldScene with molecules and atoms from World.
 *
 */
void GLWorldScene::init()
{
  const std::vector &molecules = World::getInstance().getAllMolecules();
  if (molecules.size() > 0) {
    for (std::vector::const_iterator Runner = molecules.begin();
        Runner != molecules.end();
        Runner++) {
      for (molecule::const_iterator atomiter = (*Runner)->begin();
          atomiter != (*Runner)->end();
          ++atomiter) {
        // create atom objects in scene
        atomInserted(*atomiter);
        // create bond objects in scene
        const BondList &bondlist = (*atomiter)->getListOfBonds();
        for (BondList::const_iterator bonditer = bondlist.begin();
            bonditer != bondlist.end();
            ++bonditer) {
          const bond::ptr _bond = *bonditer;
          const GLMoleculeObject_bond::SideOfBond side = (_bond->leftatom == *atomiter) ?
              GLMoleculeObject_bond::left : GLMoleculeObject_bond::right;
          bondInserted(_bond, side);
        }
      }
    }
  }
}
/** Adds an atom to the scene.
 *
 * @param _atom atom to add
 */
void GLWorldScene::atomInserted(const atom *_atom)
{
  LOG(3, "INFO: GLWorldScene: Received signal atomInserted for atom "+toString(_atom->getId())+".");
  GLMoleculeObject_atom *atomObject = new GLMoleculeObject_atom(meshSphere, this, _atom);
  AtomNodeMap::iterator iter = AtomsinSceneMap.find(_atom->getId());
  ASSERT(iter == AtomsinSceneMap.end(),
      "GLWorldScene::atomAdded() - same atom "+_atom->getName()+" added again.");
  AtomsinSceneMap.insert( make_pair(_atom->getId(), atomObject) );
  qRegisterMetaType("atomId_t");
  qRegisterMetaType("bond::ptr");
  qRegisterMetaType("GLMoleculeObject_bond::SideOfBond");
  connect (atomObject, SIGNAL(clicked(atomId_t)), this, SLOT(atomClicked(atomId_t)));
  connect (atomObject, SIGNAL(changed()), this, SIGNAL(changed()));
  connect (atomObject, SIGNAL(hoverChanged(GLMoleculeObject *)), this, SIGNAL(changed()));
  connect (atomObject, SIGNAL(hoverChanged(GLMoleculeObject *)), this, SLOT(hoverChangedSignalled(GLMoleculeObject *)));
  connect (atomObject, SIGNAL(selectionChanged()), this, SIGNAL(changed()));
  connect (atomObject, SIGNAL(BondsInserted(const bond::ptr , const GLMoleculeObject_bond::SideOfBond)), this, SLOT(bondInserted(const bond::ptr , const GLMoleculeObject_bond::SideOfBond)));
  connect (atomObject, SIGNAL(indexChanged(GLMoleculeObject_atom*, int, int)), this, SLOT(changeAtomId(GLMoleculeObject_atom*, int, int)));
  //bondsChanged(_atom);
  emit changeOccured();
}
/** Removes an atom from the scene.
 *
 * We just the id as the atom might have already been destroyed.
 *
 * @param _id id of atom to remove
 */
void GLWorldScene::atomRemoved(const atomicNumber_t _id)
{
  LOG(3, "INFO: GLWorldScene: Received signal atomRemoved for atom "+toString(_id)+".");
  // bonds are removed by signal coming from ~bond
  // remove atoms
  AtomNodeMap::iterator iter = AtomsinSceneMap.find(_id);
  ASSERT(iter != AtomsinSceneMap.end(),
      "GLWorldScene::atomRemoved() - atom "+toString(_id)+" not on display.");
  GLMoleculeObject_atom *atomObject = iter->second;
  atomObject->disconnect();
  AtomsinSceneMap.erase(iter);
  delete atomObject;
  emit changeOccured();
}
/** ....
 *
 */
void GLWorldScene::worldSelectionChanged()
{
  LOG(3, "INFO: GLWorldScene: Received signal selectionChanged.");
  const std::vector &molecules = World::getInstance().getAllMolecules();
  if (molecules.size() > 0) {
    for (std::vector::const_iterator Runner = molecules.begin();
        Runner != molecules.end();
        Runner++) {
      MoleculeNodeMap::iterator iter = MoleculesinSceneMap.find((*Runner)->getId());
      bool isSelected = World::getInstance().isSelected(*Runner);
      // molecule selected but not in scene?
      if (isSelected && (iter == MoleculesinSceneMap.end())){
        // -> create new mesh
        GLMoleculeObject_molecule *molObject = new GLMoleculeObject_molecule(meshEmpty, this, *Runner);
        MoleculesinSceneMap.insert( make_pair((*Runner)->getId(), molObject) );
        connect (molObject, SIGNAL(changed()), this, SIGNAL(changed()));
        connect (molObject, SIGNAL(selectionChanged()), this, SIGNAL(changed()));
        connect (molObject, SIGNAL(selectionChanged()), this, SIGNAL(changed()));
        emit changed();
        emit changeOccured();
      }
      // molecule not selected but in scene?
      if (!isSelected && (iter != MoleculesinSceneMap.end())){
        // -> remove from scene
        moleculeRemoved(*Runner);
      }
    }
  }
}
/** Removes a molecule from the scene.
 *
 * @param _molecule molecule to remove
 */
void GLWorldScene::moleculeRemoved(const molecule *_molecule)
{
  LOG(3, "INFO: GLWorldScene: Received signal moleculeRemoved for molecule "+toString(_molecule->getId())+".");
  MoleculeNodeMap::iterator iter = MoleculesinSceneMap.find(_molecule->getId());
  // only remove if the molecule is in the scene
  //  (= is selected)
  if (iter != MoleculesinSceneMap.end()){
    GLMoleculeObject_molecule *molObject = iter->second;
    molObject->disconnect();
    MoleculesinSceneMap.erase(iter);
    delete molObject;
    emit changed();
    emit changeOccured();
  }
}
/** Adds a bond to the scene.
 *
 * @param _bond bond to add
 * @param side which side of the bond (left or right)
 */
void GLWorldScene::bondInserted(const bond::ptr _bond, const enum GLMoleculeObject_bond::SideOfBond side)
{
  LOG(3, "INFO: GLWorldScene::bondInserted() - Adding bond "+toString(*_bond)+".");
  //LOG(4, "INFO: Currently present bonds " << BondsinSceneMap << ".");
  BondIds ids;
  switch (side) {
    case GLMoleculeObject_bond::left:
      ids = std::make_pair(_bond->leftatom->getId(), _bond->rightatom->getId());
      break;
    case GLMoleculeObject_bond::right:
      ids = std::make_pair(_bond->rightatom->getId(), _bond->leftatom->getId());
      break;
  }
#ifndef NDEBUG
  BondNodeMap::iterator iter = BondsinSceneMap.find(ids);
  ASSERT(iter == BondsinSceneMap.end(),
      "GLWorldScene::bondAdded() - same left-sided bond "+toString(*_bond)+" added again.");
#endif
  GLMoleculeObject_bond * bondObject =
      new GLMoleculeObject_bond(meshCylinder, this, _bond, side);
  connect (
      bondObject, SIGNAL(BondRemoved(const atomId_t, const atomId_t)),
      this, SLOT(bondRemoved(const atomId_t, const atomId_t)));
  connect (bondObject, SIGNAL(changed()), this, SIGNAL(changed()));
  BondsinSceneMap.insert( make_pair(ids, bondObject) );
//    BondIdsinSceneMap.insert( Leftids );
  emit changeOccured();
}
/** Removes a bond from the scene.
 *
 * @param _bond bond to remove
 */
void GLWorldScene::bondRemoved(const atomId_t leftnr, const atomId_t rightnr)
{
  LOG(3, "INFO: GLWorldScene::bondRemoved() - Removing bond between "+toString(leftnr)+" and "+toString(rightnr)+".");
  {
    // left bond
    const BondIds Leftids( make_pair(leftnr, rightnr) );
    BondNodeMap::iterator leftiter = BondsinSceneMap.find( Leftids );
    ASSERT(leftiter != BondsinSceneMap.end(),
        "GLWorldScene::bondRemoved() - bond "+toString(leftnr)+"-"
        +toString(rightnr)+" not on display.");
    //GLMoleculeObject_bond::ptr bondObject = leftiter->second;
    BondsinSceneMap.erase(leftiter);
    //delete bondObject; // is done by signal from bond itself
    //LOG(4, "INFO: Still present bonds " << BondsinSceneMap << ".");
  }
  emit changeOccured();
}
/** Adds a shape to the scene.
 *
 * uses ShapeRegistry::lastChanged()
 *
 */
void GLWorldScene::addShape()
{
  Shape &shape = *ShapeRegistry::getInstance().lastChanged();
  GLMoleculeObject_shape *shapeObject = new GLMoleculeObject_shape(shape, this);
  ShapeNodeMap::iterator iter = ShapesinSceneMap.find(shape.getName());
  ASSERT(iter == ShapesinSceneMap.end(),
      "GLWorldScene::addShape() - same shape "+shape.getName()+" added again.");
  ShapesinSceneMap.insert( make_pair(shape.getName(), shapeObject) );
}
void GLWorldScene::removeShape()
{
  Shape &shape = *ShapeRegistry::getInstance().lastChanged();
  ShapeNodeMap::iterator iter = ShapesinSceneMap.find(shape.getName());
  ASSERT(iter != ShapesinSceneMap.end(),
      "GLWorldScene::removeShape() - shape "+shape.getName()+" not in scene.");
  ShapesinSceneMap.erase(iter);
  delete(iter->second);
}
void GLWorldScene::updateSelectedShapes()
{
  foreach (QObject *obj, children()) {
    GLMoleculeObject_shape *shapeobj = qobject_cast(obj);
      if (shapeobj){
        shapeobj->enable(ShapeRegistry::getInstance().isSelected(shapeobj->getShape()));
      }
  }
}
void GLWorldScene::initialize(QGLView *view, QGLPainter *painter) const
{
  // Initialize all of the mesh objects that we have as children.
   foreach (QObject *obj, children()) {
     GLMoleculeObject *meshobj = qobject_cast(obj);
       if (meshobj)
         meshobj->initialize(view, painter);
   }
}
void GLWorldScene::draw(QGLPainter *painter, const QVector4D &cameraPlane) const
{
   // Draw all of the mesh objects that we have as children.
   foreach (QObject *obj, children()) {
     GLMoleculeObject *meshobj = qobject_cast(obj);
       if (meshobj)
         meshobj->draw(painter, cameraPlane);
   }
}
void GLWorldScene::atomClicked(atomId_t no)
{
   LOG(3, "INFO: GLWorldScene - atom " << no << " has been clicked.");
   const atom *Walker = World::getInstance().getAtom(AtomById(no));
   if (selectionMode == SelectAtom){
     if (!World::getInstance().isSelected(Walker))
       SelectionAtomById(std::vector(1,no));
     else
       SelectionNotAtomById(std::vector(1,no));
   }else if (selectionMode == SelectMolecule){
     const molecule *mol = Walker->getMolecule();
     ASSERT(mol, "Atom without molecule has been clicked.");
     if (!World::getInstance().isSelected(mol))
       SelectionMoleculeById(mol->getId());
     else
       SelectionNotMoleculeById(mol->getId());
   }
   emit clicked(no);
}
void GLWorldScene::setSelectionMode(SelectionModeType mode)
{
  selectionMode = mode;
  // TODO send update to toolbar
}
void GLWorldScene::setSelectionModeAtom()
{
  setSelectionMode(SelectAtom);
}
void GLWorldScene::setSelectionModeMolecule()
{
  setSelectionMode(SelectMolecule);
}
void GLWorldScene::hoverChangedSignalled(GLMoleculeObject *ob)
{
  // Find the atom, ob corresponds to.
  hoverAtom = NULL;
  GLMoleculeObject_atom *atomObject = dynamic_cast(ob);
  if (atomObject){
    for (AtomNodeMap::iterator iter = AtomsinSceneMap.begin();iter != AtomsinSceneMap.end(); ++ iter){
      if (iter->second == atomObject)
        hoverAtom = World::getInstance().getAtom(AtomById(iter->first));
    }
  }
  // Propagate signal.
  emit hoverChanged(hoverAtom);
}
void GLWorldScene::changeAtomId(GLMoleculeObject_atom *ob, int oldId, int newId)
{
  LOG(3, "INFO: GLWorldScene - change atom id " << oldId << " to " << newId << ".");
  // Remove from map.
  AtomNodeMap::iterator iter = AtomsinSceneMap.find(oldId);
  ASSERT(iter != AtomsinSceneMap.end(),
        "GLWorldScene::objectIdChangedSignalled() - atom with id "+toString(oldId)+" not on display.");
  GLMoleculeObject_atom *atomObject = iter->second;
  AtomsinSceneMap.erase(iter);
  // Reinsert with new id.
  AtomsinSceneMap.insert( make_pair(newId, atomObject) );
}