<?php
/**
 * ezTemplate
 * 
 * Codeigniter Template Engine
 * 
 * @author Doug Linsmeyer
 * @copyright Copyright (c) 2012, Doug Linsmeyer
 * @version 1.0.0
 * 
 * Templating library is a view organization and asset management library
 * for use in CodeIgniter implementations.
 * 
 * Dependencies:
 *   Helpers:    url
 */
class Template 
{
    
    /**
     * CONFIGURATION OPTIONS
     * 
     * Edit this section to fit your implementation
     *
     * @access protected
     * @var array
     *
     */
    protected $_config = array(

        /**
          * Page title that will be used unless otherwise specified
          *
          */
        'default_page_title' => '', 

        /**
         * Asset paths
         * 
         * All of the below settings require a trailing "/"
         * A convenient folder structure for your template assets might be:
         * http://www.yoursite.com/
         * -> assets/
         *       -> css/
         *           -> views/
         *       -> js/
         *           -> views/
         *
         * Inside each of your css/ and js/ folders you should create a 'views' folder
         * the contents of the 'views' folder should mirror the contents of your actual project
         * views folder. 
         *
         * In short, for every one of your actual project views, you can have a matching css and js file
         * it is important to note that all of these files are completely optional, you do not NEED to
         * create a js and css file for each view, but you can if you would like.
         * 
         * ex:  view-name.php cooresponds to view-name.js and view-name.css
         * 
         * Additionally, you can create a 'group file' in the same folder as your view that will be loaded
         * when any view from that folder is loaded. The default filename of your group file is "all", but this
         * can be changed in the configuration settings below
         * 
         * An example folder structure:
         *
         * <site_root>/application/views/
         * -> templates/
         *    -> primary_template.php
         * -> my_view_group/
         *    -> view_1.php
         *    -> view_2.php
         * 
         * will correspond to:
         *
         * <site_root>/assets/
         * -> css/
         *    -> views/
         *       -> my_view_group/
         *          -> all.css           <- will load when view_1.php or view_2.php are loaded
         *          -> view_1.css
         *          -> view_2.css
         * -> js/
         *    -> views/
         *       -> my_view_group/
         *          -> all.js            <- will load when view_1.php or view_2.php are loaded
         *          -> view_1.js
         *          -> view_2.js
         *
         *
         *
         */
        'templates_path'       => 'templates/',   // path to template files within views folder <root>/application/views/<template_folder>
        'template_default'     => 'main',      // filename (without extention) of the default template within the templates directory
        
        'asset_path'           => 'assets/',      // ex: assets/  -> http://www.yourwebsite.com/codeigniter/base/url/assets/
        'asset_css_folder'     => 'css/',         // is added to asset_path
        'asset_js_folder'      => 'js/',          // include trailing slash
        'asset_views_folder'   => '/',       // include trailing slash
        
        'view_group_file_name' => 'all'           // name of the group file to look for when loading view assets

    );
    
    /**
     * Codeigniter instance
     *
     * this is used to gain access to the CI native
     * $this->load->view() method.
     * 
     * @access protected
     * @var object
     */
    protected $_ci;
    
    /**
     * Carriage return string
     * this is just used for code formatting, it should not need to be changed
     *
     * @access protected
     * @var string
     */
    protected $_cr = "\r\n";
    
    /**
     * Views data container
     * 
     * You can auto-load views by adding them to this array here.
     * for example, if all of your templates have the same nav menu, you can create a 
     * view for the nav menu called for example: "my_nav_menu.php" and add it to this array
     * 
     * It is important to note though that, views are loaded and output in top-down order so any views
     * defined here will be loaded before views that are load via the $this->template->load() method
     *
     * @access protected
     * @var array
     */
    protected $_views = array();

    /**
     * Template name container var
     * 
     * There is no need to set this variable here, ever. It is set either specifically in a
     * controller via $this->template->set('template', 'some_template_file'); or automatically
     * as the default controller when the library renders output
     * 
     * @var string
     */
    protected $_template = NULL;
    
    /**
     * Page data model
     * these values get redefined on load() and are defined here to
     * initialize the values and provide an outline for your reference
     *
     * @access protected
     * @var array
     */
    protected $_page_data = array(
        'title'      => '',
        'meta'       => array(),
        'css'        => array(),
        'javascript' => array(),
        'body'       => '',
        'views'      => array()
    );
      

