/***************************************************************************
 *   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.                                       *
 ***************************************************************************/


#ifndef GELEMENTH
#define GELEMENTH

#include <qdatastream.h>
#include <qdatetime.h>
#include <qobject.h>
#include <qptrlist.h>

namespace GCS
{

class GObject;
class GEnergy;
class GForm;
class GMatrix44;
class GElementID;
class GElementInfluence;
class GAgent;

/**
  \class GElement GElement.h
  \brief Everything in the world is an element,
  \brief they consist of energy, agents and form
  @author Raphael Langerhorst
  
  GElement is the basic building block with which
  the whole universe is built. It is possible to order
  elements in a hierarchical structure - like
  universe - galaxies - solar systems - planets - continents - ...
  for this structuring child elements are used.
  
  GElement is the interface class for an element that
  consists of multiple individual classes. Most notably
  GObject is used to store data.
  

  Every element basically consists of three things:
  matter/form,
  energy,
  behaviour

  Matter and form are represented by the GForm, energy is
  represented by GEnergy. Behaviour is represented by GAgent.

  From elements with these three parts everything can be made.

  Everything influences each other, so elements are "connected"
  to each other; this connection can be used for influencing
  by the behaviour part of the element.
  
*/

class GElement : public QObject
{
  //defines GElement to be a Q_OBJECT;
  //this means that the signal and slot mechanism provided by Qt
  //can be used;
  Q_OBJECT

  private:

    /**
     * The elements object, contains energy, form, ...
     */
    GObject* Object;

    /**
     * Contains all behaviour for this element,
     * that is, agents.
     */
    QValueList<GAgent*> Agents;
    
    /**
     * Used to measure the elapsed time between element parking
     * and resuming execution.
     * When the element is parked ParkTime is set to the current
     * time. When execution of the element resumes, this
     * attribute can be used to tell how much time has elapsed.
     * @see parkElement(), executeElement()
     */
    QDateTime ParkTime;

  public:

    /**
     * Constructor: GElement takes over ownership of the object;
     * that means that the object is destroyed when the element
     * is destroyed;
     * 
     * @param park_time sets the time when the element was last parked,
     *       be careful with time difference between different computer systems!!
     */
    GElement(GObject* object, const QDateTime& park_time = QDateTime::currentDateTime());

    /**
     * Destructor also deletes the Object and all Agents.
     * If you need to save a specific agent, you need to use
     * removeAgent() before deleting the element.
     */
    virtual ~GElement();

    /**
     * returns a read only reference to the element's GElementID;
     */
    const GElementID& getElementID() const;
    
    /**
     * @return the date and time when the element was last parked
     * @note Be careful here! The element should always
     *             be parked BEFORE calling getParkTime()
     */
    QDateTime getParkTime() const;

    /**
     * Returns true when element is parked. In such a state
     * the element could be safely serialized for example.
     *
     * @note when the element is parked, this means that
     * no agent is executing
     */
    bool isParked();
    
    /**
     * @return a list with all agents (const reference)
     */
    QValueList<const GAgent*> getAgents() const;
    
    /**
     * @return the element's object (const pointer)
     */
    const GObject* getObject() const;

  public slots:

    /**
     * Takes any kind of element influence.
     * Currently it only emits a forwardInfluence signal
     * which is connected to all the agent's receiveInfluence slot.
     *
     * @see GElement::forwardInfluenceInternal()
     */
    void receiveInfluence(const GCS::GElementInfluence&);

    /**
     * Called by the GWE after reparenting this element from old_parent
     * to new_parent.
     * 
     * No element data is directly changed by the GWE, but the GWE expects that the
     * element changes all data appropriately and applies given transformation
     * to the form.
     *
     * The GWE must take care of updating the child data for the old (remove this)
     * and new parent (add this)!
     * 
     * Internally all agents are notified about reparenting AFTER the
     * transfromation is applied to the form.
     * 
     * After changing the parent, applying the transformation and
     * notifying all agents a parentChanged signal is emitted.
     * 
     * @see notifyReparentingInternal
     */
    void reparent(const GCS::GElementID& old_parent, const GCS::GElementID& new_parent, const GCS::GMatrix44& transformation);

    /**
     * Initializes given agent for this element and
     * adds it to the private list of agents;
     *
     * Initialization includes connecting signals and slots and
     * setting the agent's Object and Agents member to enable
     * the agent access to these parts of the element.
     */
    void addAgent(GCS::GAgent* agent);
    
