<?php

	/**
	 * NRE
	 *
	 * @author	coderkun <olli@coderkun.de>
	 * @copyright	2013 coderkun (http://www.coderkun.de)
	 * @license	http://www.gnu.org/licenses/gpl.html
	 * @link	http://www.coderkun.de/projects/nre
	 */
	
	namespace nre\core;
	
	
	/**
	 * Abstract class for implementing a Controller.
	 * 
	 * @author	coderkun <olli@coderkun.de>
	 */
	abstract class Controller
	{
		/**
		 * Corresponding Agent
		 * 
		 * @var Agent
		 */
		protected $agent;
		/**
		 * View of the Controller
		 * 
		 * @var View
		 */
		protected $view = null;
		/**
		 * Data to pass to the View
		 * 
		 * @var array
		 */
		protected $viewData = array();
		/**
		 * Current request
		 * 
		 * @var Request
		 */
		protected $request = null;
		/**
		 * Current response
		 * 
		 * @var Response
		 */
		protected $response = null;
		
		
		
		
		/**
		 * Load the class of a Controller.
		 * 
		 * @throws	ControllerNotFoundException
		 * @throws	ControllerNotValidException
		 * @param	string	$controllerName		Name of the Controller to load
		 */
		public static function load($controllerName)
		{
			// Determine full classname
			$className = self::getClassName($controllerName);
			
			try {
				// Load class
				ClassLoader::load($className);
				
				// Validate class
				ClassLoader::check($className, get_class());
			}
			catch(\nre\exceptions\ClassNotValidException $e) {
				throw new \nre\exceptions\ControllerNotValidException($e->getClassName());
			}
			catch(\nre\exceptions\ClassNotFoundException $e) {
				throw new \nre\exceptions\ControllerNotFoundException($e->getClassName());
			}
		}
		
		
		/**
		 * Instantiate a Controller (Factory Pattern).
		 * 
		 * @throws	DatamodelException
		 * @throws	DriverNotFoundException
		 * @throws	DriverNotValidException
		 * @throws	ModelNotValidException
		 * @throws	ModelNotFoundException
		 * @throws	ViewNotFoundException
		 * @param	string	$controllerName	Name of the Controller to instantiate
		 * @param	string	$layoutName	Name of the current Layout
		 * @param	string	$action		Current Action
		 */
		public static function factory($controllerName, $layoutName, $action, $agent)
		{
			// Determine full classname
			$className = self::getClassName($controllerName);
			
			// Construct and return Controller
			return new $className($layoutName, $action, $agent);
		}
		
		
		/**
		 * Determine the classname for the given Controller name.
		 * 
		 * @param	string	$controllerName	Controller name to get classname of
		 * @return	string			Classname for the Controller name
		 */
		private static function getClassName($controllerName)
		{
			$className = \nre\core\ClassLoader::concatClassNames($controllerName, \nre\core\ClassLoader::stripNamespace(get_class()));
			
			
			return \nre\configs\AppConfig::$app['namespace']."controllers\\$className";
		}
		
		
		
		
		/**
		 * Construct a new Controller.
		 * 
		 * @throws	DriverNotFoundException
		 * @throws	DriverNotValidException
		 * @throws	ModelNotValidException
		 * @throws	ModelNotFoundException
		 * @throws	ViewNotFoundException
		 * @param	string	$layoutName	Name of the current Layout
		 * @param	string	$action		Current Action
		 * @param	Agent	$agent		Corresponding Agent
		 */
		protected function __construct($layoutName, $action, $agent)
		{
			// Store values
			$this->agent = $agent;
			
			// Load Components
			$this->loadComponents();
			
			// Load Models
			$this->loadModels();
			
			// Load View
			$this->loadView($layoutName, $action);
		}
		
		
		
		
		/**
		 * Prefilter that is executed before running the Controller.
		 * 
		 * @param	Request		$request	Current request
		 * @param	Response	$response	Current response
		 */
		public function preFilter(Request $request, Response $response)
		{
			// Request speichern
			$this->request = $request;
			
			// Response speichern
			$this->response = $response;
			
		}
		
		
		/**
		 * Prefilter that is executed after running the Controller.
		 * 
		 * @param	Request		$request	Current request
		 * @param	Response	$response	Current response
		 */
		public function postFilter(Request $request, Response $response)
		{
		}
		
		
		/**
		 * Run the Controller.
		 * 
		 * This method executes the Action of the Controller defined by
		 * the current Request.
		 * 
		 * @throws	ParamsNotValidException
		 * @throws	IdNotFoundException
		 * @throws	DatamodelException
		 * @throws	ActionNotFoundException
		 * @param	Request		$request	Current request
		 * @param	Response	$response	Current response
		 */
		public function run(Request $request, Response $response)
		{
			// Determine Action
			$action = $response->getParam(2, 'action');
			if(!method_exists($this, $action)) {
				throw new \nre\exceptions\ActionNotFoundException($action);
			}
			
			// Determine parameters
			$params = $response->getParams(3);
			if(empty($params)) {
				$params = $request->getParams(3);
			}
			
			// Fill missing parameters
			$rc = new \ReflectionClass($this);
			$nullParamsCount = $rc->getMethod($action)->getNumberOfParameters() - count($params);
			$nullParams = ($nullParamsCount > 0 ? array_fill(0, $nullParamsCount, NULL) : array());
			
			
			// Call Action
			call_user_func_array(
				array(
					$this,
					$action
				),
				array_merge(
					$params,
					$nullParams
				)
			);
		}
		
		
		/**
		 * Generate the output.
		 * 
		 * @param	array	$viewData	Data to pass to the View
		 * @return	string			Generated output
		 */
		public function render($viewData=null)
		{
			// Combine given data and data of this Controller
			$data = $this->viewData;
			if(!is_null($viewData)) {
				$data = array_merge($viewData, $data);
			}
			
			// Rendern and return output
			return $this->view->render($data);
		}
		
		
		
		
		/**
		 * Set data for the View.
		 * 
		 * @param	string	$name	Key
		 * @param	mixed	$data	Value
		 */
		protected function set($name, $data)
		{
			$this->viewData[$name] = $data;
		}
		
		
		/**
		 * Redirect to the given URL.
		 * 
		 * @param	string	$url	Relative URL
		 */
		protected function redirect($url)
		{
			$url = 'http://'.$_SERVER['HTTP_HOST'].$url;
			header('Location: '.$url);
			exit;
		}
		
		
		/**
		 * Check if Models of this Controller are loaded and available.
		 * 
		 * @param	string	$modelName	Arbitrary number of Models to check
		 * @return	bool			All given Models are loaded and available
		 */
		protected function checkModels($modelName)
		{
			foreach(func_get_args() as $modelName)
			{
				if(!isset($this->$modelName) || !is_subclass_of($this->$modelName, 'Model')) {
					return false;
				}
			}
			
			
			return true;
		}
		
		
		/**
		 * Get the View of the Controller
		 * 
		 * @return	View	View of the Controller
		 */
		protected function getView()
		{
			return $this->view;
		}
		
		
		
		
		/**
		 * Load the Components of this Controller.
		 * 
		 * @throws	ComponentNotValidException
		 * @throws	ComponentNotFoundException
		 */
		private function loadComponents()
		{
			// Determine components
			$components = array();
			if(property_exists($this, 'components')) {
				$components = $this->components;
			}
			if(!is_array($components)) {
				$components = array($components);
			}
			// Components of parent classes
			$parent = $this;
			while($parent = get_parent_class($parent))
			{
				$properties = get_class_vars($parent);
				if(array_key_exists('components', $properties)) {
					$components = array_merge($components, $properties['components']);
				}
			}
			
			// Load components
			foreach($components as &$component)
			{
				// Load class
				Component::load($component);
					
				// Construct component
				$componentName = ucfirst(strtolower($component));
				$this->$componentName = Component::factory($component);
			}
		}
		
		
		/**
		 * Load the Models of this Controller.
		 * 
		 * @throws	DatamodelException
		 * @throws	DriverNotFoundException
		 * @throws	DriverNotValidException
		 * @throws	ModelNotValidException
		 * @throws	ModelNotFoundException
		 */
		protected function loadModels()
		{
			// Determine Models
			$explicit = false;
			$models = \nre\core\ClassLoader::stripClassType(\nre\core\ClassLoader::stripNamespace(get_class($this)));
			if(property_exists($this, 'models'))
			{
				$models = $this->models;
				$explicit = true;
			}
			if(!is_array($models)) {
				$models = array($models);
			}
			// Models of parent classes
			$parent = $this;
			while($parent = get_parent_class($parent))
			{
				$properties = get_class_vars($parent);
				if(array_key_exists('models', $properties)) {
					$models = array_merge($models, $properties['models']);
				}
			}
			
			// Load Models
			foreach($models as &$model)
			{
				try {
					// Load class
					Model::load($model);
					
					// Construct Model
					$modelName = ucfirst(strtolower($model));
					$this->$modelName = Model::factory($model);
				}
				catch(\nre\exceptions\ModelNotValidException $e) {
					if($explicit) {
						throw $e;
					}
				}
				catch(\nre\exceptions\ModelNotFoundException $e) {
					if($explicit) {
						throw $e;
					}
				}
			}
		}
		
		
		/**
		 * Load the View of this Controller.
		 * 
		 * @throws	ViewNotFoundException
		 * @param	string	$layoutName	Name of the current Layout
		 * @param	string	$action		Current Action
		 */
		protected function loadView($layoutName, $action)
		{
			// Check Layout name
			if(is_null($layoutName)) {
				return;
			}
			
			// Determine controller name
			$controllerName = \nre\core\ClassLoader::getClassName(get_class($this));
			
			
			// Load view
			$isToplevel = is_subclass_of($this->agent, '\nre\agents\ToplevelAgent');
			$this->view = View::loadAndFactory($layoutName, $controllerName, $action, $isToplevel);
		}
		
	}

?>
