<?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 the implementation af an Agent.
	 * 
	 * @author	coderkun <olli@coderkun.de>
	 */
	abstract class Agent
	{
		/**
		 * Name of BottomlevelAgent for showing inline errors
		 * 
		 * @var string
		 */
		const INLINEERROR_AGENT		=	'inlineerror';
		
		/**
		 * Current request
		 * 
		 * @var Request
		 */
		private $request;
		/**
		 * Current response
		 * 
		 * @var Response
		 */
		private $response;
		/**
		 * Log-system
		 * 
		 * @var Logger
		 */
		protected $log;
		/**
		 * SubAgents
		 * 
		 * @var array
		 */
		protected $subAgents = array();
		/**
		 * Controller of this Agent
		 * 
		 * @var Controller
		 */
		public $controller = null;
		
		
		
		
		/**
		 * Load the class of an Agent.
		 * 
		 * @static
		 * @throws	AgentNotFoundException
		 * @throws	AgentNotValidException
		 * @param	string	$agentName	Name of the Agent to load
		 */
		public static function load($agentName)
		{
			// Determine full classname
			$agentType = self::getAgentType();
			$className = self::getClassName($agentName, $agentType);
			
			try {
				// Load class
				ClassLoader::load($className);
			
				// Validate class
				$parentAgentClassName = ClassLoader::concatClassNames($agentType, 'agent');
				$parentAgentClassName = "\\nre\\agents\\$parentAgentClassName";
				ClassLoader::check($className, $parentAgentClassName);
			}
			catch(\nre\exceptions\ClassNotValidException $e) {
				throw new \nre\exceptions\AgentNotValidException($e->getClassName());
			}
			catch(\nre\exceptions\ClassNotFoundException $e) {
				throw new \nre\exceptions\AgentNotFoundException($e->getClassName());
			}
		}
		
		
		
		/**
		 * Instantiate an Agent (Factory Pattern).
		 * 
		 * @static
		 * @throws	DatamodelException
		 * @throws	DriverNotValidException
		 * @throws	DriverNotFoundException
		 * @throws	ViewNotFoundException
		 * @throws	ModelNotValidException
		 * @throws	ModelNotFoundException
		 * @throws	ControllerNotValidException
		 * @throws	ControllerNotFoundException
		 * @param	string		$agentName	Name of the Agent to instantiate
		 * @param	Request		$request	Current request
		 * @param	Response	$respone	Current respone
		 * @param	Logger		$log		Log-system
		 */
		public static function factory($agentName, Request $request, Response $response, Logger $log=null)
		{
			// Determine full classname
			$agentType = self::getAgentType();
			$className = self::getClassName($agentName, $agentType);
			
			
			// Construct and return Agent
			return new $className($request, $response, $log);
		}
		
		
		/**
		 * Determine the type of an Agent.
		 * 
		 * @static
		 * @return	string	Agent type
		 */
		private static function getAgentType()
		{
			return strtolower(ClassLoader::getClassName(get_called_class()));
		}
		
		
		/**
		 * Determine the classname for the given Agent name.
		 * 
		 * @static
		 * @param	string	$agentName	Agent name to get classname of
		 * @param	string	$agentType	Agent type of given Agent name
		 * @return	string			Classname for the Agent name
		 */
		private static function getClassName($agentName, $agentType)
		{
			$className = ClassLoader::concatClassNames($agentName, 'agent');
			
			
			return \nre\configs\AppConfig::$app['namespace']."agents\\$agentType\\$className";
		}
		
		
		
		
		/**
		 * Construct a new Agent.
		 * 
		 * @throws	DatamodelException
		 * @throws	DriverNotValidException
		 * @throws	DriverNotFoundException
		 * @throws	ViewNotFoundException
		 * @throws	ModelNotValidException
		 * @throws	ModelNotFoundException
		 * @throws	ControllerNotValidException
		 * @throws	ControllerNotFoundException
		 * @param	Request		$request	Current request
		 * @param	Response	$respone	Current response
		 * @param	Logger		$log		Log-system
		 */
		protected function __construct(Request $request, Response $response, Logger $log=null)
		{
			// Store values
			$this->request = $request;
			$this->response = $response;
			$this->log = $log;
			
			// Construct SubAgent
			$this->actionConstruct();
			
			// Load corresponding Controller
			$this->loadController();
		}
		
		
		
		
		/**
		 * Run the Controller of this Agent and its SubAgents.
		 * 
		 * @throws	ParamsNotValidException
		 * @throws	IdNotFoundException
		 * @throws	DatamodelException
		 * @throws	ActionNotFoundException
		 * @param	Request		$request	Current request
		 * @param	Response	$response	Current response
		 * @return	Exception			Last occurred exception of SubAgents
		 */
		public function run(Request $request, Response $response)
		{
			// Check Controller
			if(!is_null($this->controller))
			{
				// Call prefilter
				$this->controller->preFilter($request, $response);
				
				// Run controller
				$this->controller->run($request, $response);
				
				// Call postfilter
				$this->controller->postFilter($request, $response);
			}
			
			
			// Run SubAgents
			$exception = null;
			foreach($this->subAgents as &$subAgent)
			{
				try {
					$subAgent['object']->run(
						$request,
						$subAgent['response']
					);
				}
		 		catch(ParamsNotValidException $e) {
		 			$subAgent = $this->errorInline($subAgent, $request, $e);
		 		}
		 		catch(IdNotFoundException $e) {
		 			$subAgent = $this->errorInline($subAgent, $request, $e);
		 		}
		 		catch(DatamodelException $e) {
		 			$exception = $e;
		 			$subAgent = $this->errorInline($subAgent, $request, $e);
		 		}
				catch(ActionNotFoundException $e) {
					$subAgent = $this->errorInline($subAgent, $request, $e);
				}
			}
			
			
			// Return last occurred exception
			return $exception;
		}
		
		
		/**
		 * Generate output of the Controller of this Agent and its
		 * SubAgents.
		 * 
		 * @param	array	$data	View data
		 * @return	string		Generated output
		 */
		public function render($data=array())
		{
			// Check Controller
			if(!is_null($this->controller))
			{
				// Render SubAgents
				foreach($this->subAgents as $subAgent)
				{
					$label = array_key_exists('label', $subAgent) ? $subAgent['label'] : $subAgent['name'];
					$data[$label] = $this->renderSubAgent($subAgent);
				}
				
				// Render the Controller of this agent
				return $this->controller->render($data);
			}
		}
		
		
		
		
		/**
		 * Construct SubAgents (per Action).
		 */
		protected function actionConstruct()
		{
			// Action ermitteln
			$action = $this->response->getParam(2);
			if(is_null($action)) {
				$action = $this->request->getParam(2, 'action');
				$this->response->addParam($action);
			}
			
			// Initialisierungsmethode für diese Action ausführen
			if(method_exists($this, $action))
			{
				call_user_func_array(
					array(
						$this,
						$action
					),
					array(
						$this->request,
						$this->response
					)
				);
			}
		}
		
		
		/**
		 * Load the Controller of this Agent.
		 * 
		 * @throws	DatamodelException
		 * @throws	DriverNotValidException
		 * @throws	DriverNotFoundException
		 * @throws	ViewNotFoundException
		 * @throws	ModelNotValidException
		 * @throws	ModelNotFoundException
		 * @throws	ControllerNotValidException
		 * @throws	ControllerNotFoundException
		 */
		protected function loadController()
		{
			// Determine Controller name
			$controllerName = ClassLoader::getClassName(get_class($this));
			
			// Determine ToplevelAgent
			$toplevelAgentName = $this->response->getParam(0);
			if(is_null($toplevelAgentName)) {
				$toplevelAgentName = $this->request->getParam(0, 'toplevel');
				$this->response->addParam($toplevelAgentName);
			}
			
			// Determine Action
			$action = $this->response->getParam(2);
			if(is_null($action)) {
				$action = $this->request->getParam(2, 'action');
				$this->response->addParam($action);
			}
			
			
			// Load Controller
			Controller::load($controllerName);
			
			// Construct Controller
			$this->controller = Controller::factory($controllerName, $toplevelAgentName, $action, $this);
		}
		
		
		/**
		 * Log an error.
		 * 
		 * @param	Exception	$exception	Occurred exception
		 * @param	int		$logMode	Log mode
		 */
		protected function log($exception, $logMode)
		{
			if(is_null($this->log)) {
				return;
			}
			
			$this->log->log(
				$exception->getMessage(),
				$logMode
			);
		}
		
		
		/**
		 * Load a SubAgent and add it.
		 * 
		 * @throws	ServiceUnavailableException
		 * @throws	AgentNotFoundException
		 * @throws	AgentNotValidException
		 * @param	string	$agentName	Name of the Agent to load
		 * @param	mixed	…		Additional parameters for the agent
		 */
		protected function addSubAgent($agentName)
		{
			try {
				call_user_func_array(
					array(
						$this,
						'_addSubAgent'
					),
					func_get_args()
				);
			}
			catch(DatamodelException $e) {
				$this->subAgents[] = $this->newInlineError($agentName, $e);
			}
		}
		
		
		/**
		 * Load a SubAgent and add it.
		 * 
		 * @throws	ServiceUnavailableException
		 * @throws	DatamodelException
		 * @throws	AgentNotFoundException
		 * @throws	AgentNotValidException
		 * @param	string	$agentName	Name of the Agent to load
		 * @param	mixed	…		Additional parameters for the agent
		 */
		protected function _addSubAgent($agentName)
		{
			try {
				// Load Agent
				\nre\agents\BottomlevelAgent::load($agentName);
				
				// Construct Agent
				$this->subAgents[] = call_user_func_array(
					array(
						$this,
						'newSubAgent'
					),
					func_get_args()
				 );
			}
			catch(ViewNotFoundException $e) {
				$this->subAgents[] = $this->newInlineError($agentName, $e);
			}
			catch(DriverNotValidException $e) {
				$this->subAgents[] = $this->newInlineError($agentName, $e);
			}
			catch(DriverNotFoundException $e) {
				$this->subAgents[] = $this->newInlineError($agentName, $e);
			}
			catch(ModelNotValidException $e) {
				$this->subAgents[] = $this->newInlineError($agentName, $e);
			}
			catch(ModelNotFoundException $e) {
				$this->subAgents[] = $this->newInlineError($agentName, $e);
			}
			catch(ControllerNotValidException $e) {
				$this->subAgents[] = $this->newInlineError($agentName, $e);
			}
			catch(ControllerNotFoundException $e) {
				$this->subAgents[] = $this->newInlineError($agentName, $e);
			}
			catch(AgentNotValidException $e) {
				$this->subAgents[] = $this->newInlineError($agentName, $e);
			}
			catch(AgentNotFoundException $e) {
				$this->subAgents[] = $this->newInlineError($agentName, $e);
			}
		}
		
		
		
		
		/**
		 * Create a new SubAgent.
		 * 
		 * @throws	DatamodelException
		 * @param	string	$agentName	Agent name
		 * @return	array			SubAgent
		 */
		private function newSubAgent($agentName)
		{
			// Response
			$response = clone $this->response;
			$response->clearParams(1);
			$params = func_get_args();
			if(count($params) < 2 || empty($params[1])) { 
				$params[1] = \nre\configs\CoreConfig::$defaults['action'];
			}
			call_user_func_array(
				array(
					$response,
					'addParams'
				),
				$params
			);
			
			return array(
				'name'		=>	strtolower($agentName),
				'response'	=>	$response,
				'object'	=>	\nre\agents\BottomlevelAgent::factory(
					$agentName,
					$this->request,
					$response,
					$this->log
				)
			);
		}
		
		
		/**
		 * Render a SubAgent.
		 * 
		 * @param	array	$subAgent	SubAgent to render
		 * @return	string			Generated output
		 */
		private function renderSubAgent(&$subAgent)
		{
			// Check for InlineError
			if(array_key_exists('inlineerror', $subAgent) && !empty($subAgent['inlineerror'])) {
				return file_get_contents($subAgent['inlineerror']);
			}
			
			
			// Rendern SubAgent and return its output
			return $subAgent['object']->render();
		}
		
		
		/**
		 * Handle the exception of a SubAgent.
		 * 
		 * @param	string		$label		Name of the original Agent
		 * @param	Excepiton	$exception	Occurred exception
		 * @return	array				InlineError-SubAgent
		 */
		private function errorInline($subAgent, $request, $exception)
		{
			// Create the SubAgent for the exception
			$subAgent = $this->newInlineError($subAgent['name'], $exception);
			
			
			// Run the InlineError-SubAgent
			try {
				$subAgent['object']->run(
					$request,
					$subAgent['response']
				);
			}
			catch(ActionNotFoundException $e) {
				$this->log($e, Logger::LOGMODE_AUTO);
				$subAgent['inlineerror'] = $this->newInlineErrorService();
			}
			
			
			// Return the InlineError-SubAgent
			return $subAgent;
		}
		
		
		/**
		 * Create a new InlineError.
		 * 
		 * @param	string		$label		Name of the original Agent
		 * @param	Exception	$exception	Occurred exception
		 */
		private function newInlineError($label, $exception)
		{
			// Log error
			$this->log($exception, Logger::LOGMODE_AUTO);
			
			// Determine Agent name
			$agentName = self::INLINEERROR_AGENT;
			
			// Create SugAgent
			$subAgent = array();
			
			
			try {
				// Load Agenten
				\nre\agents\BottomlevelAgent::load($agentName);
			
				// Construct Agent
				$subAgent = $this->newSubAgent($agentName);
				$subAgent['label'] = $label;
				$subAgent['response']->addParam($exception);
			}
			catch(ViewNotFoundException $e) {
				$subAgent['inlineerror'] = $this->newInlineErrorService();
			}
			catch(DatamodelException $e) {
				$subAgent['inlineerror'] = $this->newInlineErrorService();
			}
			catch(DriverNotValidException $e) {
				$subAgent['inlineerror'] = $this->newInlineErrorService();
			}
			catch(DriverNotFoundException $e) {
				$subAgent['inlineerror'] = $this->newInlineErrorService();
			}
			catch(ModelNotValidException $e) {
				$subAgent['inlineerror'] = $this->newInlineErrorService();
			}
			catch(ModelNotFoundException $e) {
				$subAgent['inlineerror'] = $this->newInlineErrorService();
			}
			catch(ControllerNotValidException $e) {
				$subAgent['inlineerror'] = $this->newInlineErrorService();
			}
			catch(ControllerNotFoundException $e) {
				$subAgent['inlineerror'] = $this->newInlineErrorService();
			}
			catch(AgentNotValidException $e) {
				$subAgent['inlineerror'] = $this->newInlineErrorService();
			}
			catch(AgentNotFoundException $e) {
				$subAgent['inlineerror'] = $this->newInlineErrorService();
			}
			
			
			// Return SubAgent
			return $subAgent;
		}
		
		
		/**
		 * Handle a hardcore error that could not be handled by the
		 * system.
		 */
		private function newInlineErrorService()
		{
			// Read and return static error file
			return ROOT.DS.\nre\configs\CoreConfig::getClassDir('views').DS.\nre\configs\CoreConfig::$defaults['inlineErrorFile'].\nre\configs\Config::getFileExt('views');
		}
		
	}

?>
