/***************************************************************************
 *   Copyright (C) 2005 by Raphael Langerhorst                             *
 *   raphael-langerhorst@gmx.at                                            *
 *                                                                         *
 *   Permission is hereby granted, free of charge, to any person obtaining *
 *   a copy of this software and associated documentation files (the       *
 *   "Software"), to deal in the Software without restriction, including   *
 *   without limitation the rights to use, copy, modify, merge, publish,   *
 *   distribute, sublicense, and/or sell copies of the Software, and to    *
 *   permit persons to whom the Software is furnished to do so, subject to *
 *   the following conditions:                                             *
 *                                                                         *
 *   The above copyright notice and this permission notice shall be        *
 *   included in all copies or substantial portions of the Software.       *
 *                                                                         *
 *   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,       *
 *   EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF    *
 *   MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*
 *   IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR     *
 *   OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, *
 *   ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR *
 *   OTHER DEALINGS IN THE SOFTWARE.                                       *
 ***************************************************************************/

#include "GweController.h"
#include "GDataController.h"

#include <GElement.h>
#include <GObject.h>
#include <GForm.h>
#include <GEnergy.h>
#include <GVector3.h>
#include <GMatrix44.h>
#include <GEnergy.h>
#include <GElementID.h>
#include <GElementInfluence.h>

#include <qptrlist.h>
#include <qtimer.h>

using namespace GCS;

