/**
  * Trigger an event
  *
  * @param string $eventName
  * @param mixed $origin
  * @param mixed $context
  *
  * @param EventInterface $event
  * @return EventInterface
  * @throws Exception
  * @throws \ObjectivePHP\Primitives\Exception
  * @throws \ObjectivePHP\ServicesFactory\Exception\ServiceNotFoundException
  */
 public function trigger($eventName, $origin = null, $context = [], EventInterface $event = null)
 {
     if (is_null($origin)) {
         $origin = $this;
     }
     // cast context to Collection
     $context = Collection::cast($context);
     // cannot get event from factory
     // because of injection process
     // which triggers an event causing
     // an infinite loop...
     if (is_null($event)) {
         $event = new Event();
     }
     $event->setName($eventName)->setOrigin($origin)->setContext($context);
     // add reference to previous event if any
     if ($previous = \current($this->currentEventsQueue)) {
         $event->setPrevious($previous);
     }
     $this->currentEventsQueue[(string) $event->getName()] = $event;
     $listeners = $this->getListeners($eventName);
     // TODO sort listeners according to their priority or other mechanism
     $i = 0;
     foreach ($listeners as $listenersGroup) {
         $callbacks = [];
         // handle callbacks aggregate
         foreach ($listenersGroup as $alias => $callback) {
             if ($callback instanceof CallbacksAggregate) {
                 $callback->getCallbacks()->each(function ($callback, $callbackAlias) use(&$callbacks, $alias) {
                     $callbackAlias = implode('.', [$alias, $callbackAlias]);
                     $callbacks[$callbackAlias] = $callback;
                 });
             } else {
                 $callbacks[$alias] = $callback;
             }
         }
         foreach ($callbacks as $alias => $callback) {
             // handle service references
             if ($callback instanceof ServiceReference) {
                 $callback = $this->getServicesFactory()->get($callback->getId());
             }
             // if listener is a class name, instantiate it
             if (!is_callable($callback) && class_exists($callback)) {
                 $className = $callback;
                 $callback = new $className();
                 if (!is_callable($callback)) {
                     throw new Exception(sprintf('Class "%s" does not implement __invoke(), thus cannot be used as a callback', $className), Exception::EVENT_INVALID_CALLBACK);
                 }
             }
             $result = $callback($event);
             // gather exceptions
             if ($result instanceof \Exception) {
                 $event->setException($i, $result);
             }
             $event->setResult(is_string($alias) ? $alias : $i, $result);
             if ($event->isHalted()) {
                 // yes, this is a goto...
                 // I know that it was not absolutely needed, but it's a quite long story
                 // so please keep it as is.
                 // ping @EmmanuelJacoby :)
                 //
                 // @gdelamarre
                 goto shunt;
             }
             if (!is_string($alias)) {
                 $i++;
             }
         }
     }
     // target reached from within the triggering loop
     // if the event was halted by a listener
     shunt:
     // event has been triggered, it is now removed from events queue
     array_pop($this->currentEventsQueue);
     end($this->currentEventsQueue);
     return $event;
 }