<?php
/**
 * MiniMVC
 *
 * Convention-based micro-framework for PHP
 *
 * @package		miniMVC
 * @author 		Timothy J. Warren
 * @copyright	Copyright (c) 2011 - 2012
 * @link 		https://github.com/timw4mail/miniMVC
 * @license 	http://philsturgeon.co.uk/code/dbad-license 
 */
 
// --------------------------------------------------------------------------
// ! Traits
// --------------------------------------------------------------------------

/** 
 * Parent trait of base class, contains much of the magic
 *
 * @package miniMVC
 * @subpackage System
 */
trait JSObject {

	/**
 	 * Constructor for creating the objects
 	 *
 	 * @param array $members
 	 * @return void
 	 */
 	public function __construct($members = array())
 	{
 		// Add the passed parameters to the object
 		foreach($members as $name => $value)
 		{
 			$this->$name = $value;
 		}
 	}
 	
 	// --------------------------------------------------------------------------
 	
 	/** 
	 * PHP magic method to facilitate dynamic methods
	 *
	 * @param string $name
	 * @param array $params
	 */
	public function __call($name, $params = array())
	{
		if(is_callable($this->$name))
		{	
			//Call the dynamic function
			return call_user_func_array($this->$name, $params);
		}
	}
	
	// --------------------------------------------------------------------------

	/**
	 * PHP magic method to facilitate dynamically set static methods
	 * 
	 * @param string $name
	 * @param array $args
	 */
	public static function __callStatic($name, $args)
	{
		if(is_callable(self::$name))
		{
			return call_user_func_array(self::$name, $args);
		}
	}
	
	// --------------------------------------------------------------------------
	
	/**
	 * Prints out the contents of the object when used as a string
	 * 
	 * @return string
	 */
	public function __toString()
	{
		if(ENVIRONMENT == 'DEVELOPMENT')
		{
			$args = func_get_args();
			$method = ( ! empty($args)) ? $args[0] : "print_r";
			$data = (isset($args[1])) ? $args[1] : array();
		
			if(empty($data))
			{
				$data =& $this;
			}
			
			$output = '<pre>';
			
			if($method == "var_dump")
			{
				ob_start();
				var_dump($data);
				$output .= ob_get_contents();
				ob_end_clean();
			}
			else if($method == "var_export")
			{
				ob_start();
				var_export($data);
				$output .= ob_get_contents();
				ob_end_clean();
			}	
			else
			{
				$output .= print_r($data, TRUE);
			}
		
			return $output . '</pre>';
		}
		else
		{
			return '';
		}
	}
	
	// --------------------------------------------------------------------------
	
	/**
	 * PHP magic method that is called when an object is treated as a function
	 *
	 * @param array $args
	 */
	public static function __invoke($args = array())
	{
		$class = __CLASS__;
		return new $class($args);
	}
}

/**
 * Singleton pattern
 *
 * @package miniMVC
 * @subpackage System
 */
trait Singleton {

	/**
	 * Singleton object
	 *
	 * @var self
	 */
	private static $instance;
	 
	/**
	 * PHP magic method that is called when an object is treated as a function
	 *
	 * @param array $params
	 * @return self
	 */
	public static function __invoke($params = array())
	{
		return self::get_instance($params);
	}
	
	// --------------------------------------------------------------------------
	
	/**
	 * Singleton getter function
	 *
	 * @return self
	 */
	public static function &get_instance()
	{
		if ( ! isset(self::$instance)) 
		{
			$class = __CLASS__;
		
			self::$instance = new $class;
		}
		
		return self::$instance;
	}
	
	// --------------------------------------------------------------------------
	
	/**
	 * Magic function called when cloning an object
	 */
	public function __clone()
	{
		trigger_error('Clone is not allowed.', E_USER_ERROR);
	}
}

// --------------------------------------------------------------------------
// ! Base Classes
// --------------------------------------------------------------------------

/**
 * Class for standalone JSObject objects
 *
 * @package miniMVC
 * @subpackage System
 */
class MM extends ArrayObject {
	
