#include "event.h"
 #include "ircevent.h"
 
-EventManager::EventManager(QObject *parent) : QObject(parent) {
-
-}
-
-EventManager::~EventManager() {
-  // pending events won't be delivered anymore, but we do need to delete them
-  qDeleteAll(_eventQueue);
+// ============================================================
+//  QueuedEvent
+// ============================================================
+class QueuedQuasselEvent : public QEvent {
+public:
+  QueuedQuasselEvent(Event *event)
+    : QEvent(QEvent::User), event(event) {}
+  Event *event;
+};
+
+// ============================================================
+//  EventManager
+// ============================================================
+EventManager::EventManager(QObject *parent)
+  : QObject(parent) {
 }
 
 QMetaEnum EventManager::eventEnum() const {
   }
 }
 
-// not threadsafe! if we should want that, we need to add a mutexed queue somewhere in this general area.
-void EventManager::sendEvent(Event *event) {
-  // qDebug() << "Sending" << event;
-  _eventQueue.append(event);
-  if(_eventQueue.count() == 1) // we're not currently processing another event
-    processEvents();
+void EventManager::postEvent(Event *event) {
+  if(sender() && sender()->thread() != this->thread()) {
+    QueuedQuasselEvent *queuedEvent = new QueuedQuasselEvent(event);
+    QCoreApplication::postEvent(this, queuedEvent);
+  } else {
+    if(_eventQueue.isEmpty())
+      // we're currently not processing events
+      processEvent(event);
+    else
+      _eventQueue.append(event);
+  }
 }
 
 void EventManager::customEvent(QEvent *event) {
   if(event->type() == QEvent::User) {
-    processEvents();
+    QueuedQuasselEvent *queuedEvent = static_cast<QueuedQuasselEvent *>(event);
+    processEvent(queuedEvent->event);
     event->accept();
   }
 }
 
-void EventManager::processEvents() {
-  // we only process one event at a time for now, and let Qt's own event processing come in between
-  if(_eventQueue.isEmpty())
-    return;
-  dispatchEvent(_eventQueue.first());
-  _eventQueue.removeFirst();
-  if(_eventQueue.count())
-    QCoreApplication::postEvent(this, new QEvent(QEvent::User));
-  else
-    emit eventQueueEmptied();
+void EventManager::processEvent(Event *event) {
+  Q_ASSERT(_eventQueue.isEmpty());
+  dispatchEvent(event);
+  // dispatching the event might cause new events to be generated. we process those afterwards.
+  while(!_eventQueue.isEmpty()) {
+    dispatchEvent(_eventQueue.first());
+    _eventQueue.removeFirst();
+  }
 }
 
 void EventManager::dispatchEvent(Event *event) {
 
   };
 
   EventManager(QObject *parent = 0);
-  virtual ~EventManager();
 
   EventType eventTypeByName(const QString &name) const;
   EventType eventGroupByName(const QString &name) const;
   //! Send an event to the registered handlers
   /**
     The EventManager takes ownership of the event and will delete it once it's processed.
-    NOTE: This method is not threadsafe!
     @param event The event to be dispatched
    */
-  void sendEvent(Event *event);
-
-signals:
-  void eventQueueEmptied();
+  void postEvent(Event *event);
 
 protected:
   virtual void customEvent(QEvent *event);
 
   int findEventType(const QString &methodSignature, const QString &methodPrefix) const;
 
-  void processEvents();
+  void processEvent(Event *event);
   void dispatchEvent(Event *event);
 
   //! @return the EventType enum
   HandlerHash _registeredHandlers;
   HandlerHash _registeredFilters;
   mutable QMetaEnum _enum;
-
   QList<Event *> _eventQueue;
 };
 
 
   connect(&socket, SIGNAL(encrypted()), this, SLOT(socketInitialized()));
   connect(&socket, SIGNAL(sslErrors(const QList<QSslError> &)), this, SLOT(sslErrors(const QList<QSslError> &)));
 #endif
+  connect(this, SIGNAL(newEvent(Event *)), coreSession()->eventManager(), SLOT(postEvent(Event *)));
 }
 
 CoreNetwork::~CoreNetwork() {
 #else
     event->setTimestamp(QDateTime::currentDateTime().toUTC());
 #endif
-    coreSession()->eventManager()->sendEvent(event);
+    emit newEvent(event);
   }
 }
 
 
 class CoreIdentity;
 class CoreUserInputHandler;
 class CoreIgnoreListManager;
