<?php
/**
 * Kohana user guide and api browser.
 *
 * @package    Kohana/Userguide
 * @category   Controller
 * @author     Kohana Team
 * @copyright  (c) Kohana Team
 * @license    https://koseven.ga/LICENSE.md
 */
abstract class Kohana_Controller_Userguide extends Controller_Template {

	public $template = 'userguide/template';

	// Routes
	protected $media;
	protected $api;
	protected $guide;

	public function before()
	{
		parent::before();

		if ($this->request->action() === 'media')
		{
			// Do not template media files
			$this->auto_render = FALSE;
		}
		else
		{
			// Grab the necessary routes
			$this->media = Route::get('docs/media');
			$this->guide = Route::get('docs/guide');

			// Set the base URL for links and images
			Kodoc_Markdown::$base_url  = URL::site($this->guide->uri()).'/';
			Kodoc_Markdown::$image_url = URL::site($this->media->uri()).'/';
		}

		// Default show_comments to config value
		$this->template->show_comments = Kohana::$config->load('userguide.show_comments');
	}
	
	// List all modules that have userguides
	public function index()
	{
		$this->template->title = "Userguide";
		$this->template->breadcrumb = ['User Guide'];
		$this->template->content = View::factory('userguide/index', ['modules' => $this->_modules()]);
		$this->template->menu = View::factory('userguide/menu', ['modules' => $this->_modules()]);
		
		// Don't show disqus on the index page
		$this->template->show_comments = FALSE;
	}
	
	// Display an error if a page isn't found
	public function error($message)
	{
		$this->response->status(404);
		$this->template->title = "Userguide - Error";
		$this->template->content = View::factory('userguide/error',['message' => $message]);
		
		// Don't show disqus on error pages
		$this->template->show_comments = FALSE;

		// If we are in a module and that module has a menu, show that
		if ($module = $this->request->param('module') AND $menu = $this->file($module.'/menu') AND Kohana::$config->load('userguide.modules.'.$module.'.enabled'))
		{
			// Namespace the markdown parser
			Kodoc_Markdown::$base_url  = URL::site($this->guide->uri()).'/'.$module.'/';
			Kodoc_Markdown::$image_url = URL::site($this->media->uri()).'/'.$module.'/';

			$this->template->menu = Kodoc_Markdown::markdown($this->_get_all_menu_markdown());
			$this->template->breadcrumb = [
				$this->guide->uri() => 'User Guide',
				$this->guide->uri(['module' => $module]) => Kohana::$config->load('userguide.modules.'.$module.'.name'),
				'Error'
			];
		}
		// If we are in the api browser, show the menu and show the api browser in the breadcrumbs
		elseif (Route::name($this->request->route()) == 'docs/api')
		{
			$this->template->menu = Kodoc::menu();

			// Bind the breadcrumb
			$this->template->breadcrumb = [
				$this->guide->uri(['page' => NULL]) => 'User Guide',
				$this->request->route()->uri() => 'API Browser',
				'Error'
			];
		}
		// Otherwise, show the userguide module menu on the side
		else
		{
			$this->template->menu = View::factory('userguide/menu',['modules' => $this->_modules()]);
			$this->template->breadcrumb = [$this->request->route()->uri() => 'User Guide','Error'];
		}
	}

