<?php

/**
 * THIS SOFTWARE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED
 * WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
 *
 * @package fusionLib
 * @copyright Copyright (c) 2008 - 2013 fusionLib. All rights reserved.
 * @link http://fusionlib.com
 */

/**
 * flEventManager Class
 *
 * Class to manage and dispatch events.
 *
 * @package fusionLib
 */
class flEventManager {

	/**
	 * The database object for persistent events.
	 *
	 * @var flDb
	 */
	protected $db;

	/**
	 * Array of arrays of listeners.
	 *
	 * @var array
	 */
	protected $listeners = array();

	/**
	 * Array of flags to track if persistent events loaded.
	 *
	 * @var array
	 */
	protected $loadedEvt = array();

	/**
	 * Array of objects created from loaded event listeners.
	 *
	 * @var array
	 */
	protected $classCache = array();

	/**
	 * Construct the object.
	 *
	 * @param flDb $db The optional database object or null if not using persistent events.
	 */
	function __construct($db = null) {
		$this->db = $db;
	}

	/**
	 * Add a listener for an event.
	 *
	 * @param string $event The name of the event to listen for.
	 * @param string|object $object The object to process the event, or a class name to create a persistent event.
	 * @param string $function The name of the function to process the event.
	 * @return flEventManager This object.
	 * @throws flEventException
	 */
	function addListener($event, $object, $function) {
		if(is_string($object)) {
			if(!$this->db)
				throw new flEventException('Unable to add persistent event as no database set for flEventManager object.');
			$this->db->replace('fl_events', array('event' => $event, 'evtclass' => $object, 'evtfunc' => $function));
		}
		else
			$this->listeners[$event][] = array($object, $function);
		return $this;
	}

	/**
	 * Remove a listener from an event.
	 *
	 * @param string $event The name of the event to remove listen from.
	 * @param string|object $object The object to process the event, or a class name to remove a persistent event.
	 * @param string $function The name of the function to process the event.
	 * @return flEventManager This object.
	 * @throws flEventException
	 */
	function removeListener($event, $object, $function) {
		if(is_string($object)) {
			if(!$this->db)
				throw new flEventException('Unable to remove persistent event as no database set for flEventManager object.');
			$this->db->delete('fl_events', 'event=? && evtclass=? && evtfunc=?', $event, $object, $function);
		}
		else {
			$listeners = array();
			foreach($this->listeners[$event] as $listener) {
				if($listener[0] !== $object || $listener[1] !== $function)
					$listeners[] = $listener;
			}
			$this->listeners[$event] = $listeners;
		}
		return $this;
	}

	/**
	 * Notify all the listeners of an event.
	 *
	 * @param flEvent $event The flEvent object.
	 * @return flEventManager This object.
	 */
	function notify($event) {
		$eventName = $event->getName();

		// If not loaded persistent events
		if($this->db !== null && !isset($this->loadedEvt[$eventName])) {
			$this->loadedEvt[$eventName] = true;
			if($result = $this->db->query('SELECT evtclass,evtfunc FROM fl_events WHERE event=?', $eventName)) {
				while($row = $result->fetch()) {
					if(!isset($this->classCache[$row['evtclass']]))
						$this->classCache[$row['evtclass']] = new $row['evtclass']();
					$this->listeners[$eventName][] = array($this->classCache[$row['evtclass']], $row['evtfunc']);
				}
			}
		}

		// If listeners available
		if(isset($this->listeners[$eventName])) {
			// Notify all listeners
			foreach($this->listeners[$eventName] as $listener) {
				call_user_func($listener, $event);
				if($event->isPropagationStopped())
					break;
			}
		}

		return $this;
	}
}