+class Event;
 
 class CoreNetwork : public Network {
   SYNCABLE_OBJECT
   void quitRequested(NetworkId networkId);
   void sslErrors(const QVariant &errorData);
 
+  void newEvent(Event *event);
+
 protected:
   inline virtual IrcChannel *ircChannelFactory(const QString &channelname) { return new CoreIrcChannel(channelname, this); }
   inline virtual IrcUser *ircUserFactory(const QString &hostmask) { return new CoreIrcUser(hostmask, this); }
 
     _ctcpParser(new CtcpParser(this)),
     _ircParser(new IrcParser(this)),
     scriptEngine(new QScriptEngine(this)),
+    _processMessages(false),
     _ignoreListManager(this)
 {
   SignalProxy *p = signalProxy();
   loadSettings();
   initScriptEngine();
 
-  connect(eventManager(), SIGNAL(eventQueueEmptied()), SLOT(processMessages()));
   eventManager()->registerObject(ircParser(), EventManager::NormalPriority);
   eventManager()->registerObject(sessionEventProcessor(), EventManager::HighPriority); // needs to process events *before* the stringifier!
   eventManager()->registerObject(ctcpParser(), EventManager::NormalPriority);
   CoreNetwork *net = network(bufinfo.networkId());
   if(net) {
     net->userInput(bufinfo, msg);
-    // FIXME as soon as user input is event-based
-    // until then, user input doesn't trigger a message queue flush!
-    processMessages();
   } else {
     qWarning() << "Trying to send to unconnected network:" << msg;
   }
     return;
 
   _messageQueue << rawMsg;
+  if(!_processMessages) {
+    _processMessages = true;
+    QCoreApplication::postEvent(this, new ProcessMessagesEvent());
+  }
 }
 
 void CoreSession::recvStatusMsgFromServer(QString msg) {
   return Core::requestBuffers(user());
 }
 
-void CoreSession::processMessages() {
-  if(_messageQueue.isEmpty())
+void CoreSession::customEvent(QEvent *event) {
+  if(event->type() != QEvent::User)
     return;
 
+  processMessages();
+  event->accept();
+}
+
+void CoreSession::processMessages() {
   if(_messageQueue.count() == 1) {
     const RawMessage &rawMsg = _messageQueue.first();
     bool createBuffer = !(rawMsg.flags & Message::Redirected);
       emit displayMsg(messages[i]);
     }
   }
+  _processMessages = false;
   _messageQueue.clear();
 }
 
 
   void networkRemoved(NetworkId);
   void networkDisconnected(NetworkId);
 
+protected:
+  virtual void customEvent(QEvent *event);
+
 private slots:
   void removeClient(QIODevice *dev);
 
   void recvStatusMsgFromServer(QString msg);
   void recvMessageFromServer(NetworkId networkId, Message::Type, BufferInfo::Type, const QString &target, const QString &text, const QString &sender = "", Message::Flags flags = Message::None);
-  void processMessages();
 
   void destroyNetwork(NetworkId);
 
   void saveSessionState() const;
 
 private:
+  void processMessages();
+
   void loadSettings();
   void initScriptEngine();
 
   QScriptEngine *scriptEngine;
 
   QList<RawMessage> _messageQueue;
+  bool _processMessages;
   CoreIgnoreListManager _ignoreListManager;
 };
 
 
   _coreSession(session)
 {
   connect(coreSession(), SIGNAL(networkDisconnected(NetworkId)), this, SLOT(destroyNetsplits(NetworkId)));
+  connect(this, SIGNAL(newEvent(Event *)), coreSession()->eventManager(), SLOT(postEvent(Event *)));
 }
 
 bool CoreSessionEventProcessor::checkParamCount(IrcEvent *e, int minParams) {
       MessageEvent *msgEvent = new MessageEvent(Message::Error, e->network(),
                                                 tr("No free and valid nicks in nicklist found. use: /nick <othernick> to continue"),
                                                 QString(), QString(), Message::None, e->timestamp());
-      coreSession()->eventManager()->sendEvent(msgEvent);
+      emit newEvent(msgEvent);
       return;
     } else {
       nextNick = errnick + "_";
 
   ircChannel->joinIrcUsers(ircUsers, newModes);
   NetworkSplitEvent *event = new NetworkSplitEvent(EventManager::NetworkSplitJoin, net, channel, newUsers, quitMessage);
-  coreSession()->eventManager()->sendEvent(event);
+  emit newEvent(event);
 }
 
 void CoreSessionEventProcessor::handleNetsplitQuit(Network *net, const QString &channel, const QStringList &users, const QString& quitMessage) {
   NetworkSplitEvent *event = new NetworkSplitEvent(EventManager::NetworkSplitQuit, net, channel, users, quitMessage);
-  coreSession()->eventManager()->sendEvent(event);
+  emit newEvent(event);
   foreach(QString user, users) {
     IrcUser *iu = net->ircUser(nickFromMask(user));
     if(iu)
   ircChannel->joinIrcUsers(ircUsers, newModes);
   foreach(NetworkEvent *event, events) {
     event->setFlag(EventManager::Fake); // ignore this in here!
-    coreSession()->eventManager()->sendEvent(event);
+    emit newEvent(event);
   }
 }
 
 
   Q_INVOKABLE void handleCtcpVersion(CtcpEvent *event);
   Q_INVOKABLE void defaultHandler(const QString &ctcpCmd, CtcpEvent *event);
 
+signals:
+  void newEvent(Event *event);
+
 protected:
   bool checkParamCount(IrcEvent *event, int minParams);
   inline CoreNetwork *coreNetwork(NetworkEvent *e) const { return qobject_cast<CoreNetwork *>(e->network()); }
 
   QByteArray XQUOTE = QByteArray("\134");
   _ctcpXDelimDequoteHash[XQUOTE + XQUOTE] = XQUOTE;
   _ctcpXDelimDequoteHash[XQUOTE + QByteArray("a")] = XDELIM;
+
+  connect(this, SIGNAL(newEvent(Event *)), _coreSession->eventManager(), SLOT(postEvent(Event *)));
 }
 
 void CtcpParser::displayMsg(NetworkEvent *event, Message::Type msgType, const QString &msg, const QString &sender,
   MessageEvent *msgEvent = new MessageEvent(msgType, event->network(), msg, sender, target, msgFlags);
   msgEvent->setTimestamp(event->timestamp());
 
-  coreSession()->eventManager()->sendEvent(msgEvent);
+  emit newEvent(msgEvent);
 }
 
 QByteArray CtcpParser::lowLevelQuote(const QByteArray &message) {
                                           ctcptype, "INVALID", QString(), e->timestamp(), uuid);
     ctcpEvents << flushEvent;
     foreach(CtcpEvent *event, ctcpEvents) {
-      coreSession()->eventManager()->sendEvent(event);
+      emit newEvent(event);
     }
   }
 
 
 
   Q_INVOKABLE void sendCtcpEvent(CtcpEvent *event);
 