	public function action_docs()
	{
		$module = $this->request->param('module');
		$page = $this->request->param('page');

		// Trim trailing slash
		$page = rtrim($page, '/');

		// If no module provided in the url, show the user guide index page, which lists the modules.
		if ( ! $module)
		{
			return $this->index();
		}
		
		// If this module's userguide pages are disabled, show the error page
		if ( ! Kohana::$config->load('userguide.modules.'.$module.'.enabled'))
		{
			return $this->error('That module doesn\'t exist, or has userguide pages disabled.');
		}
		
		// Prevent "guide/module" and "guide/module/index" from having duplicate content
		if ($page == 'index')
		{
			return $this->error('Userguide page not found');
		}
		
		// If a module is set, but no page was provided in the url, show the index page
		if ( ! $page)
		{
			$page = 'index';
		}

		// Find the markdown file for this page
		$file = $this->file($module.'/'.$page);

		// If it's not found, show the error page
		if ( ! $file)
		{
			return $this->error('Userguide page not found');
		}
		
		// Namespace the markdown parser
		Kodoc_Markdown::$base_url  = URL::site($this->guide->uri()).'/'.$module.'/';
		Kodoc_Markdown::$image_url = URL::site($this->media->uri()).'/'.$module.'/';

		// Set the page title
		$this->template->title = ($page == 'index')
			? Kohana::$config->load('userguide.modules.'.$module.'.name')
			: $this->title($page);

		// Parse the page contents into the template
		Kodoc_Markdown::$show_toc = TRUE;
		$this->template->content = Kodoc_Markdown::markdown(file_get_contents($file));
		Kodoc_Markdown::$show_toc = FALSE;

		// Attach this module's menu to the template
		$this->template->menu = Kodoc_Markdown::markdown($this->_get_all_menu_markdown());

		// Bind the breadcrumb
		$this->template->bind('breadcrumb', $breadcrumb);
		
		// Bind the copyright
		$this->template->copyright = Kohana::$config->load('userguide.modules.'.$module.'.copyright');

		// Add the breadcrumb trail
		$breadcrumb = [];
		$breadcrumb[$this->guide->uri()] = 'User Guide';
		$breadcrumb[$this->guide->uri(['module' => $module])] = Kohana::$config->load('userguide.modules.'.$module.'.name');
		
		// TODO try and get parent category names (from menu).  Regex magic or javascript dom stuff perhaps?
		
		// Only add the current page title to breadcrumbs if it isn't the index, otherwise we get repeats.
		if ($page != 'index')
		{
			$breadcrumb[] = $this->template->title;
		}
	}

	public function action_api()
	{
		// Enable the missing class autoloader.  If a class cannot be found a
		// fake class will be created that extends Kodoc_Missing
		spl_autoload_register(['Kodoc_Missing', 'create_class']);

		// Get the class from the request
		$class = $this->request->param('class');

		// If no class was passed to the url, display the API index page
		if ( ! $class)
		{
			$this->template->title = 'Table of Contents';

			$this->template->content = View::factory('userguide/api/toc')
				->set('classes', Kodoc::class_methods())
				->set('route', $this->request->route());
		}
		else
		{
			// Create the Kodoc_Class version of this class.
			$_class = Kodoc_Class::factory($class);
			
			// If the class requested and the actual class name are different
			// (different case, orm vs ORM, auth vs Auth) redirect
			if ($_class->class->name != $class)
			{
				$this->redirect($this->request->route()->uri(['class'=>$_class->class->name]));
			}

			// If this classes immediate parent is Kodoc_Missing, then it should 404
			if ($_class->class->getParentClass() AND $_class->class->getParentClass()->name == 'Kodoc_Missing')
				return $this->error('That class was not found. Check your url and make sure that the module with that class is enabled.');

			// If this classes package has been disabled via the config, 404
			if ( ! Kodoc::show_class($_class))
				return $this->error('That class is in package that is hidden.  Check the <code>api_packages</code> config setting.');
		
			// Everything is fine, display the class.
			$this->template->title = $class;

			$this->template->content = View::factory('userguide/api/class')
				->set('doc', $_class)
				->set('route', $this->request->route());
		}

		// Attach the menu to the template
		$this->template->menu = Kodoc::menu();

		// Bind the breadcrumb
		$this->template->bind('breadcrumb', $breadcrumb);

		// Add the breadcrumb
		$breadcrumb = [];
		$breadcrumb[$this->guide->uri(['page' => NULL])] = 'User Guide';
		$breadcrumb[$this->request->route()->uri()] = 'API Browser';
		$breadcrumb[] = $this->template->title;
	}

	public function action_media()
	{
		// Get the file path from the request
		$file = $this->request->param('file');

		// Find the file extension
		$ext = pathinfo($file, PATHINFO_EXTENSION);

		// Remove the extension from the filename
		$file = substr($file, 0, -(strlen($ext) + 1));

		if ($file = Kohana::find_file('media/guide', $file, $ext))
		{
			// Check if the browser sent an "if-none-match: <etag>" header, and tell if the file hasn't changed
			$this->check_cache(sha1($this->request->uri()).filemtime($file));
			
			// Send the file content as the response
			$this->response->body(file_get_contents($file));

			// Set the proper headers to allow caching
			$this->response->headers('content-length', filesize($file));
			$this->response->headers('content-type',  File::mime_by_ext($ext));
			$this->response->headers('last-modified', date('r', filemtime($file)));
		}
		else
		{
			// Return a 404 status
			$this->response->status(404);
		}
	}

