<?php
/**
 * A class for establishing a fractal environment.
 *
 * This class establishes a cascading directory structure and enables class
 * autoloading.
 */
class fractal
{
    /**
     * @var array The cascading directory list.
     */
    protected static $directory = array('');
    
    /**
     * @var string The inner class directory.
     */
    protected static $class = '';
    
    /**
     * @var callback The autoload callback.
     */
    protected static $autoload;
    
    ////////////////////////////////////////////////////////////////////////////
    // Construct and destruct functions.

    ////////////////////////////////////////////////////////////////////////////
    // Overload functions.

    ////////////////////////////////////////////////////////////////////////////
    // Interface functions.

    /**
     * Loads the fractal environment.
     * 
     * @param string/array $directory The cascading directory list.
     * @param string (optional) $class The inner class directory.
     * @param (optional) callback $autoload The autoload callback.
     * @throws logicexception If the autoload callback could not be registered
     * to the SPL autoload registry.
     */
    public static function load($directory, $class = '', $autoload = 'fractal::load_class')
    {
        self::set($directory);
        self::$class = $class;
        
        if (!spl_autoload_register($autoload))
        {
            throw new logicexception('Callback ' . var_export($autoload, true) . ' could not be registered to the SPL autoload registry.');
        }
        
        self::$autoload = $autoload;
    }

    /**
     * Unloads the fractal environment.
     *
     * @throws logicexception If the registered autoload callback could not be
     * unregistered from the SPL autoload registry.
     */
    public static function unload()
    {
        self::clear();
        self::$class = '';
        
        if (!spl_autoload_unregister(self::$autoload))
        {
            throw new logicexception('Callback ' . var_export(self::$autoload, true) . ' could not be unregistered from the SPL autoload registry.');
        }
        
        self::$autoload = null;
    }

    /**
     * Finds a file from the fractal environment.
     * 
     * @param string $file The filename.
     * @return string/null The filename if found, else null.
     */
    public static function find($file)
    {
        foreach (self::$directory as $d)
        {
            $f = $d . $file;
            
            if (is_file($f))
            {
                return $f;
            }
        }
    }

    /**
     * Sets a cascading directory.
     * 
     * @param string/array $directory The directory name.
     */
    public static function set($directory)
    {
        if (!is_array($directory))
        {
            $directory = array($directory);
        }
        
        foreach (array_reverse($directory) as $d)
        {
            self::clear($d);
            array_unshift(self::$directory, $d);
        }
    }
    
    /**
     * Clears a cascading directory.
     * 
     * @param array $directory The directory name.
     */
    public static function clear($directory = null)
    {
        if ($directory === null)
        {
            self::$directory = array('');
        }
        else
        {
            $key = array_search($directory, self::$directory);
            
            if ($key !== false)
            {
                unset(self::$directory[$key]);
            }
        }
    }
    
    /**
     * Loads a class.
     * 
     * @param string $class The class name.
     * @throws logicexception If the class definition is not present in the file.
     */
    public static function load_class($class)
    {
        $file = self::find(self::$class . str_replace('_', '/', $class) . '.php');
        
        if ($file)
        {
            include $file;
            
            if (!class_exists($class, false) && !interface_exists($class, false))
            {
                # Prevent fatal error.
                eval('class ' . preg_replace('~[^_a-zA-Z0-9]~', '', $class) . ' {}');
                #
                
                throw new logicexception('Class ' . $class . ' is not defined in file ' . var_export($file, true) . '.');
            }
        }
    }
    
    ////////////////////////////////////////////////////////////////////////////
    // Internal functions.
}
