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

#include "GCoreXmlSerializer.h"

#include "GStorage.h"
#include "GXmlNetwork.h"

#include "GXmppNetwork.h"

#include <GElement.h>
#include <GElementID.h>
#include <GMatrix44.h>
#include <GVector3.h>

#include <qapplication.h>
#include <qtimer.h>

namespace GWE
{

GXmlDataController::GXmlDataController(GStorage* storage, GXmlNetwork* network, const QString& master_server, QObject *parent, const char *name)
: GDataController(parent,name),
  Serializer(new GCoreXmlSerializer(this,this,"XML Serializer")),
  Storage(storage),
  Network(network),
  MasterServer(master_server)
{
  if (master_server == this->Network->getNetworkId())  // we can't be our own master
    this->MasterServer = "";
  connect(network,SIGNAL(networkConnected()),this,SLOT(registerWithMaster()));
  connect(network,SIGNAL(dataAvailable(QDomElement, const QString& )),this,SLOT(receiveData(QDomElement, const QString& )));
  
  connect(network,SIGNAL(presenceChanged(QString, bool )),this,SLOT(updateServerPresence(QString, bool )));
  
//   connect(this,SIGNAL(elementUpdated(const GCS::GElementID& )),this,SLOT(postSyndication(const GCS::GElementID& )));
  connect(this,SIGNAL(elementAdded(const GCS::GElementID& )),this,SLOT( postSyndication(const GCS::GElementID&)));
  
  QTimer* checksyndication = new QTimer(this,"check syndication timer");
  connect(checksyndication,SIGNAL(timeout()),this,SLOT(checkElementsForSyndication()));
  checksyndication->start(207);  //distribute performance nicely - not all events should be at the same time (1 sec interval)
  

  //called by the GweController
//   connect(qApp,SIGNAL(aboutToQuit()),this,SLOT(shutdown()));

  if (this->isMasterServer())
  {
    int size_uint = sizeof(unsigned long);
    unsigned long upper_bound = 65535; // init to 2^16 by default
//     if (size_uint >= 8)
//     {
//       upper_bound = 18446744073709551615; // init to 2^64 - 1
//     }
//     else if (size_uint >= 6)
//     {
//       upper_bound = 281474976710655; // init to 2^48 - 1
//     }
    if (size_uint >= 4)
    {
      upper_bound = 4294967295; // init to 2^32 - 1
    }
    else
    {
      qWarning("The length of the unsigned long data type is very short, it should be at least 4 Bytes.");
    }
    GCS::GElementID::addFreeIDRange(1,upper_bound);  //use 1 for the galaxy
  }
  else
  {
    QTimer* t = new QTimer(this,"free ID timer");
    connect(t,SIGNAL(timeout()),this,SLOT(checkFreeIDs()));
    t->start(5000); //5 seconds should be fine for now, don't set it lower than 3000
  }
}

GXmlDataController::~GXmlDataController()
{
  // we don't delete the serializer because GXmlDataController is the parent
  // - in terms of QObject - of the serializer (it is automatically deleted)
  
  if (Storage)
  {
    delete Storage;
    Storage=NULL;
  }
  if (Network)
  {
    delete Network;
    Network=NULL;
  }
}

bool GXmlDataController::add(GCS::GElement* element)
{
  try
  {
    Storage->lock();
    Storage->addElement(element,this->Network->getNetworkId());
    Storage->unlock();
    this->OpenElements.insert(element->getElementID(),element);
    this->prepareOpenedElement(element);
    emit this->elementAdded(element->getElementID());
    return true;
  }
  catch (GStorageException e)
  {
    Storage->unlock();
    qWarning(QString("Couldn't add element with ID %1 to storage!").arg(element->getElementID().getID()));
    qWarning(e.toString());
    return false;
  }
}

bool GXmlDataController::writeOpenElementToStorage(const GCS::GElementID& id)
{
  if (this->OpenElements.contains(id))
  {
    GCS::GElement* element = this->OpenElements[id];
    if (element)
    {
      try
      {
        Storage->lock();
        this->Storage->updateElement(element);
        Storage->unlock();
        return true;
      }
      catch (GStorageException e)
      {
        Storage->unlock();
        qWarning(QString("Couldn't write open element with ID %1 to storage!").arg(id.getID()));
        qWarning(e.toString());
        return false;
      }
    }
    else
    {
      qWarning(QString("INCONSISTENCY DETECTED: element %1 detected as open, but could not be found.").arg(id.getID()));
    }
  }
  else
  {
    qWarning(QString("Can't write open element to storage because element with ID %1 is not open").arg(id.getID()));
  }
  return false;
}

const GCS::GElement* GXmlDataController::read(const GCS::GElementID& id) const
{
  if (id.getID() == 0)
  {
    qWarning("read: given element id is 0, returning NULL");
    return NULL;
  }
  if (this->OpenElements.contains(id))
  {
    return OpenElements[id];
  }
  else
  {
    try
    {
      Storage->lock();
      GCS::GElement* element = this->Storage->getElement(id,this);
      Storage->unlock();
      return element;
    }
    catch (GStorageException e)
    {
      Storage->unlock();
      qWarning(QString("Couldn't read element with ID %1 from storage!").arg(id.getID()));
      qWarning(e.toString());
      return NULL;
    }
  }
}

QValueList<GCS::GElementID> GXmlDataController::getChildren(const GCS::GElementID& parent) const
{
  QValueList<GCS::GElementID> children;
  try
  {
    Storage->lock();
    children = this->Storage->getChildren(parent);
    Storage->unlock();
  }
  catch (GStorageException e)
  {
    Storage->unlock();
    qWarning(QString("Couldn't read children of element with ID %1 from storage!").arg(parent.getID()));
    qWarning(e.toString());
  }
  return children;
}

GCS::GElement* GXmlDataController::open(const GCS::GElementID& id)
{
  if (id.getID() == 0)
  {
    qWarning("open: given element id is 0, returning NULL");
    return NULL;
  }
  if (this->OpenElements.contains(id))
  {
    qWarning("Element already opened, returning open element");
    return this->getOpenElement(id);
  }
  
  try
  {
    Storage->lock();
    GCS::GElement* element = this->Storage->getElement(id,this);
    Storage->unlock();
    prepareOpenedElement(element);
    emit this->elementOpened(id);
    return element;
  }
  catch (GStorageException e)
  {
    Storage->unlock();
    qWarning(QString("Couldn't read element with ID %1 from storage!").arg(id.getID()));
    qWarning(e.toString());
    return NULL;
  }
}

GCS::GElement* GXmlDataController::getOpenElement(const GCS::GElementID& id)
{
  return OpenElements[id];
}

const QValueList<GCS::GElementID> GXmlDataController::getListOfOpenElements()
{
  QValueList<GCS::GElementID> list(this->OpenElements.keys());
  return list;
}

const QValueList<GCS::GElementID> GXmlDataController::getListOfAllElements()
{
  QValueList<GCS::GElementID> list;
  try
  {
    Storage->lock();
    list = this->Storage->getAllElementIDs();
    Storage->unlock();
  }
  catch (GStorageException e)
  {
    Storage->unlock();
    qWarning(QString("Couldn't get element IDs from storage!"));
    qWarning(e.toString());
  }

  return list;
}

bool GXmlDataController::close(const GCS::GElementID& id)
{
  if (this->OpenElements.contains(id))
  {
    GCS::GElement* element = this->getOpenElement(id);
    Q_CHECK_PTR(element);
    if (element)
    {
      bool resume_later = true;
      if (element->isParked())
        resume_later = false;
      element->parkElement();
      if (this->writeOpenElementToStorage(id))
      {
        this->OpenElements.remove(id);
        element->deleteLater();
        emit this->elementClosed(id);
        return true;
      }
      else
      {
        qWarning("Couldn't store element back to storage!");
        if (resume_later)
        {
          qWarning(" Resuming element execution.");
          element->executeElement(); //resume
        }
        return false;
      }
    }
    else
    {
      qWarning("INCONSISTENCY DETECTED: Couldn't find element although it was declared open!");
      return false;
    }
  }
  else
  {
    qWarning("Can't close element, it is not opened, returning true");
    return true;
  }
}
    
bool GXmlDataController::postDelete(const GCS::GElementID& id)
{
  //if an element should be deleted that does not belong to this server... this can't happen because an element can only delete itself (think about it).
  //note: elements are also deleted by the data controller when a child server unregisters.
  qDebug(QString("Deleting element %1").arg(QString::number(id.getID())));
  
  if (this->close(id))
  {
    qWarning("Element was still open, it is now closed");
  }
  
  try
  {
    Storage->lock();
    this->Storage->removeElement(id);
    Storage->unlock();
  }
  catch (GStorageException e)
  {
    Storage->unlock();
    qWarning(QString("Couldn't delete element with ID %1 from storage!").arg(id.getID()));
    qWarning(e.toString());
  }
  //add back the element ID to the available free IDs
  //this is NOT optimal, we should be able to consolidate ranges.
  //@todo add GCS::GElementID::consolidateRanges() which looks for ranges that have upper bound 1 + 1 = lower bound 2
  GCS::GElementID::addFreeIDRange(id.getID(),id.getID());
  emit this->elementDeleted(id);
  return true;
}

bool GXmlDataController::isMasterServer()
{
  if (this->MasterServer.isEmpty())
    return true;
  return false;
}

QString GXmlDataController::getManagingServerOfElement(const GCS::GElementID& id) const
{
  try
  {
    Storage->lock();
    QString server = Storage->getManagingServerForElement(id);
    Storage->unlock();
    return server;
  }
  catch (GStorageException e)
  {
    Storage->unlock();
    qWarning(QString("Couldn't get managing server for element %1 from storage!").arg(id.toString()));
    qWarning(e.toString());
    return "";
  }
}

QStringList GXmlDataController::getAllKnownServers() const
{
  QStringList servers;
  try
  {
    Storage->lock();
    QStringList servers = Storage->getAllServers();
    Storage->unlock();
    return servers;
  }
  catch (GStorageException e)
  {
    Storage->unlock();
    qWarning(QString("Couldn't get servers from storage!"));
    qWarning(e.toString());
    return "";
  }
}

void GXmlDataController::shutdown()
{
  qDebug("Shutting down XML Data Controller");
  QValueList<GCS::GElementID> oe = OpenElements.keys();
  QValueList<GCS::GElementID>::iterator it;
  for (it = oe.begin(); it != oe.end(); ++it)
  {
    this->close(*it);
  }
  this->LastSyndicationTime.clear();  //make sure syndication can be done...
  this->checkElementsForSyndication();  //syndicate all remaining updates...
  if (!this->PendingSyndication.isEmpty())
  {
    qWarning("Pending Syndications NOT empty after final syndication!");
    this->PendingSyndication.clear();  //this *should* already be empty, but just to make sure...
  }
  if (!this->isMasterServer())
  {
    this->sendFreeIDs(this->MasterServer,GCS::GElementID::countFreeIDs());
  }

  this->unregisterFromAllKnownServers();
  //we need to implement a way to "wait" for all data to be sent, flush should do this...
  this->Network->flushOutput();
//   this->Network->closeNetwork();
}

void GXmlDataController::registerWithMaster()
{
  if (!Network->isConnected())
  {
    qWarning("Can't register with master server because network is not connected!");
  }
  else if (this->MasterServer.isEmpty())
  {
    qWarning("Can't register with master server because no master server is set!");
    qWarning("This usually means that this server is the master server!");
  }
  else
  {
    qDebug("Registering with master server");
    QDomDocument data;
    QDomElement e = data.createElement("register");
    data.appendChild(e);
    QDomElement version = data.createElement("version");
    e.appendChild(version);
    version.appendChild(data.createTextNode("0.5.1"));
    this->Network->send(e,this->MasterServer);

//     this->requestFreeIDs(1000);
  }
}

void GXmlDataController::unregisterFromMaster()
{
  if (!Network->isConnected())
  {
    qWarning("Can't unregister from master server because network is not connected!");
  }
  else if (this->MasterServer.isEmpty())
  {
    qWarning("Can't unregister from master server because no master server is set!");
  }
  else
  {
    //@todo send back all free IDs, send back all primary element data
    qDebug("Unregistering from master server");
    QDomDocument data;
    QDomElement e = data.createElement("unregister");
    data.appendChild(e);
    this->Network->send(e,this->MasterServer);
  }
}

void GXmlDataController::unregisterFromAllKnownServers()
{
  if (!Network->isConnected())
  {
    qWarning("Can't unregister from all servers because network is not connected!");
  }
  else
  {
    qDebug("Unregistering from all known servers");
    QDomDocument data;
    QDomElement e = data.createElement("unregister");
    data.appendChild(e);
    QValueList<QString> known = this->getAllKnownServers();
    QValueList<QString>::iterator it;
    for (it = known.begin(); it != known.end(); ++it)
    {
      qDebug(QString("Unregistering from %1").arg(*it));
      this->Network->send(e,*it);
    }
  }
}

void GXmlDataController::checkFreeIDs()
{
  //in case we're already really low!
  if (GCS::GElementID::countFreeIDs() < 400)
  {
    requestFreeIDs(2000);
  }
  else if (GCS::GElementID::countFreeIDs() < 800)
  {
    requestFreeIDs(500);
  }
}

void GXmlDataController::requestFreeIDs(unsigned long amount)
{
  if (amount == 0)
  {
    qWarning("I'm not going to request 0 free IDs!");
    return;
  }
  
  if (this->MasterServer.isEmpty())
  {
    qWarning("Can't request free GElementIDs because no master server is set!");
    return;
  }

  if (!this->Network->isConnected())
  {
    qWarning("Can't request free GElementIDs because network is not connected!");
    return;
  }

  qDebug(QString("Requesting %1 free element IDs").arg(QString::number(amount)));
  QDomDocument d;
  QDomElement e = d.createElement("requestfreeids");
  d.appendChild(e);
  e.appendChild(d.createTextNode(QString::number(amount)));
  this->Network->send(e,this->MasterServer);
}

void GXmlDataController::sendFreeIDs(QString server, unsigned long amount)
{
  GCS::GIDContainer c = GCS::GElementID::getFreeIDRange(amount);
  QDomDocument d;
  QDomElement e = d.createElement("freeids");
  d.appendChild(e);
      
  QValueList<GCS::GIDRange*>::iterator it;
  for (it = c.begin(); it != c.end(); ++it)
  {
    QDomElement range = d.createElement("range");
    e.appendChild(range);
    QDomElement from = d.createElement("from");
    range.appendChild(from);
    from.appendChild(d.createTextNode(QString::number((*it)->getLowerBound())));
    QDomElement to = d.createElement("to");
    range.appendChild(to);
    to.appendChild(d.createTextNode(QString::number((*it)->getUpperBound())));
  }
  this->Network->send(e,server);
}

void GXmlDataController::postSyndication(const GCS::GElementID& id)
{
  if (!this->PendingSyndication.contains(id))
    this->PendingSyndication.append(id);
}

void GXmlDataController::checkElementsForSyndication()
{
  QValueList<GCS::GElementID>::iterator it;
  QDateTime current = QDateTime::currentDateTime();
  QValueList<GCS::GElementID> to_be_or_not_to_be;
  for (it = this->PendingSyndication.begin(); it != this->PendingSyndication.end(); ++it)
  {
    bool send = false;
    if (this->LastSyndicationTime.contains(*it))
    {
      QDateTime datetime = this->LastSyndicationTime[*it];
      if (datetime.secsTo(current)>3)  // THIS is the minimum time between two syndications of the same element
        send = true;
    }
    else
    {
      send = true;
    }
    
    if (send)
    {
      this->syndicateElementData(*it);
      to_be_or_not_to_be.append(*it);
//       this->PendingSyndication.remove(*it);  // NEVER EVER DO THAT
    }
  }
  
  for (it = to_be_or_not_to_be.begin(); it != to_be_or_not_to_be.end(); ++it)
  {
    this->PendingSyndication.remove(*it);  //remove by value
  }
}

void GXmlDataController::syndicateElementDataToServer(const GCS::GElementID& id,const QString& server)
{
  if (server == this->Network->getNetworkId())
  {
    qWarning(QString("Not syndicating element %1 to self!").arg(id.toString()));
  }
  else
  {
    QString managing_server;
    try
    {
      Storage->lock();
      managing_server = this->Storage->getManagingServerForElement(id);
      Storage->unlock();
      if (managing_server == server)
      {
        qWarning(QString("Not syndicating element %1 to server %2 because given server manages given element").arg(QString::number(id.getID())).arg(server));
        return;
      }
    }
    catch (GStorageException exception)
    {
      Storage->unlock();
      qWarning(QString("Could not get managing server for element %1 from storage!").arg(id.toString()));
    }
    QDomElement e;
    if (OpenElements.contains(id))
    {
      e = Serializer->serializeElement(OpenElements[id],"GElement");
    }
    else
    {
      try
      {
        Storage->lock();
        e = Serializer->serializeElement(Storage->getElement(id,this),"GElement");
        Storage->unlock();
      }
      catch (GStorageException exception)
      {
        Storage->unlock();
        qWarning(QString("Couldn't get element %1 from storage!").arg(id.toString()));
        qWarning(exception.toString());
      }
    }
    if (e.isNull())
    {
      qWarning(QString("Couldn't get data for element with ID %1").arg(id.getID()));
    }
    else
    {
      if (!managing_server.isEmpty())
        e.setAttribute("owner",managing_server);

      this->Network->send(e,server);
    }
  }
}

void GXmlDataController::syndicateElementData(const GCS::GElementID& id)
{
  if (this->LastSyndicationTime.contains(id))
    this->LastSyndicationTime.replace(id,QDateTime::currentDateTime());  //only applies to syndicateElementData()
  else
    this->LastSyndicationTime.insert(id,QDateTime::currentDateTime());  //only applies to syndicateElementData()
  
  //@todo use syndicateElementDataToServer(const GCS::GElementID& id,const QString& server)
//   if (this->PrimaryElements.contains(id) || this->SecondaryElements.contains(id) && ChildServers.contains(ElementServerMapping[id]))
//   {
    QDomElement e;
    if (OpenElements.contains(id))
    {
      e = Serializer->serializeElement(OpenElements[id],"GElement");
    }
    else
    {
      try
      {
        Storage->lock();
        e = Serializer->serializeElement(Storage->getElement(id,this),"GElement");
        Storage->unlock();
      }
      catch (GStorageException exception)
      {
        Storage->unlock();
        qWarning(QString("Could not get element %1 from storage").arg(id.toString()));
      }
    }
    if (e.isNull())
    {
      qWarning(QString("Couldn't get data for element with ID %1").arg(id.getID()));
    }
    else
    {
      QString managing_server;
      try
      {
        Storage->lock();
        managing_server = Storage->getManagingServerForElement(id);
        Storage->unlock();
      }
      catch (GStorageException e)
      {
        Storage->unlock();
        qWarning(QString("Couldn't get managing server for element %1 from storage!").arg(id.toString()));
        qWarning(e.toString());
      }

      if (!managing_server.isEmpty())
        e.setAttribute("owner",managing_server);
      
      
      //@todo this is a HACK until subscriptions are used!
      //The gweserver table in the GStorage has a column with master servers
      //for every server, this way a hierarchical structure can be retrieved
      //for this particular element - use it to define syndication logic without
      //creating loops. Also consider subscriptions(?)
      
      //send to master server if this is NOT a master server
      if (!this->isMasterServer())
      {
        
        //@todo this is probably inefficient, maybe find a way to efficiently send element data to multiple destinations (see XMPP specs, since this needs to be routed).
        if (!this->MasterServer.isEmpty())
        {
          if (managing_server != this->MasterServer)
            this->Network->send(e,this->MasterServer);
        }
      }
      else  //else send to all child servers
      {
        try
        {
          Storage->lock();
          //@todo update this HACK as soon as subscriptions are in use
  //         QStringList child_servers = this->Storage->getServersForElementChildren(id);
          QStringList child_servers = this->Storage->getAllServers();
          Storage->unlock();
          QStringList::iterator it;
          for (it = child_servers.begin(); it != child_servers.end(); ++it)
          {
            if (managing_server != (*it) && this->Network->getNetworkId() != (*it))
            {
              this->Network->send(e,(*it));
            }
          }
        }
        catch (GStorageException exception)
        {
          Storage->unlock();
          qWarning(QString("Couldn't get list of servers for children of element %1 from storage! Element not syndicated to child servers!").arg(id.toString()));
          qWarning(exception.toString());
        }
      }
    }
//   }
}

void GXmlDataController::syndicateAllElementDataToServer(const QString& server)
{
  qWarning(QString("Syndicating all known elements to %1, this could cause high network load!").arg(server));
  const QValueList<GCS::GElementID> list = this->getListOfAllElements();
  QValueList<GCS::GElementID>::const_iterator it;
  for (it=list.begin(); it != list.end(); ++it)
  {
    syndicateElementDataToServer((*it),server);
  }
}

void GXmlDataController::updateServerPresence(QString server, bool available)
{
  QString presence = "0";
  if (available)
    presence = "1";
  
  bool internal = false;
  
  try
  {
    Storage->lock();
    if (this->Storage->getServerExists(server))  //else its external
    {
      internal = true;
      this->Storage->updateServerPresence(server,presence);
    }
    Storage->unlock();
  }
  catch (GStorageException exception)
  {
    Storage->unlock();
    qWarning(QString("Couldn't update server presence for server %1 with presence %2!").arg(server).arg(presence));
    qWarning(exception.toString());
  }
  
  emit this->serverPresenceChanged(server,available,internal);
  
  //HACK for pre XMPP 1.0 protocol
  if (!available)
    this->processUnregister(server);
}

void GXmlDataController::prepareOpenedElement(GCS::GElement* element)
{
  this->OpenElements.insert(element->getElementID(),element);
  connect(element,SIGNAL(parentChanged(GCS::GElement*, const GCS::GElementID&, const GCS::GElementID&, const GCS::GMatrix44& )),
          this,SLOT(processReparenting(GCS::GElement*, const GCS::GElementID&, const GCS::GElementID&, const GCS::GMatrix44& )));
  connect(element,SIGNAL(influenceReceived(const GCS::GElementInfluence& )),
          this,SLOT(processInfluencing(const GCS::GElementInfluence& )));
  connect(element,SIGNAL(agentChanged(const GCS::GAgent& )),this,SLOT(processAgentChanged(const GCS::GAgent& )));
}

void GXmlDataController::processReparenting(GCS::GElement* element, const GCS::GElementID& oldParent, const GCS::GElementID& newParent,const GCS::GMatrix44& transformation)
{
  QDomDocument d;
  QDomElement e = d.createElement("reparent");
  d.appendChild(e);
  e.appendChild(d.importNode(this->Serializer->serializeElementID(element->getElementID(),"element"),true));
  e.appendChild(d.importNode(this->Serializer->serializeElementID(oldParent,"from"),true));
  e.appendChild(d.importNode(this->Serializer->serializeElementID(newParent,"to"),true));
  e.appendChild(d.importNode(this->Serializer->serializeMatrix44(transformation,"transformation"),true));

  QString managing_server_element;
  QString managing_server_old_parent;
  QString managing_server_new_parent;
  
  QString remote; //used temporary
  
  QStringList sentto;
  
  // perform reparenting
  try
  {
    Storage->lock();
    this->Storage->reparentElement(element->getElementID(),oldParent,newParent);
    Storage->unlock();
  }
  catch (GStorageException exception)
  {
    Storage->unlock();
    qWarning("Could not reparent element in storage!");
  }
  
  //element itself
  try
  {
    Storage->lock();
    managing_server_element = this->Storage->getManagingServerForElement(element->getElementID());
    Storage->unlock();
    remote = managing_server_element;
    
    if (!remote.isEmpty() && remote != this->Network->getNetworkId() && !sentto.contains(remote))
    {
      this->Network->send(e,remote);
      sentto.append(remote);
    }
  }
  catch (GStorageException exception)
  {
    Storage->unlock();
    qWarning(QString("Could find managing server of element %1 in storage!").arg(element->getElementID().toString()));
  }
  
  //elements do not directly know about their children, so we do not need to update either old or new parent
//   //old parent
//   try
//   {
//     Storage->lock();
//     managing_server_old_parent = this->Storage->getManagingServerForElement(oldParent);
//     Storage->unlock();
//     remote = managing_server_old_parent;
//     
//     if (!remote.isEmpty() && remote != this->Network->getNetworkId() && !sentto.contains(remote))
//     {
//       this->Network->send(e,remote);
//       sentto.append(remote);
//     }
//   }
//   catch (GStorageException exception)
//   {
//     Storage->unlock();
//     qWarning("Could find old parent element in storage!");
//   }
//   
//   //new parent
//   try
//   {
//     Storage->lock();
//     managing_server_new_parent = this->Storage->getManagingServerForElement(newParent);
//     Storage->unlock();
//     remote = managing_server_new_parent;
//     
//     if (!remote.isEmpty() && remote != this->Network->getNetworkId() && !sentto.contains(remote))
//     {
//       this->Network->send(e,remote);
//       sentto.append(remote);
//     }
//   }
//   catch (GStorageException exception)
//   {
//     Storage->unlock();
//     qWarning("Could find old parent element in storage!");
//   }
  
  //all processed or should we send to master server as well?
  
//   remote = this->MasterServer;
//   if ( (managing_server_element.isEmpty() || managing_server_old_parent.isEmpty() || managing_server_new_parent.isEmpty()) && !sentto.contains(remote))
//   {
//     this->Network->send(e,remote);
//     sentto.append(remote);
//   }
  
  //make sure others are notified
//   this->postSyndication(oldParent);
//   this->postSyndication(newParent);
  this->postSyndication(element->getElementID());
}

void GXmlDataController::processInfluencing(const GCS::GElementInfluence& influence)
{
  const QObject* sender_generic = sender();
  if (sender_generic==NULL)
  {
    qWarning("GXmlDataController::processInfluencing() called without a sender()!");
    return;
  }
  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;
  
  QString managing_server;
  try
  {
    Storage->lock();
    managing_server = this->Storage->getManagingServerForElement(const_element->getElementID());
    Storage->unlock();
  }
  catch(GStorageException exception)
  {
    Storage->unlock();
    qWarning(QString("Could not find managing server for element %1").arg(const_element->getElementID().toString()));
    qWarning(exception.toString());
    return;
  }
  
  if (managing_server != this->Network->getNetworkId())
  {
    QDomElement e = this->Serializer->serializeElementInfluence(influence,const_element->getElementID(),"GElementInfluence");
    if (e.isNull())
    {
      qWarning("Serializing influence returned a NULL element");
    }
    else
    {
      this->Network->send(e,managing_server);
    }
  }
}

void GXmlDataController::processAgentChanged(const GCS::GAgent& agent)
{
  this->postSyndication(agent.getElementID());
}

void GXmlDataController::processUnregister(const QString& server)
{
  if (!this->MasterServer.isEmpty() && this->MasterServer == server)
  {
    //@todo we should manage this more elegantly!
    qWarning(" ");
    qWarning("SHUTTING DOWN BECAUSE MASTER SERVER HAS SHUT DOWN!!!");
    qWarning("IF THIS IS UNEXPECTED TO YOU, PLEASE CONTACT THE MASTER SERVER ADMINISTRATOR");
    qWarning("OR CONTACT THE G SYSTEM TEAM.");
    qWarning(" ");
    this->shutdown();
    qWarning("Shutting down in 2 seconds...");
    QTimer::singleShot(2000,qApp,SLOT(quit()));
  }
  else
  {
    //remove associated elements
    qDebug(QString("Removing all elements associated with GWE Server %1").arg(server));
    try
    {
      Storage->lock();
      QValueList<GCS::GElementID> to_be_deleted = this->Storage->getElementsForServer(server);
      Storage->unlock();
    
      QValueList<GCS::GElementID>::iterator it_del;
      for (it_del = to_be_deleted.begin(); it_del != to_be_deleted.end(); ++it_del)
      {
        this->postDelete(*it_del);
      }
    }
    catch (GStorageException exception)
    {
      Storage->unlock();
      qWarning(QString("Could not retrieve elements associated with GWE Server %1").arg(server));
      qWarning(exception.toString());
    }
    
    //remove server
    try
    {
      Storage->lock();
      this->Storage->removeServer(server);
      if (this->Storage->getServerExists(server))
      {
        qWarning(QString("Removed %1 from storage, but the entry still exists! There is sth wrong with the storage!").arg(server));
      }
      Storage->unlock();
    }
    catch (GStorageException exception)
    {
      Storage->unlock();
      qWarning(QString("Could not remove GWE Server %1").arg(server));
      qWarning(exception.toString());
    }
  }
}

void GXmlDataController::receiveData(QDomElement data, const QString& sender)
{
  QString message = data.tagName();
  
  if (message == "GElement")
  {
    qDebug("Received a secondary GCS::GElement");
    
    QString managing_server = sender;
    
    if (data.hasAttribute("owner"))
      managing_server = data.attribute("owner",sender);
    
    try
    {
      this->Storage->lock();
      if (!sender.isEmpty())
      {
        if (!this->Storage->getServerExists(sender))
          this->Storage->addServer(sender,"1");
      }
      if (!managing_server.isEmpty() && managing_server != sender)
      {
        if (!this->Storage->getServerExists(managing_server))
          this->Storage->addServer(managing_server,"1");
      }
      this->Storage->unlock();
    }
    catch (GStorageException exc)
    {
      this->Storage->unlock();
      qWarning("Could not update server information in storage!");
      qWarning(exc.toString());
    }
    
    emit this->serverPresenceChanged(sender,true,true);
    if (managing_server != sender)
      emit this->serverPresenceChanged(managing_server,true,true);
    
    if (managing_server == this->Network->getNetworkId())
    {
      qWarning(QString("INCONSISTENCY DETECTED: Received data from server %1 about a primary element on this server!!").arg(sender));
      qWarning(" GWE Servers are probably inconsistent!!");
      qWarning(" Another GWE Server probably has this element stored as primary as well!!");
//       qWarning(QString(" Element ID is: %1").arg(id.getID()));
      return;
    }
    
    //basically it looks like we don't need to create the element,
    //but we want to check if the received data is valid.
    // -- we need it for checking secondary elements.
    GCS::GElement* element = this->Serializer->createElement(data);
    
    if (element)
    {
      const GCS::GElementID id = element->getElementID();
      qDebug(QString("adding/updating secondary element with ID %1").arg(id.toString()));

      if (this->OpenElements.contains(id))
      {
        qDebug("received an update for an open element, temporarily closing element for update");
        this->close(id);
      }
      
      bool element_added = false;
      bool element_updated = false;
      
      try
      {
        this->Storage->lock();
        if (this->Storage->getElementExists(id))
        {
          this->Storage->updateElement(element);
          this->Storage->updateManagingServer(id,managing_server);
          element_updated = true;
        }
        else
        {
          this->Storage->addElement(element,managing_server);
          element_added = true;
        }
      }
      catch (GStorageException exception)
      {
        qWarning("Failed to update element in storage!");
        qWarning(exception.toString());
      }
      this->Storage->unlock();

      
      this->OpenElements.insert(element->getElementID(),element);
      this->prepareOpenedElement(element);
      
      if (element_added)
        emit this->elementAdded(id);
      if (element_updated)
        emit this->elementUpdated(id);
      
      emit this->elementOpened(id);
      
      //@todo only syndicate on element_added, it is a HACK until subscriptions are used
      if (this->isMasterServer())
      {
        this->postSyndication(id);
      }
    }
    else
    {
      qWarning(QString("received element from ") + sender + " but couldn't deserialize it! Received XML Document: ");
      qWarning(data.ownerDocument().toString());
    }
  }
  else if (message == "GElementInfluence")
  {
    //@todo continue from here:
    GCS::GElementID id(this->Serializer->getInfluenceTarget(data));
    if (id.getID() != 0)
    {
      GCS::GElement* element = this->getOpenElement(id);
      if (element == NULL)
      {
        element = this->open(id);
      }
      GCS::GElementInfluence influence = this->Serializer->createElementInfluence(data);
      element->receiveInfluence(influence);
    }
  }
  else if (message == "reparent")
  {
    QDomElement dom_element = data.elementsByTagName("element").item(0).toElement();
    QDomElement dom_from = data.elementsByTagName("from").item(0).toElement();
    QDomElement dom_to = data.elementsByTagName("to").item(0).toElement();
    QDomElement dom_transform = data.elementsByTagName("transformation").item(0).toElement();
    if (dom_element.isNull() || dom_from.isNull() || dom_to.isNull())
    {
      qWarning(QString("Can't interpret reparent message from %1, required XML elements are missing").arg(sender));
      qWarning(" XML document:");
      qWarning(data.ownerDocument().toString());
    }
    else
    {
      GCS::GElementID element = this->Serializer->createElementID(dom_element);
      GCS::GElementID from = this->Serializer->createElementID(dom_from);
      GCS::GElementID to = this->Serializer->createElementID(dom_to);
      
      GCS::GMatrix44 transform;
      if (!dom_transform.isNull())
      {
        transform = this->Serializer->createMatrix44(dom_transform);
      }
      
      if (element.getID() == 0 || from.getID() == 0 || to.getID() == 0)
      {
        qWarning("One or more of the received IDs for reparenting are zero, this is likely to be invalid.");
      }
      
      try
      {
        Storage->lock();
        this->Storage->reparentElement(element,from,to);
        Storage->unlock();
      }
      catch(GStorageException exception)
      {
        Storage->unlock();
        qWarning("Could not perform reparenting in storage!");
        qWarning(exception.toString());
      }
      
      //update element in case it's currently open
      
      if (this->OpenElements.contains(element))
      {
        GCS::GElement* open_element = this->OpenElements[element];
        open_element->reparent(from,to,transform);
      }
    }
  }
  else if (message == "register")
  {
    qDebug(QString("Registering server: %1").arg(sender));
    try
    {
      Storage->lock();
      if (this->Storage->getServerExists(sender))
      {
        this->Storage->updateServerPresence(sender,"1");
        Storage->unlock();
        qDebug(QString("Server registration updated: %1").arg(sender));
      }
      else
      {
        this->Storage->addServer(sender,"1");
        Storage->unlock();
        qDebug(QString("New server registered: %1, syndicating element data.").arg(sender));
        this->syndicateAllElementDataToServer(sender);
      }
    }
    catch (GStorageException exception)
    {
      Storage->unlock();
      qWarning("Could not update server data in storage!");
    }
  }
  else if (message == "unregister")
  {
    qDebug(QString("UNREGISTER MESSAGE RECEIVED FROM %1").arg(sender));
    processUnregister(sender);
  }
  else if (message == "requestfreeids")
  {
    bool ok;
    unsigned long amount = data.text().toULong(&ok);
    if (ok)
    {
      //send free IDs:
      this->sendFreeIDs(sender,amount);
    }
    else
    {
      qWarning("Couldn't read the requested amount of free element IDs");
    }
  }
  else if (message == "freeids")
  {
    QDomNodeList list = data.elementsByTagName("range");
    for (unsigned int i=0; i<list.length(); i++)
    {
      QDomElement range = list.item(i).toElement();
      if (!range.isNull())
      {
        QDomElement start = range.elementsByTagName("from").item(0).toElement();
        if (!start.isNull())
        {
          QDomElement end = range.elementsByTagName("to").item(0).toElement();
          if (!end.isNull())
          {
            bool ok=false;;
            unsigned long from = start.text().toULong(&ok);
            if (ok)
            {
              unsigned long to = end.text().toULong(&ok);
              if (ok)
              {
                GCS::GElementID::addFreeIDRange(from,to);
              }
            }
          }
        }
      }
    }
  }
  else if (message == "body") // <-- THAT's an USER MESSAGE
  {
    if (data.isElement())
    {
      QString text = data.toElement().text();
      
      qDebug(" ");
      qDebug(QString("RECEIVED USER MESSAGE FROM %1:").arg(sender));
      qDebug(" ");
      qDebug(text);
      qDebug(" ");
      qDebug("USER MESSAGE END");
      qDebug(" ");
      
      emit this->userMessageReceived(text,sender,data.hasAttribute("g"));
    }
    else
    {
      qWarning("Received an user message, but XML tag is not an element!");
    }
  }
  else
  {
    qWarning(("received unknown XML message: ") + data.tagName());
  }
}

bool GXmlDataController::checkConsistency(bool& ok)
{
  //@todo check primary, secondary and open elements as well as storage(against primary and secondary)
  qWarning("Consistency check not yet implemented, returning true");
  ok=true;
  return true;
}

void GXmlDataController::sendUserMessage(QString message, QString destination)
{
  //@todo: this doesn't work???
//   XMPP::Jid destjid;
//   destjid.set(destination);
//   if (!destjid.isValid())
//   {
//     qWarning(QString("Destination %1 not a valid JID, not sending message!").arg(destination));
//     return;
//   }
  QDomDocument d;
  QDomElement e = d.createElement("message");
  d.appendChild(e);
  e.setAttribute("type","chat");
  e.setAttribute("to",destination);
  QDomElement body = d.createElement("body");
  e.appendChild(body);
  //add an identification to differentiate between G internal and external messages
  body.setAttribute("g","1");
  body.appendChild(d.createTextNode(message));
  this->Network->send(d.toString());
}

}


syntax highlighted by Code2HTML, v. 0.9.1