    /**
     * Removes given agent from element but does not delete it (by default).
     * all signals and slots between this element and the agent are
     * disconnected. If del is set to TRUE then the agent is also deleted
     * from memory.
     */
    void removeAgent(GCS::GAgent* agent, bool del=FALSE);

  signals:  // signals used inside the element
  
    /**
     * Stops all element-internal processing (execution
     * of agents). This is useful for removing the
     * element from memory and keeping it somewhere in
     * a database. If the element needs to be reactivated
     * then this can be performed with executeElement().
     * By doing so the element measures the time between
     * where it was 
     *
     *
     * @note It is not guranteed that this signal
     *  returns immediately because it depends on the
     *  agent's implementation of GAgent::beginPark().
     *
     * @see execute(), executeElement(), parkElement(), ParkTime
     */
    void park();

    /**
     * Resumes all element-internal processing (execution
     * of agents).
     *
     * @param seconds_elapsed tells how much time has passed since parking.
     * @see park(), parkElement(), executeElement()
     */
    void execute(double seconds_elapsed);

    /**
     * takes any kind of element influence and passes it on to
     * all agents for processing;
     *
     * please note that this is a blocking call until all
     * agents have received the influence; some agents
     * might even process the influence fully before returning
     */
    void forwardInfluenceInternal(const GCS::GElementInfluence&);
    
    /**
     * Forwards the reparent signal from the GWE to all agents.
     * This happens after applying the transformation to the
     * form and before emitting a parentChanged signal.
     * 
     * @see reparent()
     */
    void notifyReparentingInternal(const GCS::GElementID& old_parent, const GCS::GElementID& new_parent, const GCS::GMatrix44& transformation);
    
    
    
  public: //these two methods are necessary to raise internal signals;
    
    /**
     * Emits a park signal.
     * @see park()
     */
    void parkElement();
    
    /**
     * Emits an execute signal.
     *
     * @param seconds_delta_t_offset can be used to compensate time
     *        differences between two hosts, e.g. when an element was parked,
     *        transfered to another host and executed again. This should
     *        be target host time minus source host time.
     * @see execute()
     */
    void executeElement(double seconds_delta_t_offset = 0);
    
  signals:  // signals related to influence
    
    /**
     * Connected from all agents' sendInfluence signals.
     */
    void sendInfluence(const GCS::GElementID& destination,
                       const GCS::GElementInfluence& influence);

    /**
     * Connected from all agents' radiateInfluence signals.
     */
    void radiateInfluence(const GCS::GElementInfluence& influence);

  signals:  // signals used for border communication (GWE)
    
    //BEGIN signals that are important for consistent distributed data management
  
    /**
     * Emitted when the element moves out of its current parent region
     * into another parent region.
     * @note This means a restructuring of the element hierarchy! The
     * GWE might be interested in this.
     */
    void parentChanged(GCS::GElement* element, const GCS::GElementID& oldParent, const GCS::GElementID& newParent, const GCS::GMatrix44& transformation);
    
    /**
     * Emitted when the element receives an influence.
     * This signal might be interesting for the data controller
     * if the actual element is not primarily managed on this
     * server in case of a networked infrastructure.
     */
    void influenceReceived(const GCS::GElementInfluence& influence);
    
    //END signals that are important for consistent distributed data management
    
    //signals from GAgent
    
    /**
     * Emitted when the element's energy has changed.
     */
    void energyChanged(const GCS::GEnergy& changedEnergy);

    /**
     * Emitted when the element's form has changed.
     */
    void formChanged(const GCS::GForm& changedForm);

    /**
     * Emitted when the agent changed it's state.
     * Usually not relevant since all data is kept in ElementData.
     */
    void agentChanged(const GCS::GAgent& changedAgent);
    
    /**
     * Emitted when a child element has been created
     * @note If an agent creates a child and doesn't emit this
     * signal it is NOT guranteed that your element gets
     * recognized by the GWE.
     */
    void childElementCreated(GCS::GElement* newElement);
    
    /**
     * Emitted when a child element was removed (deleted).
     * @note This signal should NOT be emitted when the child
     * just changed its parent. Through this signal a parent
     * can cause deletion of an existing child element!
     * @see parentChanged()
     */
    void childElementRemoved(const GCS::GElementID& childID);
};

}

#endif