+signals:
+  void newEvent(Event *event);
+
 protected:
   inline CoreNetwork *coreNetwork(NetworkEvent *e) const { return qobject_cast<CoreNetwork *>(e->network()); }
 
 
   _coreSession(parent),
   _whois(false)
 {
-
+  connect(this, SIGNAL(newMessageEvent(Event *)), coreSession()->eventManager(), SLOT(postEvent(Event *)));
 }
 
 void EventStringifier::displayMsg(NetworkEvent *event, Message::Type msgType, const QString &msg, const QString &sender,
     return;
 
   MessageEvent *msgEvent = createMessageEvent(event, msgType, msg, sender, target, msgFlags);
-  sendMessageEvent(msgEvent);
+  //sendMessageEvent(msgEvent);
+  emit newMessageEvent(msgEvent);
 }
 
 MessageEvent *EventStringifier::createMessageEvent(NetworkEvent *event, Message::Type msgType, const QString &msg, const QString &sender,
   return msgEvent;
 }
 
-void EventStringifier::sendMessageEvent(MessageEvent *event) {
-  coreSession()->eventManager()->sendEvent(event);
-}
-
 bool EventStringifier::checkParamCount(IrcEvent *e, int minParams) {
   if(e->params().count() < minParams) {
     if(e->type() == EventManager::IrcEventNumeric) {
 
                   const QString &target = QString(),
                   Message::Flags msgFlags = Message::None);
 
+signals:
+  void newMessageEvent(Event *event);
+
 private:
   bool checkParamCount(IrcEvent *event, int minParams);
-  void sendMessageEvent(MessageEvent *event);
 
   CoreSession *_coreSession;
   bool _whois;
 
   QObject(session),
   _coreSession(session)
 {
-
+  connect(this, SIGNAL(newEvent(Event *)), coreSession()->eventManager(), SLOT(postEvent(Event *)));
 }
 
 bool IrcParser::checkParamCount(const QString &cmd, const QList<QByteArray> ¶ms, int minParams) {
   QList<Event *> events;
   EventManager::EventType type = EventManager::Invalid;
 
-  // numeric replies have the target as first param (RFC 2812 - 2.4). this is usually our own nick. Remove this!
   uint num = cmd.toUInt();
   if(num > 0) {
+    // numeric reply
     if(params.count() == 0) {
       qWarning() << "Message received from server violates RFC and is ignored!" << msg;
       return;
     }
+    // numeric replies have the target as first param (RFC 2812 - 2.4). this is usually our own nick. Remove this!
     target = net->serverDecode(params.takeFirst());
     type = EventManager::IrcEventNumeric;
   } else {
+    // any other irc command
     QString typeName = QLatin1String("IrcEvent") + cmd.at(0).toUpper() + cmd.mid(1).toLower();
     type = eventManager()->eventTypeByName(typeName);
     if(type == EventManager::Invalid) {
   }
 
   foreach(Event *event, events) {
-    coreSession()->eventManager()->sendEvent(event);
+    emit newEvent(event);
   }
 }
 
   inline CoreSession *coreSession() const { return _coreSession; }
   inline EventManager *eventManager() const { return coreSession()->eventManager(); }
 
+signals:
+  void newEvent(Event *);
+
 protected:
   Q_INVOKABLE void processNetworkIncoming(NetworkDataEvent *e);