<?php
	
	/**
	* Fancy class loader which uses an index
	* 
	* This class is based on SmartLoader
	*
	* @package Environment
	*/
	class AutoLoader {
	  
		/**
		* mod settings for the index file
		*/
		const INDEX_FILE_MOD = 0775;
		
		/**
		* Name of the $GLOBALS var where we'll store class names
		*
		* @var string
		*/
		const GLOBAL_VAR = 'autoloader_classes';
		
		/**
		* Filename of index file
		*
		* @var string
		*/
		private $index_filename = 'autoloader_index.php';
		
		/**
		* Array of directory paths that need to be parsed
		*
		* @var array
		*/
		private $parse_directories = array();
		
		/**
		* Extension of files that need to be scaned
		*
		* @var string
		*/
		private $scan_file_extension = 'class.php';
		
		/**
		* Ignore hidden and system files
		*
		* @var boolean
		*/
		private $ignore_hidden_files = true;
		
		/**
		* Cached array of parsed directories, keep it from endless loop
		* 
		* @var array
		*/
		private $parsed_directories = array();
		
		/**
		* Class index
		*
		* @var array
		*/
		private $class_index = array();
		
		// ---------------------------------------------------
		//  Doers
		// ---------------------------------------------------
		
		/**
		* Loads a class by its name
		*
		* @access public
		* @param string $class_name
		* @throws Exception
		* @return boolean Success
		*/
		public function loadClass($load_class_name) {
			static $retrying = false; // is this our second loading attempt?
			
			$class_name = strtoupper($load_class_name);
			
			/* Recreate the index file, if outdated */
			if(!isset($GLOBALS[self::GLOBAL_VAR])) {
				if($retrying || !is_readable($this->getIndexFilename())) {
					$this->createCache();
					if(!is_readable($this->getIndexFilename())) {
					  throw new Exception('SmartLoader index file "'.$this->indexFilename.'" is not readable!');
					} // if
				} // if
				include $this->getIndexFilename();
			} // if
			
			/* include the needed file or retry on failure */
			if(isset($GLOBALS[self::GLOBAL_VAR][$class_name])) {
				if(@include($GLOBALS[self::GLOBAL_VAR][$class_name])) {
					return true;
				} else {
					if($retrying) {
						throw new Exception('Class file "' . $GLOBALS[self::GLOBAL_VAR][$class_name] . '" for class "'.$class_name.'" cannot be included!');
					} // if
				} // if
			} elseif($retrying) {
				/* we failed while retrying. this is bad. */
				throw new Exception('Could not find class file for "'.$class_name.'"');
			} // if
			
			/* including failed. try again. */
			unset($GLOBALS[self::GLOBAL_VAR]);
			$retrying = true;
			return $this->loadClass($class_name);
		} // loadClass
		
		/**
		* - Scans the class dirs for class/interface definitions and 
		* 	creates an associative array (class name => class file) 
		* - Generates the array in PHP code and saves it as index file
		*
		* @access private
		* @param param_type $param_name
		* @throws Exception
		*/
		private function createCache() {
			if(is_array($this->parse_directories) && count($this->parse_directories)) {
			  foreach($this->parse_directories as $dir) $this->parseDir($dir);
			} // if
			$this->createIndexFile();
		} // createCache
		
		/**
		* Write out to the index file
		*
		* @access private
		* @throws Exception
		*/
		private function createIndexFile() {
			/* generate php index file */
			$index_content = "<?php\n";
			foreach($this->class_index as $class_name => $class_file) {
				$index_content .= "\t\$GLOBALS['autoloader_classes'][". var_export(strtoupper($class_name), true) . "] = " . var_export($class_file, true) . ";\n";
			} // foreach
			$index_content .= "?>";
			if(!@file_put_contents($this->getIndexFilename(), $index_content)) {
				throw new Exception('Could not write to "'.$this->getIndexFilename().'". Make sure, that your webserver has write access to it.');
			} // if
			
			/* Apply mod rights */
			@chmod($this->getIndexFilename(), self::INDEX_FILE_MOD );
		} // createIndexFile
		
		/**
		* Parses a directory for class/interface definitions. Saves found definitions
		* in $classIndex
		*
		* @access private
		* @param string $directory_path
		* @throws Exception
		* @return boolean Success
		*/
		private function parseDir($directory_path) {
		  $directory_path = with_slash($directory_path);
		  if(in_array($directory_path, $this->parsed_directories)) {
		    return;
		  } else {
		    $this->parsed_directories[] = $directory_path;
		  } // if
		  
		  $dir = dir($directory_path);
		  while (false !== ($entry = $dir->read())) {
		    if($entry == '.' || $entry == '..') {
		      continue;
		    } // if
		    $path = $directory_path . $entry;
		    if(is_dir($path)) {
		      if($this->getIgnoreHiddenFiles() && ($entry[0] == '.')) {
		        continue;
		      } // if
		      if(!is_readable($path)) {
		        continue;
		      } // if
		      $this->parseDir($path);
		    } elseif(is_file($path)) {
		      if(!is_readable($path)) {
		        continue;
		      } // if
		      if(str_ends_with($path, $this->getScanFileExtension())) {
		        $this->parseFile($path);
		      } // if
		    } // if
		  } // if
		  $dir->close();
		} // parseDir
		
		/**
		* Parse a file for PHP classes and add them to our classIndex
		*
		* @access private
		* @param string path to file
		* @throws Exception
		*/
		private function parseFile($path) {
			if(!$buf = @file_get_contents($path)) {
			  throw new Exception('Couldn\'t read file contents from "'.$path.'".');
			} // if
			
			/* searching for classes */
			if(preg_match_all("%(interface|class)\s+(\w+)\s+(extends\s+(\w+)\s+)?(implements\s+\w+\s*(,\s*\w+\s*)*)?{%im", $buf, $result)) {
				foreach($result[2] as $class_name) {
				  $this->class_index[$class_name] = str_replace('\\', '/', $path);
				} // if
			} // if
		} // parseFile
		
		// ---------------------------------------------------
		//  Gettes and setters
		// ---------------------------------------------------
		
		/**
		* Add directory that need to be scaned
		*
		* @param stirng $path Direcotry path
		* @return null
		*/
		function addDir($path) {
		  if(is_dir($path)) $this->parse_directories[] = $path;
		} // addDir
		
		/**
		* Get index_filename
		*
		* @access public
		* @param null
		* @return string
		*/
		function getIndexFilename() {
		  return $this->index_filename;
		} // getIndexFilename
		
		/**
		* Set index_filename value
		*
		* @access public
		* @param string $value
		* @return null
		*/
		function setIndexFilename($value) {
		  $this->index_filename = $value;
		} // setIndexFilename
		
		/**
		* Get scan_file_extension
		*
		* @access public
		* @param null
		* @return string
		*/
		function getScanFileExtension() {
		  return $this->scan_file_extension;
		} // getScanFileExtension
		
		/**
		* Set scan_file_extension value
		*
		* @access public
		* @param string $value
		* @return null
		*/
		function setScanFileExtension($value) {
		  $this->scan_file_extension = $value;
		} // setScanFileExtension
		
		/**
		* Get ignore_hidden_files
		*
		* @access public
		* @param null
		* @return boolean
		*/
		function getIgnoreHiddenFiles() {
		  return $this->ignore_hidden_files;
		} // getIgnoreHiddenFiles
		
		/**
		* Set ignore_hidden_files value
		*
		* @access public
		* @param boolean $value
		* @return null
		*/
		function setIgnoreHiddenFiles($value) {
		  $this->ignore_hidden_files = $value;
		} // setIgnoreHiddenFiles
		
	} // AutoLoader
	
?>