namespace GWE
{

//see GweController.h
class QPtrListGElement : public QPtrList<GCS::GElement>
{
  public:
    virtual ~QPtrListGElement() {};
};

GweController::GweController(GDataController* data, QObject *parent, const char *name)
: QObject(parent,name),
  Data(data)
{
}

GweController::~GweController()
{
}

GDataController* GweController::getDataController()
{
  return this->Data;
}

const GDataController* GweController::getDataController() const
{
  return this->Data;
}

void GweController::connectBasicElementSignals(const GCS::GElementID& id)
{
  GElement* element = NULL;
  element = Data->getOpenElement(id);
  bool close_afterwards = false;
  if (element == NULL)
  {
    element = Data->open(id);
    close_afterwards = true;
  }
  if (element)
  {
    qDebug(QString("connecting signals and slots for element %1 !").arg(id.getID()));
    connect(element,SIGNAL(childElementCreated(GCS::GElement*)),
              Data,SLOT(add(GCS::GElement*)));
    connect(element,SIGNAL(radiateInfluence(const GCS::GElementInfluence&)),
              this,SLOT(radiateInfluence(const GCS::GElementInfluence& )));
    connect(element,SIGNAL(sendInfluence(const GCS::GElementID&, const GCS::GElementInfluence& )),
              this,SLOT(routeInfluence(const GCS::GElementID&, const GCS::GElementInfluence& )));
    connect(element,SIGNAL(formChanged(const GCS::GForm& )),
              this,SLOT(handleReparenting()));
    connect(element,SIGNAL(energyChanged(const GCS::GEnergy& )),
            this,SLOT(removeElementWithNoEnergyLeft(const GCS::GEnergy& )));
    
    if (close_afterwards)
      Data->close(id);
  }
  else
  {
    qWarning(QString("could not connect element %1 !").arg(id.getID()));
  }
}

//@todo max_traverse_children and max_traverse_parents parameters not yet used!!
QPtrListGElement GweController::findInRange(GElement* source, unsigned max_traverse_children, unsigned max_traverse_parents)
{
  QPtrListGElement list;
  
  GDataController* data = this->getDataController();
  
  Q_CHECK_PTR(source);
  
  if (source==NULL)
  {
    qWarning("source is NULL!!!");
    return list;
  }
    
  const GObject* source_o = source->getObject();
  
  bool close_parent_afterwards = false;
  
  const GElementID& parentID = source->getObject()->getParent();
  
  GElement* parent = NULL;
  
  parent = data->getOpenElement(parentID);
  
  if (parent == NULL)
  {
    close_parent_afterwards = true;
    parent = data->open(parentID);
  }
  
  Q_CHECK_PTR(parent);
        
  //now, getParent()->children() to get a list of elements with the
  //same parent as source
  
  QValueList<GElementID> children;
  
  if (parent)
  {
    //parent is affected as well
    list.append(parent);
    
    //if the element is it's own parent we do not need to go through
    //it's children here, they will all be added later anyway
    if (parentID != source->getElementID().getID())
    {
    
      children = parent->getObject()->getChildren();
      
      bool has_form = source_o->hasForm() ? true : false;
      
      if (has_form) //if the source element has a form, we need to check if they touch each other
      {
        const GForm* source_f = source_o->getForm();
        const GVector3& position = source_f->Position;
        const double range = source_f->getRadiusMax();
      
        for (QValueListIterator<GElementID> childrenID = children.begin(); childrenID != children.end(); ++childrenID)
        {
          GElement* el = data->open(*childrenID);
          Q_CHECK_PTR(el);
          if (el)
          {
            const GObject* el_o= el->getObject();
            if (el_o->hasForm())
            {
              const GForm* f = el_o->getForm();
              if ((f->Position - position).length() < (range + f->getRadiusMax()))
                list.append(el);
              else
                data->close(el_o->getID());
            }
            else
            {
              //if something has no form it is everywhere
              list.append(el);
            }
          }
        }
      }
      else //if the source element has no form, it reaches everything anyway
      {
        for (QValueListIterator<GElementID> childrenID = children.begin(); childrenID != children.end(); ++childrenID)
        {
          GElement* el = data->open(*childrenID);
          Q_CHECK_PTR(el);
          if (el)
          {
            list.append(el);
          }
        }
      }
    }
    if (close_parent_afterwards)
      data->close(parentID);
  }
  else
  {
    qDebug("element with ID " + QString::number(source->getElementID().getID()) + " has no parent!");
    //the element at least influences itself
    list.append(source);
  }
  
  //add own children - the element that radiates a certain influence DOES influence "itself" by this
  children = source->getObject()->getChildren();
  
  for (QValueListIterator<GElementID> childrenID = children.begin(); childrenID != children.end(); ++childrenID)
  {
    GElement* el = data->open(*childrenID);
    Q_CHECK_PTR(el);
    if (el)
    {
      list.append(el);
    }
  }
  
  //this list contains:
  // * the parent
  // * all children of it's parent that touch the source's form (including itself)
  // * all own children
  // ALL elements are opened and should be closed when usage finished!
  return list;
}

void GweController::executeOpenElement(const GCS::GElementID& id)
{
  GCS::GElement* element = this->Data->getOpenElement(id);
  if (element)
  {
    element->executeElement();
  }
  else
  {
    qWarning(QString("Couldn't execute element with ID %1 because it couldn't be found").arg(id.getID()));
  }
}

void GweController::radiateInfluence(const GElementInfluence& influence)
{
  GDataController* data = this->getDataController();
  
  Q_CHECK_PTR(data);
  
  
  GElement* source_element = NULL;
  
  source_element = data->getOpenElement(influence.source());
  
  if (source_element==NULL)
    source_element = data->open(influence.source());
  
  if (source_element==NULL)
  {
    qWarning("Received influence to radiate from a source that does not exist!");
    return;
  }
  
  Q_CHECK_PTR(source_element);
  
//   qDebug("got influence to radiate from " + QString::number(source_element->getElementID().getID()));
  
  QPtrList<GCS::GElement> influenced_elements = this->findInRange(source_element);
  
  //for now provide a very simple mechanism for influence distribution:
  // energy amount of the influence is evenly distributed among its receivers
  
  unsigned dest_count = influenced_elements.count();
  
  if (dest_count > 0)
  {
    double energy_level_original = influence.Energy.level();
    double energy_amount_original = influence.Energy.amount();
    double energy_sigma_original = influence.Energy.sigma();
    
    double energy_amount_single_dest = energy_amount_original/dest_count;
    
    double energy_amount_left = 0;
    
    GCS::GElementInfluence influence_partial(influence.source(),GEnergy(energy_level_original,energy_amount_single_dest,energy_sigma_original));
    
    GElement* e = influenced_elements.first();
    while(e)
    {
      
//       qDebug(" sending to " + QString::number(e->getElementID().getID()));
      e->receiveInfluence(influence_partial);
      data->close(e->getElementID());
//       energy_amount_left += influence.Energy.amount();
      e = influenced_elements.next();
    }
    
    //set the energy amount to what has been left by the receivers
    //the sender should put this energy back to it's own energy
//     influence.Energy.set(energy_level_original,energy_amount_left,energy_sigma_original);
  }
  else
  {
    qWarning("destination count for influence radiation is 0!");
  }
  
  data->close(source_element->getElementID());
}


void GweController::routeInfluence(const GElementID& destination, const GElementInfluence& influence)
{

//   qDebug("got influence to route from " + QString::number(influence.source().getID()));

  GDataController* data = this->getDataController();
  
  Q_CHECK_PTR(data);

  GElement* element = data->open(destination);
  Q_CHECK_PTR(element);
  if (element)
  {
    element->receiveInfluence(influence);
    data->close(element->getElementID());
  }
  else
  {
    element = data->open(influence.source());
    if (element==NULL)
    {
      Q_CHECK_PTR(element);
      qDebug("got influence from %lu, but this element is not stored?!",influence.source().getID());
    }
    else
    {
//       qDebug(" sending to " + QString::number(element->getElementID().getID()));
      element->receiveInfluence(influence);
    }
    data->close(element->getElementID());
  }
}

void GweController::handleReparenting()
{
//   return; //not implemented yet
  const QObject* sender_generic = sender();
  Q_ASSERT(sender_generic->inherits("GCS::GElement"));  //sender MUST be a GElement;
  if (!sender_generic->inherits("GCS::GElement"))
    return;
  
  const GCS::GElement* const_element = (const GCS::GElement*)sender_generic;
  GCS::GElement* element = this->Data->getOpenElement(const_element->getElementID());  //element MUST be open
  
  Q_CHECK_PTR(element->getObject());
  
  if (element->getObject() && element->getObject()->hasForm())
  {
    const GCS::GForm* f = element->getObject()->getForm();
    const GCS::GElementID& id = element->getElementID();
    
    const GCS::GElementID& old_parent = element->getObject()->getParent();
    
    if (old_parent.getID() == 0)
      return;
    
    if (old_parent == id)
      return;  //we're our own parent, probably the universe element
    
//     if (old_parent.getID() == 0)
//     {
//       const GCS::GElementID& new_parent = const_old_parent_element->getObject()->getParent();
//       if (new_parent == old_parent)
//       {
//         return;
//       }
//       qWarning("handling reparenting: old parent had ID 0, not notifying old parent");
//       new_parent_element->addChildElement(id);
//       GCS::GElement* new_parent_element = this->Data->open(new_parent);
//       GCS::GMatrix44 m = GCS::GMatrix44::createScaleMatrix(f_old_parent->Ellipsoid);
//       m.multiply(GCS::GMatrix44::createRotationAroundX(f_old_parent->Rotation.x));
//       m.multiply(GCS::GMatrix44::createRotationAroundY(f_old_parent->Rotation.y));
//       m.multiply(GCS::GMatrix44::createRotationAroundZ(f_old_parent->Rotation.z));
//       m.multiply(GCS::GMatrix44::createTranslationMatrix(f_old_parent->Position));
//       qDebug(QString("reparenting element %1 from old parent %2 to new parent %3").arg(QString::number(id.getID()),QString::number(old_parent.getID()),QString::number(new_parent.getID())));
//       element->reparent(old_parent,new_parent,m);
//       this->Data.close(new_parent);
//       return;
//     }
    
    const GCS::GElement* const_old_parent_element = this->Data->read(old_parent);
    
    Q_CHECK_PTR(const_old_parent_element);
    if (!const_old_parent_element)
      return;
    
    if (!const_old_parent_element->getObject()->hasForm())
      return;  //@todo: if parent has no form, traverse up until a parent with form is found
    
    const GCS::GForm* f_old_parent = const_old_parent_element->getObject()->getForm();
    
    double old_parent_radius_min = f_old_parent->getRadiusMin();
    double farthest_distance_current = f->Position.length() + f->getRadiusMax();
    
//     qDebug(QString("reparenting, old parent radius min: %1, this max distance: %2").arg(QString::number(old_parent_radius_min),QString::number(farthest_distance_current)));
    
    if (f_old_parent->getRadiusMin() < f->Position.length() + f->getRadiusMax())
    {
      const GCS::GElementID& new_parent = const_old_parent_element->getObject()->getParent();
      
      if (new_parent != old_parent)
      {
        GCS::GMatrix44 m = GCS::GMatrix44::createScaleMatrix(f_old_parent->Ellipsoid);
        m.multiply(GCS::GMatrix44::createRotationAroundX(f_old_parent->Rotation.x));
        m.multiply(GCS::GMatrix44::createRotationAroundY(f_old_parent->Rotation.y));
        m.multiply(GCS::GMatrix44::createRotationAroundZ(f_old_parent->Rotation.z));
        m.multiply(GCS::GMatrix44::createTranslationMatrix(f_old_parent->Position));
        qDebug(QString("reparenting element %1 from old parent %2 to new parent %3").arg(QString::number(id.getID()),QString::number(old_parent.getID()),QString::number(new_parent.getID())));
        element->reparent(old_parent,new_parent,m);
      }
    }
    
    //@todo IMPORTANT: check if element enters a sibling!
  }
}

void GweController::removeElementWithNoEnergyLeft(const GCS::GEnergy& changedEnergy)
{
  if (changedEnergy.amount()>0)
    return;

  const QObject* sender_generic = sender();
  Q_ASSERT(sender_generic->inherits("GCS::GElement"));  //sender MUST be a GElement;
  if (!sender_generic->inherits("GCS::GElement"))
    return;
  
  const GCS::GElement* const_element = (const GCS::GElement*)sender_generic;
  
  Q_CHECK_PTR(const_element->getObject());
  
  qDebug(QString("removing element %1 because energy amount is 0").arg(const_element->getElementID().getID()));

  Data->postDelete(const_element->getElementID());
}

void GweController::shutdown()
{
  qWarning("Shutting down world engine");
  this->Data->shutdown();
  QTimer::singleShot(2000,this,SIGNAL(quit()));
}

}


syntax highlighted by Code2HTML, v. 0.9.1