<?php

abstract class CI_TestCase extends PHPUnit_Framework_TestCase {

    public $ci_vfs_root;
    public $ci_app_root;
    public $ci_base_root;
    protected $ci_instance;
    protected static $ci_test_instance;

    private $global_map = array(
        'benchmark' => 'bm',
        'config'    => 'cfg',
        'hooks'     => 'ext',
        'utf8'      => 'uni',
        'router'    => 'rtr',
        'output'    => 'out',
        'security'  => 'sec',
        'input'     => 'in',
        'lang'      => 'lang',
        'loader'    => 'load',
        'model'     => 'model'
    );

    // --------------------------------------------------------------------

    public function __construct()
    {
        parent::__construct();
        $this->ci_instance = new stdClass();
    }

    // --------------------------------------------------------------------

    public function setUp()
    {
        // Setup VFS with base directories
        $this->ci_vfs_root = vfsStream::setup();
        $this->ci_app_root = vfsStream::newDirectory('application')->at($this->ci_vfs_root);
        $this->ci_base_root = vfsStream::newDirectory('system')->at($this->ci_vfs_root);
        $this->ci_view_root = vfsStream::newDirectory('views')->at($this->ci_app_root);

        if (method_exists($this, 'set_up'))
        {
            $this->set_up();
        }
    }

    // --------------------------------------------------------------------

    public function tearDown()
    {
        if (method_exists($this, 'tear_down'))
        {
            $this->tear_down();
        }
    }

    // --------------------------------------------------------------------

    public static function instance()
    {
        return self::$ci_test_instance;
    }

    // --------------------------------------------------------------------

    public function ci_set_config($key = '', $val = '')
    {
        // Add test config
        if ( ! isset($this->ci_instance->config))
        {
            $this->ci_instance->config = new CI_TestConfig();
        }

        // Empty key means just do setup above
        if ($key === '')
        {
            return;
        }

        if (is_array($key))
        {
            $this->ci_instance->config->config = $key;
        }
        else
        {
            $this->ci_instance->config->config[$key] = $val;
        }
    }

    // --------------------------------------------------------------------

    public function ci_get_config()
    {
        return isset($this->ci_instance->config) ? $this->ci_instance->config->config : array();
    }

    // --------------------------------------------------------------------

    public function ci_instance($obj = FALSE)
    {
        if ( ! is_object($obj))
        {
            return $this->ci_instance;
        }

        $this->ci_instance = $obj;
    }

    // --------------------------------------------------------------------

    public function ci_instance_var($name, $obj = FALSE)
    {
        if ( ! is_object($obj))
        {
            return $this->ci_instance->$name;
        }

        $this->ci_instance->$name =& $obj;
    }

    // --------------------------------------------------------------------

    /**
     * Grab a core class
     *
     * Loads the correct core class without extensions
     * and returns a reference to the class name in the
     * globals array with the correct key. This way the
     * test can modify the variable it assigns to and
     * still maintain the global.
     */
    public function &ci_core_class($name)
    {
        $name = strtolower($name);

        if (isset($this->global_map[$name]))
        {
            $class_name = ucfirst($name);
            $global_name = $this->global_map[$name];
        }
        elseif (in_array($name, $this->global_map))
        {
            $class_name = ucfirst(array_search($name, $this->global_map));
            $global_name = $name;
        }
        else
        {
            throw new Exception('Not a valid core class.');
        }

        if ( ! class_exists('CI_'.$class_name))
        {
            require_once SYSTEM_PATH.'core/'.$class_name.'.php';
        }

        $GLOBALS[strtoupper($global_name)] = 'CI_'.$class_name;
        return $GLOBALS[strtoupper($global_name)];
    }

    // --------------------------------------------------------------------

    // convenience function for global mocks
    public function ci_set_core_class($name, $obj)
    {
        $orig =& $this->ci_core_class($name);
        $orig = $obj;
    }

    /**
     * Create VFS directory
     *
     * @param    string    Directory name
     * @param    object    Optional root to create in
     * @return    object    New directory object
     */
    public function ci_vfs_mkdir($name, $root = NULL)
    {
        // Check for root
        if ( ! $root)
        {
            $root = $this->ci_vfs_root;
        }

        // Return new directory object
        return vfsStream::newDirectory($name)->at($root);
    }

