<?php

class Hook
{
	static $hooks=array();
	static $merged_hooks=array();
	static $current_hook=array();
	static $hook_id_count = 0;

	public static function add($tag, $function_to_add, $priority = 10, $accepted_args = 1)
	{
		$idx = self::_build_unique_id($tag, $function_to_add, $priority);
		self::$hooks[$tag][$priority][$idx] = array('function' => $function_to_add, 'accepted_args' => $accepted_args);
		unset(self::$merged_hooks[$tag]);
		return true;
	}

	public static function has($tag, $function_to_check = false)
	{
		$has = !empty(self::$hooks[$tag]);
		if (false === $function_to_check || false == $has)
			return $has;

		if (!$idx = self::_build_unique_id($tag, $function_to_check, false))
			return false;

		foreach ((array) array_keys(self::$hooks[$tag]) as $priority) {
			if (isset(self::$hooks[$tag][$priority][$idx]))
				return $priority;
		}

		return false;
	}

	public static function call($tag, $value = '')
	{
		$args = array();

		// Do 'all' actions first
		if (isset(self::$hooks['all'])) {
			self::$current_hook[] = $tag;
			$args = func_get_args();
			self::_call_all_hook($args);
		}

		if (!isset(self::$hooks[$tag])) {
			if (isset(self::$hooks['all']))
				array_pop(self::$current_hook);
			return $value;
		}

		if (!isset(self::$hooks['all']))
			self::$current_hook[] = $tag;

		// Sort
		if (!isset(self::$merged_hooks[$tag])) {
			ksort(self::$hooks[$tag]);
			self::$merged_hooks[$tag] = true;
		}

		reset(self::$hooks[$tag]);

		if (empty($args))
			$args = func_get_args();

		do {
			foreach ((array) current(self::$hooks[$tag]) as $the_) {
				if (!is_null($the_['function'])) {
					$args[1] = $value;
					$value = call_user_func_array($the_['function'], array_slice($args, 1, (int) $the_['accepted_args']));
				}
			}
		} while (next(self::$hooks[$tag]) !== false);

		array_pop(self::$current_hook);

		return $value;
	}

	public static function call_ref_array($tag, $args)
	{

		// Do 'all' actions first
		if (isset(self::$hooks['all'])) {
			self::$current_hook[] = $tag;
			$all_args = func_get_args();
			self::_call_all_hook($all_args);
		}

		if (!isset(self::$hooks[$tag])) {
			if (isset(self::$hooks['all']))
				array_pop(self::$current_hook);
			return $args[0];
		}

		if (!isset(self::$hooks['all']))
			self::$current_hook[] = $tag;

		// Sort
		if (!isset(self::$merged_hooks[$tag])) {
			ksort(self::$hooks[$tag]);
			self::$merged_hooks[$tag] = true;
		}

		reset(self::$hooks[$tag]);

		do {
			foreach((array) current(self::$hooks[$tag]) as $the_)
				if (!is_null($the_['function']))
					$args[0] = call_user_func_array($the_['function'], array_slice($args, 0, (int) $the_['accepted_args']));

		} while (next(self::$hooks[$tag]) !== false);

		array_pop(self::$current_hook);

		return $args[0];
	}

	public static function remove($tag, $function_to_remove, $priority = 10)
	{
		$function_to_remove = self::_build_unique_id($tag, $function_to_remove, $priority);

		$r = isset(self::$hooks[$tag][$priority][$function_to_remove]);

		if (true === $r) {
			unset(self::$hooks[$tag][$priority][$function_to_remove]);
			if (empty(self::$hooks[$tag][$priority]))
				unset(self::$hooks[$tag][$priority]);
			unset(self::$merged_hooks[$tag]);
		}

		return $r;
	}

	public static function remove_all($tag, $priority = false)
	{
		if (isset(self::$hooks[$tag])) {
			if(false !== $priority && isset(self::$hooks[$tag][$priority]))
				unset(self::$hooks[$tag][$priority]);
			else
				unset(self::$hooks[$tag]);
		}

		if (isset(self::$merged_hooks[$tag]))
			unset(self::$merged_hooks[$tag]);

		return true;
	}

	public static function current_hook()
	{
		return end(self::$current_hook);
	}

	private static function _call_all_hook($args)
	{
		reset(self::$hooks['all']);
		do {
			foreach((array) current(self::$hooks['all']) as $the_)
				if (!is_null($the_['function']))
					call_user_func_array($the_['function'], $args);

		} while (next(self::$hooks['all']) !== false);
	}

	private static function _build_unique_id($tag, $function, $priority)
	{
		if (is_string($function))
			return $function;

		if (is_object($function)) {
			// Closures are currently implemented as objects
			$function = array($function, '');
		} else {
			$function = (array) $function;
		}

		if (is_object($function[0])) {
			// Object Class Calling
			if (function_exists('spl_object_hash')) {
				return spl_object_hash($function[0]) . $function[1];
			} else {
				$obj_idx = get_class($function[0]).$function[1];
				if (false === $priority)
					return false;
				$obj_idx.=isset(self::$hooks[$tag][$priority])
				?count((array)self::$hooks[$tag][$priority])
				:self::$hook_id_count;
				
				++self::$hook_id_count;

				return $obj_idx;
			}
		} else if (is_string($function[0])) {
			// Static Calling
			return $function[0] . '::' . $function[1];
		}
	}
}
