/***************************************************************************
 *   Copyright (C) 2003-2004 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 "GElement.h"

#include "GElementID.h"
#include "GObject.h"

#include "GElementInfluence.h"
#include "GAgent.h"

#include "GEnergy.h"
#include "GForm.h"

#include "GVector3.h"
#include "GMatrix44.h"

#include <qmutex.h>

#include <exception>

using namespace std;


namespace GCS
{

// ###################### PUBLIC ##################

GElement::GElement(GObject* object, const QDateTime& park_time)
: Object(object),
  ParkTime(park_time)
{
}

GElement::~GElement()
{
  //stop all running agents
  emit park();

  QValueList<GAgent*>::iterator it;
  for (it = Agents.begin(); it != Agents.end(); ++it)
  {
    (*it)->wait();  //wait until thread is stopped before deleting
    (*it)->deleteLater();  //post a delete event (the event loop will delete it)
  }

  Q_CHECK_PTR(Object);
  if (Object)
    delete Object;
}

const GElementID& GElement::getElementID() const
{
  Q_CHECK_PTR(Object);
  if (Object)
    return Object->getID();
  else
    throw exception();
}

QDateTime GElement::getParkTime() const
{
  return this->ParkTime;
}

bool GElement::isParked()
{
  //check all agents
  QValueList<GAgent*>::iterator it;
  for (it = this->Agents.begin(); it != this->Agents.end(); ++it)
  {
    if ((*it)->running())  //directly accesses the QThread method
      return false;
  }
  return true; //when no running agent was found
}

QValueList<const GAgent*> GElement::getAgents() const
{
  QValueList<const GAgent*> list;
  QValueList<GAgent*>::const_iterator it;
  for (it = this->Agents.begin(); it != this->Agents.end(); ++it)
  {
    list.append(*it);
  }
  return list;
}

const GObject* GElement::getObject() const
{
  Q_CHECK_PTR(Object);
  if (Object)
    return Object;
  else
    throw exception();
}

// ################# PUBLIC SLOTS ####################

void GElement::receiveInfluence(const GCS::GElementInfluence& influence)
{
  QTime t(QTime::currentTime());
  emit forwardInfluenceInternal(influence);
  
  //add the energy of the influence to own energy
  if (Object)
  {
    if (Object->hasEnergy())
    {
      GEnergy* e = Object->getEnergy();
      QMutexLocker m(e);
      e->put(influence.Energy);
    }
  }
  
  if (t.elapsed() > 100)
  {
    qWarning("Internal influence processing time longer than 100 milliseconds, this is bad agent design!!!");
  }
  emit influenceReceived(influence);
  
  if (t.elapsed() > 100)
  {
    qWarning("External influence processing time longer than 100 milliseconds, this is bad agent design!!!");
  }
}

void GElement::reparent(const GCS::GElementID& old_parent, const GCS::GElementID& new_parent, const GCS::GMatrix44& transformation)
{
  if (Object)
  {
    if (old_parent != Object->getParent())
      qWarning(QString("INCONSISTENCY DETECTED: Reparenting element %1, but old parent doesn't apply!").arg(old_parent.getID()));
    
    Object->reparent(new_parent);
    
    emit this->notifyReparentingInternal(old_parent,new_parent,transformation);
    
    if (this->Object->hasForm())
    {
      GCS::GForm* f = Object->getForm();
      QMutexLocker lock(f);
      f->Position = transformation.transform(f->Position);
      f->Rotation = transformation.transform(f->Rotation);
    }
    
    emit this->parentChanged(this,old_parent,new_parent,transformation);
  }
}

void GElement::addAgent(GCS::GAgent* agent)
{
  agent->Object = this->Object;
  agent->Agents = &this->Agents;

  //connect signals from agent

  connect(agent,SIGNAL(
           sendInfluence(const GCS::GElementID&, const GCS::GElementInfluence&)),
           this,SIGNAL(
           sendInfluence(const GCS::GElementID&, const GCS::GElementInfluence&)));

  connect(agent,SIGNAL(radiateInfluence(const GCS::GElementInfluence&)),
           this,SIGNAL(radiateInfluence(const GCS::GElementInfluence&)));

  //connect slots from agent
  connect(this,SIGNAL(forwardInfluenceInternal(const GCS::GElementInfluence&)),
         agent,SLOT(receiveInfluence(const GCS::GElementInfluence&)));


  // control stuff (parking, executing)
   
  connect(this,SIGNAL(park()),
         agent,SLOT(beginPark()));

  // execute is used for "resuming" AND for starting the first time(!)
  connect(this,SIGNAL(execute(double)),
         agent,SLOT(threadStart(double)));
            
            
  // data change stuff
  
  connect(this,SIGNAL(notifyReparentingInternal(const GCS::GElementID&, const GCS::GElementID&, const GCS::GMatrix44& )),
          agent,SLOT(reparented(const GCS::GElementID&, const GCS::GElementID&, const GCS::GMatrix44& )));
  
  connect(agent,SIGNAL(agentChanged(const GCS::GAgent& )),
           this,SIGNAL(agentChanged(const GCS::GAgent& )));

  connect(agent,SIGNAL(energyChanged(const GCS::GEnergy& )),
           this,SIGNAL(energyChanged(const GCS::GEnergy& )));

  connect(agent,SIGNAL(formChanged(const GCS::GForm& )),
           this,SIGNAL(formChanged(const GCS::GForm& )));

  connect(agent,SIGNAL(childElementCreated(GCS::GElement* )),
           this,SIGNAL(childElementCreated(GCS::GElement* )));

  connect(agent,SIGNAL(childElementRemoved(const GCS::GElementID& )),
           this,SIGNAL(childElementRemoved(const GCS::GElementID& )));

  this->Agents.append(agent);
}

void GElement::removeAgent(GCS::GAgent* agent, bool del)
{
  //park the agent
  agent->beginPark();
  
  //wait until the agent is parked (the agent thread stops)
  agent->wait();

  agent->Object = NULL;
  agent->Agents = NULL;

  //disconnect agent  
  agent->disconnect(this);
  this->disconnect(agent);

  this->Agents.remove(agent);
  
  if (del)
    delete agent;
}

void GElement::parkElement()
{
//   qDebug("Parking element " + QString::number(getElementID().getID()));
  emit park();
  this->ParkTime = QDateTime::currentDateTime();
}

void GElement::executeElement(double seconds_delta_t_offset)
{
//   qDebug("Executing element " + QString::number(getElementID().getID()));
  QDateTime current = QDateTime::currentDateTime();
  double delta_t = (double)(this->ParkTime.secsTo(current)) + seconds_delta_t_offset;
  emit execute(delta_t);
}

}


syntax highlighted by Code2HTML, v. 0.9.1