    // --------------------------------------------------------------------

    /**
     * Create VFS content
     *
     * @param    string    File name
     * @param    string    File content
     * @param    object    VFS directory object
     * @param    mixed    Optional subdirectory path or array of subs
     * @return    void
     */
    public function ci_vfs_create($file, $content = '', $root = NULL, $path = NULL)
    {
        // Check for array
        if (is_array($file))
        {
            foreach ($file as $name => $content)
            {
                $this->ci_vfs_create($name, $content, $root, $path);
            }
            return;
        }

        // Assert .php extension if none given
        if (pathinfo($file, PATHINFO_EXTENSION) == '')
        {
            $file .= '.php';
        }

        // Build content
        $tree = array($file => $content);

        // Check for path
        $subs = array();
        if ($path)
        {
            // Explode if not array
            $subs = is_array($path) ? $path : explode('/', trim($path, '/'));
        }

        // Check for root
        if ( ! $root)
        {
            // Use base VFS root
            $root = $this->ci_vfs_root;
        }

        // Handle subdirectories
        while (($dir = array_shift($subs)))
        {
            // See if subdir exists under current root
            $dir_root = $root->getChild($dir);
            if ($dir_root)
            {
                   // Yes - recurse into subdir
                $root = $dir_root;
            }
            else
            {
                // No - put subdirectory back and quit
                array_unshift($subs, $dir);
                break;
            }
        }

        // Create any remaining subdirectories
        if ($subs)
        {
            foreach (array_reverse($subs) as $dir)
            {
                // Wrap content in subdirectory for creation
                $tree = array($dir => $tree);
            }
        }

        // Create tree
        vfsStream::create($tree, $root);
    }

    // --------------------------------------------------------------------

    /**
     * Clone a real file into VFS
     *
     * @param    string    Path from base directory
     * @return    bool    TRUE on success, otherwise FALSE
     */
    public function ci_vfs_clone($path)
    {
        // Check for array
        if (is_array($path))
        {
            foreach ($path as $file)
            {
                $this->ci_vfs_clone($file);
            }
            return;
        }

        // Get real file contents
        $content = file_get_contents(PROJECT_BASE.$path);
        if ($content === FALSE)
        {
            // Couldn't find file to clone
            return FALSE;
        }

        $this->ci_vfs_create(basename($path), $content, NULL, dirname($path));
        return TRUE;
    }

    // --------------------------------------------------------------------

    /**
     * Helper to get a VFS URL path
     *
     * @param    string    Path
     * @param    string    Optional base path
     * @return    string    Path URL
     */
    public function ci_vfs_path($path, $base = '')
    {
        // Check for base path
        if ($base)
        {
            // Prepend to path
            $path = rtrim($base, '/').'/'.ltrim($path, '/');

            // Is it already in URL form?
            if (strpos($path, '://') !== FALSE)
            {
                // Done - return path
                return $path;
            }
        }

        // Trim leading slash and return URL
        return vfsStream::url(ltrim($path, '/'));
    }

    // --------------------------------------------------------------------
    // Internals
    // --------------------------------------------------------------------

    /**
     * Overwrite runBare
     *
     * PHPUnit instantiates the test classes before
     * running them individually. So right before a test
     * runs we set our instance. Normally this step would
     * happen in setUp, but someone is bound to forget to
     * call the parent method and debugging this is no fun.
     */
    public function runBare()
    {
        self::$ci_test_instance = $this;
        parent::runBare();
    }

    // --------------------------------------------------------------------

    public function helper($name)
    {
        require_once(SYSTEM_PATH.'helpers/'.$name.'_helper.php');
    }

    // --------------------------------------------------------------------

    public function lang($name)
    {
        require(SYSTEM_PATH.'language/english/'.$name.'_lang.php');
        return $lang;
    }

    // --------------------------------------------------------------------

    /**
     * This overload is useful to create a stub, that need to have a specific method.
     */
    public function __call($method, $args)
    {
        if ($this->{$method} instanceof Closure)
        {
            return call_user_func_array($this->{$method},$args);
        }
        else
        {
            return parent::__call($method, $args);
        }
    }

}