/*
 * 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.cpp
 *
 *  This is based on the Qt3D example "teaservice", specifically meshobject.cpp.
 *
 *  Created on: Aug 17, 2011
 *      Author: heber
 */
// include config.h
#ifdef HAVE_CONFIG_H
#include 
#endif
#include "GLMoleculeObject.hpp"
#include 
#include 
#include 
#include 
#include "CodePatterns/MemDebug.hpp"
#include "CodePatterns/Assert.hpp"
#include "CodePatterns/Log.hpp"
#include "Helpers/defs.hpp"
#include "Element/element.hpp"
#include "Element/periodentafel.hpp"
#include "World.hpp"
GLMoleculeObject::ElementMaterialMap GLMoleculeObject::ElementNoMaterialMap;
#include "CodePatterns/MemDebug.hpp"
QGLMaterial *GLMoleculeObject::m_hoverMaterial = NULL;
QGLMaterial *GLMoleculeObject::m_selectionMaterial = NULL;
QGLMaterial *GLMoleculeObject::m_selectionBoxMaterial = NULL;
double GLMoleculeObject::detailMinDistance[GLMoleculeObject::DETAILTYPES_MAX] = {0, 15, 30, 42};
GLMoleculeObject::GLMoleculeObject(QGLSceneNode *mesh[], QObject *parent)
   : QObject(parent)
{
   //mesh->setParent(this);
   for (int i=0;isetParent(this);
   for (int i=0;isetParent(this);
   for (int i=0;imainNode();
   m_scaleX = 1.0f;
   m_scaleY = 1.0f;
   m_scaleZ = 1.0f;
   m_rotationAngle = 0.0f;
   m_effect = 0;
   m_objectId = -1;
   m_hovering = false;
   m_selected = false;
   m_material = 0;
   initStaticMaterials();
}
GLMoleculeObject::~GLMoleculeObject()
{
}
void GLMoleculeObject::initialize(QGLView *view, QGLPainter *painter)
{
   Q_UNUSED(painter);
   if (m_objectId != -1)
       view->registerObject(m_objectId, this);
}
/** Draws a box around the mesh.
 *
 */
void GLMoleculeObject::drawSelectionBox(QGLPainter *painter)
{
  painter->setFaceMaterial(QGL::AllFaces, m_selectionBoxMaterial);
  QVector3DArray array;
  qreal radius = 1.0;
  array.append(-radius, -radius, -radius); array.append( radius, -radius, -radius);
  array.append( radius, -radius, -radius); array.append( radius,  radius, -radius);
  array.append( radius,  radius, -radius); array.append(-radius,  radius, -radius);
  array.append(-radius,  radius, -radius); array.append(-radius, -radius, -radius);
  array.append(-radius, -radius,  radius); array.append( radius, -radius,  radius);
  array.append( radius, -radius,  radius); array.append( radius,  radius,  radius);
  array.append( radius,  radius,  radius); array.append(-radius,  radius,  radius);
  array.append(-radius,  radius,  radius); array.append(-radius, -radius,  radius);
  array.append(-radius, -radius, -radius); array.append(-radius, -radius,  radius);
  array.append( radius, -radius, -radius); array.append( radius, -radius,  radius);
  array.append(-radius,  radius, -radius); array.append(-radius,  radius,  radius);
  array.append( radius,  radius, -radius); array.append( radius,  radius,  radius);
  painter->clearAttributes();
  painter->setVertexAttribute(QGL::Position, array);
  painter->draw(QGL::Lines, 24);
}
void GLMoleculeObject::draw(QGLPainter *painter, const QVector4D &cameraPlane)
{
   // Position the model at its designated position, scale, and orientation.
   painter->modelViewMatrix().push();
   painter->modelViewMatrix().translate(m_position);
   if (m_rotationAngle != 0.0f)
     painter->modelViewMatrix().rotate(m_rotationAngle, m_rotationVector);
   painter->modelViewMatrix().scale(m_scaleX, m_scaleY, m_scaleZ);
   // Apply the material and effect to the painter.
   QGLMaterial *material;
   if (m_hovering)
       material = m_hoverMaterial;
   else if (m_selected)
       material = m_selectionMaterial;
   else
       material = m_material;
   ASSERT(material, "GLMoleculeObject::draw: chosen material is NULL");
   painter->setColor(material->diffuseColor());
   painter->setFaceMaterial(QGL::AllFaces, material);
   if (m_effect)
       painter->setUserEffect(m_effect);
   else
       painter->setStandardEffect(QGL::LitMaterial);
   // Mark the object for object picking purposes.
   int prevObjectId = painter->objectPickId();
   if (m_objectId != -1)
       painter->setObjectPickId(m_objectId);
   // Draw the geometry mesh.
   QVector4D pos4d(m_position, -1);
   qreal distance = QVector4D::dotProduct(cameraPlane, pos4d);
   if (distance > detailMinDistance[DETAIL_LOW])
     m_mesh[DETAIL_LOW]->draw(painter);
   else if (distance > detailMinDistance[DETAIL_MEDIUM])
     m_mesh[DETAIL_MEDIUM]->draw(painter);
   else if (distance > detailMinDistance[DETAIL_HIGH])
     m_mesh[DETAIL_HIGH]->draw(painter);
   else if (distance > detailMinDistance[DETAIL_HIGHEST])
     m_mesh[DETAIL_HIGHEST]->draw(painter);
   // Draw a box around the mesh, if selected.
   if (m_selected)
     drawSelectionBox(painter);
   // Turn off the user effect, if present.
   if (m_effect)
       painter->setStandardEffect(QGL::LitMaterial);
   // Revert to the previous object identifier.
   painter->setObjectPickId(prevObjectId);
   // Restore the modelview matrix.
   painter->modelViewMatrix().pop();
}
bool GLMoleculeObject::event(QEvent *e)
{
   // Convert the raw event into a signal representing the user's action.
   if (e->type() == QEvent::MouseButtonPress) {
       QMouseEvent *me = (QMouseEvent *)e;
       if (me->button() == Qt::LeftButton)
           emit pressed();
   } else if (e->type() == QEvent::MouseButtonRelease) {
       QMouseEvent *me = (QMouseEvent *)e;
       if (me->button() == Qt::LeftButton) {
           emit released();
           if (me->x() >= 0)   // Positive: inside object, Negative: outside.
               emit clicked();
       }
   } else if (e->type() == QEvent::MouseButtonDblClick) {
       emit doubleClicked();
   } else if (e->type() == QEvent::Enter) {
       m_hovering = true;
       emit hoverChanged(this);
   } else if (e->type() == QEvent::Leave) {
       m_hovering = false;
       emit hoverChanged(NULL);
   }
   return QObject::event(e);
}
/** Returns the ref to the Material for element No \a from the map.
 *
 * \note We create a new one if the element is missing.
 *
 * @param no element no
 * @return ref to QGLMaterial
 */
QGLMaterial* GLMoleculeObject::getMaterial(size_t no)
{
  ASSERT( (no > 0) && (no < MAX_ELEMENTS),
      "GLMoleculeView::getMaterial() - Element no "+toString(no)+" is invalid.");
  if (ElementNoMaterialMap.find(no) != ElementNoMaterialMap.end()){
    // get present one
    return ElementNoMaterialMap[no];
  } else {
    // create new one
    LOG(1, "Creating new material for element "+toString(no)+".");
    QGLMaterial *newmaterial = new QGLMaterial(NULL);
    // create material for element
    periodentafel *periode = World::getInstance().getPeriode();
    const element *desiredelement = periode->FindElement(no);
    ASSERT(desiredelement != NULL,
        "GLMoleculeView::getMaterial() - desired element "+toString(no)+" not present in periodentafel.");
    const unsigned char* color = desiredelement->getColor();
    LOG(1, "Creating new material with color " << (int)color[0] << "," << (int)color[1] << "," << (int)color[2] << ".");
    newmaterial->setAmbientColor( QColor((int)color[0], (int)color[1], (int)color[2]) );
    newmaterial->setSpecularColor( QColor(60, 60, 60) );
    newmaterial->setShininess( 128 );
    ElementNoMaterialMap.insert( make_pair(no, newmaterial) );
    return newmaterial;
  }
}
/** Create the 3 materials shared by all objects.
 *
 */
void GLMoleculeObject::initStaticMaterials()
{
  if (!m_hoverMaterial){
    m_hoverMaterial = new QGLMaterial(NULL);
    m_hoverMaterial->setAmbientColor( QColor(0, 128, 128) );
    m_hoverMaterial->setSpecularColor( QColor(60, 60, 60) );
    m_hoverMaterial->setShininess( 128 );
  }
  if (!m_selectionMaterial){
    m_selectionMaterial = new QGLMaterial(NULL);
    m_selectionMaterial->setAmbientColor( QColor(255, 50, 50) );
    m_selectionMaterial->setSpecularColor( QColor(60, 60, 60) );
    m_selectionMaterial->setShininess( 128 );
  }
  if (!m_selectionBoxMaterial){
    m_selectionBoxMaterial = new QGLMaterial(NULL);
    m_selectionBoxMaterial->setAmbientColor( QColor(0, 0, 0) );
    m_selectionBoxMaterial->setDiffuseColor( QColor(0, 0, 0) );
    m_selectionBoxMaterial->setEmittedLight( QColor(155, 50, 50) );
  }
}
/** Static function to be called when Materials have to be removed.
 *
 */
void GLMoleculeObject::cleanMaterialMap()
{
  for (ElementMaterialMap::iterator iter = ElementNoMaterialMap.begin();
      !ElementNoMaterialMap.empty();
      iter = ElementNoMaterialMap.begin()) {
    delete iter->second;
    ElementNoMaterialMap.erase(iter);
  }
}
void GLMoleculeObject::setSelected(bool value)
{
  if (value != m_selected){
    m_selected = value;
    emit selectionChanged();
  }
}