	public function after()
	{
		if ($this->auto_render)
		{
			// Get the media route
			$media = Route::get('docs/media');

			// Add styles
			$this->template->styles = [
				$media->uri(['file' => 'css/print.css'])  => 'print',
				$media->uri(['file' => 'css/screen.css']) => 'screen',
				$media->uri(['file' => 'css/kodoc.css'])  => 'screen',
				$media->uri(['file' => 'css/shCore.css']) => 'screen',
				$media->uri(['file' => 'css/shThemeKodoc.css']) => 'screen',
			];

			// Add scripts
			$this->template->scripts = [
				$media->uri(['file' => 'js/jquery.min.js']),
				$media->uri(['file' => 'js/jquery.cookie.js']),
				$media->uri(['file' => 'js/kodoc.js']),
				// Syntax Highlighter
				$media->uri(['file' => 'js/shCore.js']),
				$media->uri(['file' => 'js/shBrushPhp.js']),
			];

			// Add languages
			$this->template->translations = Kohana::message('userguide', 'translations');
		}

		return parent::after();
	}

	/**
	 * Locates the appropriate markdown file for a given guide page. Page URLS
	 * can be specified in one of three forms:
	 *
	 *  * userguide/adding
	 *  * userguide/adding.md
	 *  * userguide/adding.markdown
	 *
	 * In every case, the userguide will search the cascading file system paths
	 * for the file guide/userguide/adding.md.
	 *
	 * @param string $page The relative URL of the guide page
	 * @return string
	 */
	public function file($page)
	{

		// Strip optional .md or .markdown suffix from the passed filename
		$info = pathinfo($page);
		if (isset($info['extension'])
			AND (($info['extension'] === 'md') OR ($info['extension'] === 'markdown')))
		{
			$page = $info['dirname'].DIRECTORY_SEPARATOR.$info['filename'];
		}
		return Kohana::find_file('guide', $page, 'md');
	}

	public function section($page)
	{
		$markdown = $this->_get_all_menu_markdown();
		
		if (preg_match('~\*{2}(.+?)\*{2}[^*]+\[[^\]]+\]\('.preg_quote($page).'\)~mu', $markdown, $matches))
		{
			return $matches[1];
		}
		
		return $page;
	}

	public function title($page)
	{
		$markdown = $this->_get_all_menu_markdown();
		
		if (preg_match('~\[([^\]]+)\]\('.preg_quote($page).'\)~mu', $markdown, $matches))
		{
			// Found a title for this link
			return $matches[1];
		}
		
		return $page;
	}
	
	protected function _get_all_menu_markdown()
	{
		// Only do this once per request...
		static $markdown = '';
		
		if (empty($markdown))
		{
			// Get menu items
			$file = $this->file($this->request->param('module').'/menu');
	
			if ($file AND $text = file_get_contents($file))
			{
				// Add spans around non-link categories. This is a terrible hack.
				$text = preg_replace('/^(\s*[\-\*\+]\s*)([^\[\]]+)$/m','$1<span>$2</span>',$text);
				$markdown .= $text;
			}
			
		}
		
		return $markdown;
	}
	
	// Get the list of modules from the config, and reverses it so it displays in the order the modules are added, but move Kohana to the top.
	protected function _modules()
	{
		$modules = array_reverse(Kohana::$config->load('userguide.modules'));
		
		if (isset($modules['kohana']))
		{
			$kohana = $modules['kohana'];
			unset($modules['kohana']);
			$modules = array_merge(['kohana' => $kohana], $modules);
		}
		
		// Remove modules that have been disabled via config
		foreach ($modules as $key => $value)
		{
			if ( ! Kohana::$config->load('userguide.modules.'.$key.'.enabled'))
			{
				unset($modules[$key]);
			}
		}
		
		return $modules;
	}

} // End Userguide