    /**
     * Constructor
     */  
    function __construct()
    {
        // Load the ci instance
        $this->_ci =& get_instance();
        
        // Load dependencies
        $this->_ci->load->helper('url');

        // Define template path
        if ( is_dir(APPPATH . 'views/' . $this->_config['templates_path']) )
        {
            define('TEMPLATE_PATH', APPPATH . 'views/' . $this->_config['templates_path']);
        }
        else
        {
            show_error('<strong>Template library:</strong><br />Unable to locate template directory, edit configuration in:<br />' . __FILE__ );
        }

        // Define asset path
        if ( is_dir( FCPATH . $this->_config['asset_path'] . $this->_config['asset_css_folder'] . $this->_config['asset_views_folder'] ) )
        {
            // TEMPLATE_ASSET_PATH
            // becomes a relative path, we checked that it exists absolutely in the above IF statement
            // but when it is defined, we dropped the FCPATH prefix,
            // this is done because the definition is used in URLS.
            define('TEMPLATE_ASSET_PATH', $this->_config['asset_path'] . $this->_config['asset_css_folder'] . $this->_config['asset_views_folder']);
        }
        else
        {
            show_error('<strong>ezTemplate library:</strong><br />Unable to locate asset directory, edit configuration in:<br />' . __FILE__ );
        }

        // Default template
        // will be overwritten if/when the user defines a different one
        $this->_set_default_template();
    }
    
    /*EB*/
    function get_page_data() {
        return $this->_page_data;
    }
    /**
     * Sets various template properties one at a time
     * 
     * @param  string $template_property name of the property to be set
     * @param  string $value             value of the property
     * 
     * @return mixed object on success, false on failure
     */
    function set( $template_property, $value, $ext_value = NULL )
    {
        switch ( $template_property )
        {
            // Page title
            case 'title':
                
                switch ( $value )
                {
                    case is_string($value):
                        $this->_page_data['title'] = $value;
                        break;

                    default:
                        return false;
                        break;
                }
                break;

            // Meta
            case 'meta':

                if ( is_null($ext_value) || ! is_string($ext_value) ) return false;

                switch ( $value )
                {
                    case is_array($value):
                        $this->_page_data['meta'][$value] = $ext_value;
                        break;

                    default:
                        return false;
                        break;
                }
                break;

            // CSS and javascript
            // all get treated the same
            case 'css':
            case 'js':
            case 'javascript':

                switch ( $value )
                {
                    case is_array($value):
                        $this->_page_data[$template_property] += $value;
                        break;

                    case is_string($value):
                        $this->_page_data[$template_property][] = $value;
                        break;

                    default:
                        return false;
                        break;
                }
                break;

            // Views
            case 'view':
            case 'views':

                switch ( $value )
                {
                    case is_array($value):
                        // Validate array structure
                        foreach ( $value as $view )
                        {
                            // If this array element is not a non-empty string, skip it
                            if ( ! is_string($view) ) continue;

                            // If it is a string, add it to our views
                            $this->_views[] = $view;
                        }
                        break;

                    case is_string($value):
                        $this->_views[] = $value;
                        break;

                    default:
                        return false;
                        break;
                }
                break;

            case 'template':
                if ( is_string($value) )
                {
                    if ( file_exists(TEMPLATE_PATH.$value.'.php') )
                    {
                        $this->_template = $value;
                    }
                    else
                    {
                        show_error('Unable to find template file: ' . TEMPLATE_PATH.$template_file.'.php');
                    }
                }
                else
                {
                    show_error('You must enter the name of a template.');
                }
                break;

            default:

                // If the property isn't one of the built-in 
                // properties, then just add it to the page data for use in
                // views.
                $this->_page_data[$template_property] = $value;
                break;
        }

        // If we haven't returned false, then
        // all has gone well, continue the chain.
        return $this;
    }


    function render()
    {
        // output the page html
        $this->_ci->load->view($this->_config['templates_path'].$this->_template, $this->_prepare_page_data());
    }
    

    /**
     * Primary template loading method
     *
     * In most cases this method is the only necessary template library call within
     * your controller.
     *
     * @param string    $template_file codeigniter view path
     * @param array     $views array of views to load into the template, using the codeigniter view path structure
     * @param array     $custom_data view data to be passed to the template and all sub-views
     *
     * @return bool
     */
    function load( $template_file = '', $views = array(), $custom_data = array() )
    {
        // Check if a template file was specified
        if ( ! empty($template_file) )
        {
             // Validate template file
            if ( ! file_exists(TEMPLATE_PATH.$template_file.'.php') )
            {
                show_error('Unable to find template file: ' . TEMPLATE_PATH.$template_file.'.php');
            }
            else
            {
                $this->_template = $template_file;
            }
        }

        // merge data
        if ( is_array($custom_data) )
        {
            $this->_page_data = $this->_default($this->_page_data, $custom_data);
        }
       
        // load views
        if ( is_array($views) )
        {
            // Add the loaded views to the view data container
            $this->_views += $views;
        }
        
        // output the page
        $this->render();
    }
    

    /**
     * Set the default template
     */
    private function _set_default_template()
    {
         // No template was specified
        // check if the default template exists
        if ( ! file_exists(TEMPLATE_PATH.$this->_config['template_default'].'.php') )
        {
            show_error('Unable to find default template file: ' . TEMPLATE_PATH.$template_default.'.php');
        }
        else
        {
            $this->_template = $this->_config['template_default'];
        }
    }