	use JSObject;
	
	/**
	 * Create the ArrayObject/JSObject hybrid object
	 *
	 * @param array
	 */
	public function __construct($members = array())
	{
		parent::__construct($members);
	
		// Add the passed parameters to the object
 		foreach($members as $name => $value)
 		{
 			$this->$name = $value;
 		}
	}
	
	// --------------------------------------------------------------------------
	
	/**
	 * Allow calling of array methods on the object and 
	 * dynamic methods
	 *
	 * @param string $name
	 * @param array $params
	 * @return mixed
	 */
	public function __call($name, $params = array())
	{
		// Allow array operations on the object
		if (substr($name, 0, 6) === 'array_' && is_callable($name))
		{
			$args = array_merge($this->getArrayCopy(), $args);
			return call_user_func_array($name, $args);
		}
		
		// Allow dynamic method calls
		if(is_callable($this->$name))
		{	
			//Call the dynamic function
			return call_user_func_array($this->$name, $params);
		}
	}
}

// --------------------------------------------------------------------------

/**
 * Base class for the framework
 *
 * @package miniMVC
 * @subpackage System
 */
class miniMVC extends MM {

	use Singleton;
	
	/**
	 * Reference to output class
	 *
	 * @var MM_Output
	 */
	public $output;

	/**
	 * Constructor - Any classes loaded here become subclasses of miniMVC
	 *
	 * @param array $members
	 */
	public function __construct($members = array())
	{
		// Allow the class to be used like an array
		parent::__construct($members);
		
		$this->output = new MM_Output();
	}
	
	// --------------------------------------------------------------------------
	
	/** 
	 * PHP magic method to facilitate dynamic methods
	 *
	 * @param string $name
	 * @param array $args
	 */
	public function __call($name, $args = array())
	{
		// Allow array operations on the object
		if (substr($name, 0, 6) === 'array_' && is_callable($name))
		{
			$args = array_merge($this->getArrayCopy(), $args);
			return call_user_func_array($name, $args);
		}
	
		// Call dynamic methods
		if (is_callable(self::$instance->$name))
		{	
			// Add $this object to args
			array_push($args, $this);
		
			// Call the dynamic function
			return call_user_func_array(self::$instance->$name, $args);
		}
		
		// Indirectly call output methods
		if (is_callable(self::$instance->output->$name))
		{
			return call_user_func_array(self::$instance->output->$name, $args);
		}
	}
	
	// --------------------------------------------------------------------------
	
	/**
	 * Method to load classes into the singleton
	 *
	 * @param string $name
	 * @param string $type
	 * @return void
	 */
	public function load_class($name, $type='class')
	{
		switch($type)
		{
			default:
				$path = MM_APP_PATH . "classes/{$name}.php";
			break;
			
			case "sys":
				$path = MM_SYS_PATH . "{$name}.php";
			break;
		}
		
		// In a subdirectory? No problem
		if(strpos("/", $name) !== FALSE)
		{
			$n = explode("/", $name);
			$name = $n[count($n) -1];
		}
		
		$class = "{$name}";
		
		if(class_exists($class, FALSE))
		{
			if ( ! isset($this->$name))
			{
				$this->$name = new $class;
				return;
			}
		}
		
		if(is_file($path))
		{
			require_once($path);
			
			if(class_exists($class, FALSE))
			{
				if ( ! isset($this->$name))
				{
					$this->$name = new $class;
					return;
				}
			}
		}
	}
	
	// --------------------------------------------------------------------------
	
	/**
	 * Convenience function to remove an object from the singleton
	 *
	 * @param string $name
	 */
	public function unload($name)
	{
		if(isset($this->$name))
		{
			unset($this->$name);
		}
	}
	
	// --------------------------------------------------------------------------
	
	/**
	 * Convenience function to load config files
	 *
	 * @param string $name
	 */
	public function load_config($name)
	{
		$path = MM_APP_PATH . "config/{$name}.php";
	
		if(is_file($path))
		{
			require_once($path);
		}
	}
	
}

// End of miniMVC.php