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

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

/**
 * There should be a generic "Object" class, stdClass is dumb
 */
class Object extends stdClass {}

/**
 * Parent class of base class, contains much of the magic
 */
class JSObject extends Object{

	/**
 	 * Constructor for creating the objects
 	 */
 	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 $args
	 */
	function __call($name, $args)
	{
		if(is_callable($this->$name))
		{	
			//Add $this object to args
			array_push($args, $this);
		
			//Call the dynamic function
			return call_user_func_array($this->$name, $args);
		}
	}

	/**
	 * 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
	 */
	function __toString()
	{
		$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>';
	}
	
	/**
	 * PHP magic method to facilitate dynamic class loading
	 *
	 * @param string $name
	 */
	function __get($name)
	{ 	
		$path = SYS_PATH."{$name}.php";
		$class = "{$name}";
	
		if(class_exists($class, FALSE))
		{
			if( ! isset($this->$name))
			{
				$this->$name = new $class;
				return;
			}
		}
	
		load_file($name, 'sys');
			
		if(class_exists($class, FALSE))
		{
			$this->$name = new $class;
		}
	}
	
	/**
	 * PHP magic method that is called when an object is treated as a function
	 */
	public static function __invoke()
	{
		$class = __CLASS__;
		return new $class;
	}
}

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

/**
 * Base class for the framework
 *
 * @extends JSObject
 */
class miniMVC extends JSObject{

	private static $instance;
	private static $count;

	/**
	 * Constructor - Any classes loaded here become subclasses of miniMVC
	 */
	function __construct()
	{
		self::$instance =& $this;
	}
	
	/** 
	 * PHP magic method to facilitate dynamic methods
	 *
	 * @param string $name
	 * @param array $args
	 */
	function __call($name, $args)
	{
		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);
		}
	}
	
	/**
	 * Magic function called when cloning an object
	 */
	public function __clone()
	{
		trigger_error('Clone is not allowed.', E_USER_ERROR);
	}
	 
	/**
	 * PHP magic method that is called when an object is treated as a function
	 */
	public static function __invoke()
	{
		return self::get_instance();
	}
	
	/**
	 * Singleton getter function
	 *
	 * @return miniMVC object
	 */
	public static function &get_instance()
	{
		if( ! isset(self::$count))
		{
			self::$count = 0;
		}
	
		if ( ! isset(self::$instance)) 
		{
			self::$count++;
			self::$instance = new miniMVC;
		}
	
		$self =& self::$instance;
		
		return $self;
	}
	
	/**
	 * Method to load classes into the singleton
	 *
	 * @param string $name
	 */
	function load_class($name, $type='class')
	{
		switch($type)
		{
			default:
				$path = APP_PATH . "classes/{$name}.php";
			break;
			
			case "sys":
				$path = 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
	 */
	function unload($name)
	{
		if(isset($this->$name))
		{
			unset($this->$name);
		}
	}
	
	/**
	 * Convenience function to load config files
	 *
	 * @param string $name
	 */
	function load_config($name)
	{
		$path = APP_PATH . "config/{$name}.php";
	
		if(is_file($path))
		{
			require_once($path);
		}
	}
}

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

/**
 * Base Controller Class
 *
 * @extends miniMVC
 */
class MM_Controller extends miniMVC {

	public $output, $page;

	function __construct()
	{
		parent::__construct();
		
		$this->output = new Output();
		$this->page = new Page();
	}
	
	/**
	 * Function for loading a model into the current controller
	 *
	 * @param string $file
	 */	
	function load_model($file, $args=array())
	{
		$path = "";
	
		// The module is the lower of the class name 
		// need to figure out a way to allow multiple controllers
		// in one module
		$module = strtolower(get_class($this));
		
		$not_modules = array('miniMVC', 'page', 'db', 'output');
		
		// If it's a module, look in the module view folder
		if( ! in_array($module, $not_modules))
		{
			$path = MOD_PATH . "{$module}/models/{$file}.php";
		}
		
		if(is_file($path))
		{
			require_once($path);
		}		
		
		if( ! empty($args))
		{
		
			$this->$file = new $file($args);
		}
		else
		{
			$this->$file = new $file;
		}
	}
	
	/**
	 * Function for loading a view
	 *
	 * @param string $file
	 * @param array $data
	 * @return mixed
	 */
	function load_view($file, $data, $return=FALSE)
	{
		$path = "";
	
		// The module is the lower of the class name 
		// need to figure out a way to allow multiple controllers
		// in one module
		$module = strtolower(get_class($this));
		
		$not_modules = array('miniMVC', 'page', 'db', 'output');
		
		// If it's a module, look in the module view folder
		if( ! in_array($module, $not_modules))
		{
			$path = MOD_PATH . "{$module}/views/{$file}.php";
		}
		
		// If it's not a module, or doesn't exist in the module view folder
		// look in the app view folder
		if( ! is_file($path))
		{	
			$path = APP_PATH . "views/{$file}.php";
		}
		
		// Contain the content for buffering
		ob_start();
		
		// Extract the data array
		extract($data);
		
		// Include the file
		include($path);
		
		$buffer = ob_get_contents();
		ob_end_clean();
		
		if($return == TRUE)
		{
			return $buffer;
		}
		else
		{
			$this->output->append_output($buffer);
		}
		 	
	}
}

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

/**
 * Base Model Class
 *
 * @extends miniMVC
 */
class MM_Model extends miniMVC {

	function __construct()
	{
		parent::__construct();
	}

	/** 
	 * Adds the database class to the current model class
	 */
	function load_db($name="default")
	{
		$this->db = db::get_instance($name);
	}

}
// End of miniMVC.php