    /**
     * function _prepare_page_data
     *
     * @return array
     * $description prepares the page_data (in array form) for html output (string form)
     */
    private function _prepare_page_data()
    {
        // create a copy of the page data (in array form)
        $page_data = $this->_page_data;
        
        // processs head
        
        // If no title has been set, use default
        if ( empty($page_data['title']) ) 
        {
            $page_data['title'] = $this->_config['default_page_title'];
        }

        // Parse page parts
        $page_data['meta'] = $this->_prepare_meta();
        $page_data['css'] = $this->_prepare_css();
        $page_data['javascript'] = $this->_prepare_javascript();
        $page_data['body'] = $this->_prepare_body();
        
        // return processed page data
        return $page_data;
    }
    

    /**
     * function _prepare_body
     *
     * This is where the magic happens.
     *
     * @return string
     */
    private function _prepare_body()
    {
        $html = '';
        
        if ( is_array($this->_views) )
        {
            foreach ( $this->_views as $view )
            {
                // add view to body output
                $html .= $this->_ci->load->view($view, $this->_page_data, true) . $this->_cr;
            }
        }
        else
        {
            $html .= $this->_page_data;
        }
        
        return $html;
    }
    

    /**
     * function _prepare_meta
     *
     * @return string
     */
    private function _prepare_meta()
    {
        $html = '';
        
        foreach ( $this->_page_data['meta'] as $name => $content )
        {
            $html .= '<meta name="' . $name . '" content="' . $content . '" />' . $this->_cr;
        }
        
        return $html;
    }
    

    /**
     * function _prepare_css
     *
     * @return string
     */
    private function _prepare_css()
    {
        $html = '';
        
        foreach ( $this->_page_data['css'] as $stylesheet )
        {
            $html .= '<link rel="stylesheet" href="' . $stylesheet . '" />' . $this->_cr;
        }
        
        // Check if there is a css file associated with any views
        // view css files are located in:
        // <root url>/assets/css/views/<view name>.css
        foreach ( $this->_views as $view )
        {
            // Set view css file path
            $view_file_path = TEMPLATE_ASSET_PATH . $view . '.css';
            
            // Set view group css file path
            $group_file_path = TEMPLATE_ASSET_PATH . $this->_config['view_group_file_name'] . '.css';

            // Verify that the view group css file exists
            if ( file_exists( $view_file_path ))
            {
                $html.= '<link rel="stylesheet" href="' . base_url() . $view_file_path . '" />' . $this->_cr;
            }

            // Verify that the view css file exists
            if ( file_exists( $group_file_path ) )
            {
                $html .= '<link rel="stylesheet" href="' . base_url() . $group_file_path . '" />' . $this->_cr;
            }
        }
        

        // Output
        return $html;
    }

    
    /**
     * function _prepare_javascript
     *
     * @return string
     */
    private function _prepare_javascript()
    {
        $html = '';
        
        foreach ( $this->_page_data['javascript'] as $script )
        {
            $html .= '<script src="' . $script . '"></script>' . $this->_cr;
        }
        
        // check if there is a js file associated with any views
        // view js files are located in:
        // <root url>/assets/js/views/<view name>.js
        foreach ( $this->_views as $view )
        {
            // Build path to view js file
            $view_js_file_path = $this->_config['asset_path'] . $this->_config['asset_js_folder'] . $this->_config['asset_views_folder'] . $view . '.js';
            
            // Build path to view group js file
            $group_js_file_path = dirname($view_js_file_path) . '/' . $this->_config['view_group_file_name'] . '.js';
            
            // Verify that the view group js file exists
            if ( file_exists( FCPATH . $group_js_file_path ) )
            {
                $html .= '<script src="' . base_url() . $group_js_file_path . '"></script>' . $this->_cr;
            }

            // Verify that the view js file exists
            if ( file_exists( FCPATH . $view_js_file_path ) )
            {
                $html .= '<script src="' . base_url() . $view_js_file_path . '"></script>' . $this->_cr;
            }

        }
        
        // Output
        return $html;
    }

    /**
     * _required method returns false if the $data array does not contain all of the keys assigned by the $required array.
     *
     * @param array $required
     * @param array $data
     *
     * @return bool
     */
    private function _required($required, $data)
    {
        foreach ( $required as $field )
        {
            if ( ! isset($data[$field]) )
            {
                return false; 
            }
        }
        return true;
    }
     

    /**
     * _default method combines the options array with a set of defaults giving the values in the options array priority.
     *
     * @param array $defaults
     * @param array $options
     *
     * @return array
     */
    private function _default($defaults, $options)
    {
        foreach( $options as $key => $value )
        {
            if ( is_array($value) and array_key_exists($key, $defaults) )
            {
                $defaults[$key] = $this->_default($defaults[$key], $options[$key]);
            }
            else
            {
                $defaults[$key] = $value;
            }
        }
        
        return $defaults;
    }

    private function _file_exists( $file )
    {
        if ( file_exists( $file ) )
        {
            return true;
        }
        else
        {
            show_error('ezTemplate Library:<br />Unable to find file: ' . $file);
        }
    }
    
}
