<?php  if (!defined('BASEPATH')) exit('No direct script access allowed');	
	
/**
 * Assets Library
 *
 * @author 		Boris Strahija <boris@creolab.hr>
 * @copyright 	Copyright (c) 2012, Boris Strahija, http://creolab.hr
 * @version 	1.5.1
 */

define('ASSETS_VERSION', '1.5.1');


class Assets {
	
	protected static $_ci;
	protected static $_less;
	protected static $_sass;
	protected static $_cache_info;
	protected static $_cache_info_file  = 'info.cache';
	protected static $_enable_benchmark = false;


	// All the assets go in here
	private static $_assets = array('js' => array(), 'css' => array());

	
	// Prefixes and groups
	public static $group;
	public static $default_group = array(
		'css' => '__def_css__',
		'js'  => '__def_js__',
	);


	// Paths and folders
	public static $assets_dir, $base_path,  $base_url;
	public static $js_dir,     $js_path,    $js_url;
	public static $css_dir,    $css_path,   $css_url;
	public static $img_dir,    $img_path,   $img_url;
	public static $cache_dir,  $cache_path, $cache_url;


	// Config
	public static $minify               = false; // Minify all
	public static $minify_js            = true;
	public static $minify_css           = true;
	public static $pack_js              = true;
	public static $auto_clear_cache     = false; // Automaticly clear all cache before creating new cache files
	public static $auto_clear_css_cache = false; // Or clear just cached CSS files
	public static $auto_clear_js_cache  = false; // Or just cached JS files
	public static $html5                = true;  // Use HTML5 tags
	public static $enable_less          = true;  // Enable LESS CSS parser
	public static $enable_sass          = true;  // Enable SASS CSS parser
	public static $enable_coffeescript  = true;  // Enable CoffeeScript parser
	public static $freeze               = false; // Disable all processing once the assets are cached (for production)

	// CssMin config
	public static $cssmin_plugins       = array();
	public static $cssmin_filters       = array();
	
	// Flags
	public static  $auto_cleared_css_cache = false;
	public static  $auto_cleared_js_cache  = false;
	private static $_cssmin_loaded         = false;
	private static $_jsmin_loaded          = false;
	private static $_jspacker_loaded       = false;
	private static $_less_loaded           = false;
	private static $_sass_loaded           = false;
	private static $_coffeescript_loaded   = false;

	
	/* ------------------------------------------------------------------------------------------ */
	
	/**
	 * Display CSS tags
	 * @param  array  $files
	 * @return string
	 */
	public static function css($files = null, $attributes = null)
	{
		self::init();

		// Start benchmark
		if (self::$_enable_benchmark) self::$_ci->benchmark->mark("Assets::css()_start");

		self::$group = null;
		// Add to process container
		self::_add_assets($files, null, 'css', $attributes);

		// And process it
		if (self::$_cache_info and self::$freeze) {}
		else                                      { self::_process('css'); }

		// End benchmark
		if (self::$_enable_benchmark) self::$_ci->benchmark->mark("Assets::css()_end");

		// Tags
		return self::_generate_tags('css');
	}

	
	/* ------------------------------------------------------------------------------------------ */
	
	/**
	 * Display a group of CSS tags
	 * @param  string $group
	 * @param  array  $files
	 * @return string
	 */
	public static function css_group($group = null, $files = null, $attributes = null)
	{
		self::$group = $group;

		self::init();

		// Start benchmark
		if (self::$_enable_benchmark) self::$_ci->benchmark->mark("Assets::css_group(".$group.")_start");

		// Add to process container
		self::_add_assets($files, $group, 'css', $attributes);

		// And process it
		if (self::$_cache_info and self::$freeze) {}
		else                                      { self::_process('css', $group); }

		// End benchmark
		if (self::$_enable_benchmark) self::$_ci->benchmark->mark("Assets::css_group(".$group.")_end");

		// Tags
		return self::_generate_tags('css');
	}

	
	/* ------------------------------------------------------------------------------------------ */
	
	/**
	 * Display JS tags
	 * @param  array  $files
	 * @return string
	 */
	public static function js($files = null)
	{
		self::$group = null;

		self::init();

		// Start benchmark
		if (self::$_enable_benchmark) self::$_ci->benchmark->mark("Assets::js()_start");

		// Add to process container
		self::_add_assets($files, null, 'js');

		// And process it
		if (self::$_cache_info and self::$freeze) {}
		else                                      { self::_process('js'); }

		// End benchmark
		if (self::$_enable_benchmark) self::$_ci->benchmark->mark("Assets::js()_end");

		// Tags
		return self::_generate_tags('js');
	}

	
	/* ------------------------------------------------------------------------------------------ */
	
	/**
	 * Display a group of JS tags
	 * @param  string   $group
	 * @param  array    $files
	 * @param  boolean  $pack
	 * @return string
	 */
	public static function js_group($group = null, $files = null, $pack = false)
	{
		self::$group = $group;

		self::init();

		// Enable packer for this group
		if ($pack) self::$pack_js = true;

		// Start benchmark
		if (self::$_enable_benchmark) self::$_ci->benchmark->mark("Assets::js_group(".$group.")_start");

		// Add to process container
		self::_add_assets($files, $group, 'js');

		// And process it
		if (self::$_cache_info and self::$freeze) {}
		else                                      { self::_process('js', $group); }

		// End benchmark
		if (self::$_enable_benchmark) self::$_ci->benchmark->mark("Assets::js_group(".$group.")_end");

		// Tags
		return self::_generate_tags('js');
	}

	
	/* ------------------------------------------------------------------------------------------ */
	
	/**
	 * Simply return html tags with CDN paths
	 * @param  array $assets
	 * @return string
	 */
	public static function cdn($assets = null, $echo = true)
	{
		if ($assets)
		{
			// Start benchmark
			if (self::$_enable_benchmark) self::$_ci->benchmark->mark("Assets::cdn()_start");

			self::init();

			$html = '';

			foreach ($assets as $asset)
			{
				// Get asset from config
				if (is_array($asset))
				{
					$cdn_asset = self::$_ci->config->item($asset[0], 'assets_cdn');
					if ($cdn_asset) $version = $asset[1];
				}
				else
				{
					$cdn_asset = self::$_ci->config->item($asset, 'assets_cdn');
					if ($cdn_asset) $version = $cdn_asset['default_version'];
				}

				// Check if asset ok
				if (isset($cdn_asset) and isset($version) and $cdn_asset and $version)
				{
					$cdn_path = str_replace('%version%', $version, $cdn_asset['path']);
					$html .= self::tag($cdn_path, 'js', false);
				}
			}

			// End benchmark
			if (self::$_enable_benchmark) self::$_ci->benchmark->mark("Assets::cdn()_end");

			if ($echo) echo $html;
			else       return $html;
		}
	}

	
	/* ------------------------------------------------------------------------------------------ */
	
	/**
	 * Place string in IE conditional comments
	 * @param  string $condition
	 * @param  string $string
	 */
	public static function conditional($condition = null, $string = null)
	{
		if ($condition and $string)
		{
			// Start benchmark
			if (self::$_enable_benchmark) self::$_ci->benchmark->mark("Assets::conditional()_start");

			echo '<!--[if '.$condition.']>'."\n";

			if (is_array($string))
			{
				foreach ($string as $str)
				{
					echo $str;
				}
			}
			else
			{
				echo $string;	
			}
			
			echo '<![endif]-->';
			
			// End benchmark
			if (self::$_enable_benchmark) self::$_ci->benchmark->mark("Assets::conditional()_end");
		}
	}



	
	/* ------------------------------------------------------------------------------------------ */
	
	/**
	 * Add assets to list for processing
	 * @param array  $assets
	 * @param string $group
	 * @param string $type
	 */
	private static function _add_assets($assets = null, $group = null, $type = null, $attributes = null)
	{
		// Start benchmark
		if (self::$_enable_benchmark) self::$_ci->benchmark->mark("Assets::add-assets()_start");

		if ( ! $group) $group = self::$default_group[$type];

		// Set last modified time to 0
		self::$_assets[$type][$group]['last_modified']       = 0;
		self::$_assets[$type][$group]['last_modified_human'] = "0000-00-00 00:00:00";

		// Attributes
		self::$_assets[$type][$group]['attributes'] = $attributes;

		// Prepare some vars
		self::$_assets[$type][$group]['combined']  = '';
		self::$_assets[$type][$group]['output']    = '';

		// List of active files in group
		if (isset(self::$_cache_info->{$type}->{$group}->file_list)) self::$_assets[$type][$group]['file_list'] = self::$_cache_info->{$type}->{$group}->file_list;
		else                                                         self::$_assets[$type][$group]['file_list'] = array();
		
		// Add assets to list
		foreach ($assets as $asset)
		{
			// Add file
			$tmp_asset = array('file' => $asset);

			// Determine path
			if     ($type === 'css') $file_path = reduce_double_slashes(self::$css_path.'/'.$asset);
			elseif ($type === 'js')  $file_path = reduce_double_slashes(self::$js_path.'/'.$asset);
			$tmp_asset['path'] = $file_path;

			// Modified time
			$tmp_asset['modified'] = filemtime(realpath($file_path));
			if ($tmp_asset['modified'] > self::$_assets[$type][$group]['last_modified'])
			{
				self::$_assets[$type][$group]['last_modified']       = $tmp_asset['modified'];
				self::$_assets[$type][$group]['last_modified_human'] = date('Y-m-d H:i:s', (int) $tmp_asset['modified']);
			}

			// Add to container
			self::$_assets[$type][$group]['src'][]       = $tmp_asset;
			self::$_assets[$type][$group]['file_list']   = (array) self::$_assets[$type][$group]['file_list'];
			self::$_assets[$type][$group]['file_list'][] = $tmp_asset['file'];
		}

		self::$_assets[$type][$group]['file_list'] = array_unique(self::$_assets[$type][$group]['file_list']);
		
		// End benchmark
		if (self::$_enable_benchmark) self::$_ci->benchmark->mark("Assets::add-assets()_end");
	}

	
	/* ------------------------------------------------------------------------------------------ */
	
	/**
	 * Process assets in group
	 * @param  string $type
	 * @param  string $group
	 */
	private static function _process($type = null, $group = null)
	{
		if ( ! $group) $group = self::$default_group[$type];

		// Start benchmark
		if (self::$_enable_benchmark) self::$_ci->benchmark->mark("Assets::process(".$type.", ".$group.")_start");

		// Last modified date
		if ( ! isset(self::$_cache_info->{$type}->{$group})) $last_modified = 0;
		else                                                 $last_modified = self::last_modified($type, $group);

		// Create a cache filename
		if ($group !== self::$default_group[$type]) $file_prefix = $group.'.';
		else                                        $file_prefix = '';
		$file_name = self::$_assets[$type][$group]['cache_file_name'] = $file_prefix.$last_modified.".".$type;

		// And check if we should process it
		$file_exists = file_exists(self::$cache_path.'/'.$file_name);

		if ( ! $file_exists or ! $last_modified)
		{
			// Get list of assets
			$assets = self::$_assets[$type][$group];

			// Loop through all original assets
			foreach ($assets['src'] as $key=>$asset)
			{
				// Get file contents
				$contents = read_file($asset['path']);

				// Get file info
				$asset['info'] = pathinfo($asset['file']);

				// Process imports in CSS files
				if ($type === 'css')
				{
					$import_result = self::_process_imports($contents, null, $asset['file']);
					$contents      = $import_result['contents'];

					list ( $asset['file_name'], $asset['file_ext']) = explode( '.', $asset['file'] );

					// LESS
					if (self::$enable_less and ! self::$freeze and $asset['file_ext'] == 'less')
					{
						self::_init_less();
						$contents = self::$_less->parse($contents);
					}
					// SASS
					if (self::$enable_sass and ! self::$freeze and ( $asset['file_ext'] == 'sass' or $asset['file_ext'] == 'scss' ))
					{
						self::_init_sass();
						$contents = self::$_sass->toCss($contents);
					}

					// Update last modified time
					if ($import_result['last_modified']  > self::$_assets[$type][$group]['last_modified'])
					{
						self::$_assets[$type][$group]['last_modified']       = $import_result['last_modified'];
						self::$_assets[$type][$group]['last_modified_human'] = date('Y-m-d H:i:s', $import_result['last_modified']);
					}

					// Update imported files
					self::$_assets[$type][$group]['file_list'] = array_unique(array_merge(self::$_assets[$type][$group]['file_list'], $import_result['file_list']));
				}
				elseif ($type === 'js')
				{
					// CoffeeScript parser
					if (self::$enable_coffeescript and $asset['info']['extension'] === 'coffee')
					{
						if ( ! self::$_coffeescript_loaded) self::_init_coffeescript();

						CoffeeScript\Init::load();
						$contents = CoffeeScript\Compiler::compile($contents);
					}
					elseif ( ! self::$enable_coffeescript and $asset['info']['extension'] === 'coffee')
					{
						$contents = '';
					}
				}

				// Or add to combine var (if we're combining)
				self::$_assets[$type][$group]['combined'] .= "\n".$contents;
			}

			// New file name
			$file_name = self::$_assets[$type][$group]['cache_file_name'] = $file_prefix.self::$_assets[$type][$group]['last_modified'].".".$type;

			// Now minify if we choose so
			if ($type === 'css')
			{
				$output = self::$_assets[$type][$group]['combined'];

				// Minify CSS
				if (self::$minify_css and ! self::$freeze)
				{
					self::_init_cssmin();

					$output = trim(CSSMin::minify($output, self::$cssmin_filters, self::$cssmin_plugins));
				}

				// Add to output
				self::$_assets[$type][$group]['output'] = $output;
				unset($output);
			}
			elseif ($type === 'js')
			{
				$output = self::$_assets[$type][$group]['combined'];

				// Minify JS
				if (self::$minify_js)
				{
					self::_init_jsmin();
					$output = trim(JSMin::minify($output));
				}

				// Pack JS
				if (self::$pack_js)
				{
					self::_init_jspacker();
					$packer = new JavaScriptPacker($output, 'Normal', true, false);
					$output = trim($packer->pack());
				}

				// Add to output
				self::$_assets[$type][$group]['output'] = $output;
				unset($output);
			}

			// Once it's processed remove vars we dont need
			unset(self::$_assets[$type][$group]['combined']);

			// And finnaly we create the actual cached files
			self::_cache_assets($type);
		}

		// Update cache info
		self::_update_cache_info();

		// End benchmark
		if (self::$_enable_benchmark) self::$_ci->benchmark->mark("Assets::process(".$type.", ".$group.")_end");
	}

	
	/* ------------------------------------------------------------------------------------------ */
	
	/**
	 * Process all CSS @imports and parse URL's
	 * @param  string $contents
	 * @param  string $import_dir
	 * @return array
	 */
	public static function _process_imports($contents = '', $import_dir = null, $asset_location = null)
	{
		// Start benchmark
		if (self::$_enable_benchmark) self::$_ci->benchmark->mark("Assets::process_imports()_start");

		$last_modified = 0;

		// Process URL's
		$contents = self::_process_urls($contents, $asset_location);
		
		// Find @import calls
		$imports_exist = preg_match_all("/@import\s+(.*);/", $contents, $imports);

		// List of import files
		$import_file_list = array();

		// If @import exist add them
		if ($imports_exist and is_array($imports) and isset($imports[1]) and is_array($imports[1]))
		{
			foreach ($imports[1] as $import_key=>$import)
			{
				$import_file = trim($import, " \"");

				// Is a directory set
				if ($import_dir) $import_file = $import_dir.'/'.$import_file;

				// Add to list
				$import_file_list[] = $import_file;

				// Path info
				$import_info     = pathinfo($import_file);
				$import_info_dir = ($import_info['dirname'] and $import_info['dirname'] !== ".") ? $import_info['dirname'] : null;
				
				// Path to file
				$import_file_path = reduce_double_slashes(self::$css_path.'/'.$import_file);

				// Get modified date
				$import_modified = filemtime(realpath($import_file_path));
				if ($import_modified > $last_modified) $last_modified = $import_modified;

				// Get contents
				$import_contents = read_file($import_file_path);

				// Nested imports?
				$import_result = self::_process_imports($import_contents, $import_info_dir, $import_file);
				if ($import_result)
				{
					$import_contents = $import_result['contents'];

					// Add to list
					if ($import_result['file_list']) $import_file_list = array_unique(array_merge($import_file_list, $import_result['file_list']));

					// Check modified time
					if ($import_result['last_modified'] > $last_modified) $last_modified = $import_result['last_modified'];
				}

				// And add to main contents
				$contents = str_replace($imports[0][$import_key], $import_contents, $contents);
			}
		}

		// End benchmark
		if (self::$_enable_benchmark) self::$_ci->benchmark->mark("Assets::process_imports()_end");

		return array(
			'last_modified' => $last_modified,
			'contents'      => $contents,
			'file_list'     => $import_file_list,
		);
	}

	
	/* ------------------------------------------------------------------------------------------ */
	
	/**
	 * Find all URL's and process em accordingly
	 * @param  string $contents
	 * @param  string $asset_location
	 * @return string
	 */
	private static function _process_urls($contents = null, $asset_location = null)
	{
		// Parse URL's
		preg_match_all("/url\((\"|'|)?((.*\.(png|gif|jpg))(\"|'|))\)/Ui", $contents, $matches);

		if (isset($matches[2]) and $matches[2])
		{
			// Unique URL's
			if (is_array($matches[2])) $matches[2] = array_unique($matches[2]);

			// Set location for assets
			if ($asset_location) $asset_location = dirname($asset_location).'/';
			else                 $asset_location = '';

			foreach ($matches[2] as $match)
			{
				$href     = trim($match, " '\"");
				$new_href = '';
				$base     = self::$css_url.'/'.$asset_location;

				if ( ! $href) $new_href = $base;

				// href="http://..." ==> href isn't relative
				$rel_parsed = parse_url($href);
				if (array_key_exists('scheme', $rel_parsed)) $new_href = $href;

				// Add an extra character so that, if it ends in a /, we don't lose the last piece.
				$base_parsed = parse_url("$base ");
				// if it's just server.com and no path, then put a / there.
				if (!array_key_exists('path', $base_parsed)) $base_parsed = parse_url("$base/ ");

				if ( ! isset($rel_parsed['host']) or (isset($rel_parsed['host']) and isset($base_parsed['host']) and ($rel_parsed['host'] == $base_parsed['host'])))
				{
					// href="/ ==> throw away current path.
					if ($href{0} === "/") $path = $href;
					else                  $path = dirname($base_parsed['path']) . "/$href";

					// bla/./bloo ==> bla/bloo
					$path = preg_replace('~/\./~', '/', $path);

					// resolve /../
					// loop through all the parts, popping whenever there's a .., pushing otherwise.
					$parts = array();
					foreach (explode('/', preg_replace('~/+~', '/', $path)) as $part)
					{
						if ($part === "..")  array_pop($parts);
						elseif ($part != "") $parts[] = $part;
					}

					$new_href = ((array_key_exists('scheme', $base_parsed)) ? $base_parsed['scheme'] . '://' . $base_parsed['host'] : "") . (array_key_exists('port', $base_parsed) ? ":".$base_parsed['port'] : '') . "/" . implode("/", $parts);
					if (substr($path, 0, 2) == '//' and substr($new_href, 0, 2) != '//' and substr($new_href, 0, 1) == '/') $new_href = '/' . $new_href;
				}
				$contents = str_replace($href, $new_href, $contents);
			}
		}

		return $contents;
	}

	
	/* ------------------------------------------------------------------------------------------ */
	
	/**
	 * Find last modified time in list of files
	 * @param  string $type  - css or js (can be null)
	 * @param  string $group - name of group so we can fetch the file list
	 * @return integer
	 */
	public static function last_modified($type = null, $group = null)
	{
		$last_modified = 0;

		// Start benchmark
		if (self::$_enable_benchmark) self::$_ci->benchmark->mark("Assets::last_modified(".$type.", ".$group.")_start");

		// Get file path
		if ($type === 'css')    $path = self::$css_path;
		elseif ($type === 'js') $path = self::$js_path;

		// Get files
		$files = array();
		if ($type and ! $group or ($type and ! isset(self::$_cache_info->{$type}->{$group})))
		{
			$files = directory_map($path, 1);
		}
		elseif ($type and $group and isset(self::$_cache_info->{$type}->{$group}->file_list))
		{
			$files = self::$_cache_info->{$type}->{$group}->file_list;
		}

		foreach ($files as $file)
		{
			if ( ! is_array($file))
			{
				$file_modified = (int) @filemtime($path."/".$file);
				if ($file_modified > $last_modified) $last_modified = $file_modified;
			}
			else
			{
				$subfiles = directory_map($path.'/'.$file, 1);

				foreach ($subfiles as $subfile)
				{
					if ( ! is_array($subfile))
					{
						$subfile_modified = filemtime($path."/".$file);
						if ($subfile_modified > $last_modified) $last_modified = $subfile_modified;
					}
				}
			}
		}

		// End benchmark
		if (self::$_enable_benchmark) self::$_ci->benchmark->mark("Assets::last_modified(".$type.", ".$group.")_end");

		return $last_modified;
	}

	
	/* ------------------------------------------------------------------------------------------ */
	
	/**
	 * Cache all assets of this type
	 * @param  string $type
	 */
	private static function _cache_assets($type = null)
	{
		// Start benchmark
		if (self::$_enable_benchmark) self::$_ci->benchmark->mark("Assets::cache_assets()_start");

		// Auto clear cache directory?
		if ($type == 'css' and (self::$auto_clear_cache or self::$auto_clear_css_cache) and ! self::$auto_cleared_css_cache)
		{
			self::clear_css_cache(self::$group, false);
			self::$auto_cleared_css_cache = true;
		}
		
		if ($type == 'js' and (self::$auto_clear_cache or self::$auto_clear_js_cache) and ! self::$auto_cleared_js_cache)
		{
			self::clear_js_cache(self::$group, false);
			self::$auto_cleared_js_cache = true;
		}

		// Loop through groups
		foreach (self::$_assets[$type] as $key=>$assets_group)
		{
			$file_path = self::$cache_path."/".$assets_group['cache_file_name'];
			
			write_file($file_path, $assets_group['output']);

			// Remove contents after caching
			unset(self::$_assets[$type][$key]['output']);
		}

		// End benchmark
		if (self::$_enable_benchmark) self::$_ci->benchmark->mark("Assets::cache_assets()_end");
	}

	
	/* ------------------------------------------------------------------------------------------ */
	
	/**
	 * Get/parse contents of cache info file
	 * @return object
	 */
	private static function _get_cache_info()
	{
		// Start benchmark
		if (self::$_enable_benchmark) self::$_ci->benchmark->mark("Assets::_get_cache_info()_start");

		self::$_cache_info = @json_decode(read_file(self::$cache_path.'/'.self::$_cache_info_file));
		
		// End benchmark
		if (self::$_enable_benchmark) self::$_ci->benchmark->mark("Assets::_get_cache_info()_end");
	}

	
	/* ------------------------------------------------------------------------------------------ */
	
	/**
	 * Displays cahce info
	 * @return string
	 */
	public static function echo_cache_info()
	{
		echo '<pre>'; print_r(self::$_cache_info); echo '</pre>';
	}

	
	/* ------------------------------------------------------------------------------------------ */
	
	/**
	 * Update cache info file
	 */
	public static function _update_cache_info()
	{
		// Start benchmark
		if (self::$_enable_benchmark) self::$_ci->benchmark->mark("Assets::_update_cache_info()_start");

		// We need to update the info file
		foreach (self::$_assets as $key_type=>$assets_type)
		{
			foreach ($assets_type as $key_group=>$assets_group)
			{
				// Remove output
				unset(self::$_assets[$key_type][$key_group]['output']);

				// Create empty placeholders if needed
				if (self::$_cache_info === null)                             self::$_cache_info = new stdClass;
				if ( ! isset(self::$_cache_info->{$key_type}))               self::$_cache_info->{$key_type} = new stdClass;
				if ( ! isset(self::$_cache_info->{$key_type}->{$key_group})) self::$_cache_info->{$key_type}->{$key_group} = new stdClass;

				// Add group to info
				self::$_cache_info->{$key_type}->{$key_group}->cache_file_name = $assets_group['cache_file_name'];
				self::$_cache_info->{$key_type}->{$key_group}->last_modified   = $assets_group['last_modified'];
				self::$_cache_info->{$key_type}->{$key_group}->file_list       = $assets_group['file_list'];
			}
		}

		write_file(self::$cache_path.'/'.self::$_cache_info_file, json_encode(self::$_cache_info));

		// End benchmark
		if (self::$_enable_benchmark) self::$_ci->benchmark->mark("Assets::_update_cache_info()_end");
	}

	
	/* ------------------------------------------------------------------------------------------ */
	
	/**
	 * Generate and output HTML tags
	 * @param  string  $type
	 * @param  boolean $echo
	 * @return string
	 */
	private static function _generate_tags($type = null, $echo = true)
	{
		if ( ! self::$group) $group = self::$default_group[$type];
		else                 $group = self::$group;

		if (isset(self::$_assets[$type][$group]))
		{
			// Get list of assets
			$assets_group = self::$_assets[$type][$group];

			// Default attributes
			$attributes = '';
			if ($type === 'css')
			{
				// Default attributes
				if ( ! isset($assets_group['attributes']['rel']))     $assets_group['attributes']['rel'] = 'stylesheet';
			}
			elseif ($type === 'js')
			{
				// Default attributes
				if ( ! isset($assets_group['attributes']['charset']) and ! self::$html5) $assets_group['attributes']['charset'] = 'utf-8';
			}

			// Custom attributes
			if (isset($assets_group['attributes']) and $assets_group['attributes'])
			{
				foreach ($assets_group['attributes'] as $att=>$val)
				{
					$attributes .= ' '.$att.'="'.$val.'"';
				}
			}

			// File name
			if ( ! isset($assets_group['cache_file_name'])) $assets_group['cache_file_name'] = self::$_cache_info->{$type}->{$group}->cache_file_name;

			// File and tag
			$file = self::$cache_url.'/'.$assets_group['cache_file_name'];
			$tag  = self::tag($file, $type, false, $attributes);

			if ($echo) echo   $tag;
			else       return $tag;
		}
	}

	
	/* ------------------------------------------------------------------------------------------ */
	
	/**
	 * Display a HTML tag
	 * @param  string  $file
	 * @param  string  $type
	 * @param  boolean $echo
	 * @return string
	 */
	public static function tag($file = null, $type = null, $echo = true, $attributes = '')
	{
		if ($type === 'css')
		{
			if (self::$html5) $tag = '<link href="'.$file.'"'.$attributes.'>'.PHP_EOL;
			else              $tag = '<link type="text/css" href="'.$file.'"'.$attributes.' />'.PHP_EOL;
		}
		elseif ($type === 'js')
		{
			if (self::$html5) $tag = '<script src="'.$file.'"'.$attributes.'></script>'.PHP_EOL;
			else              $tag = '<script src="'.$file.'" type="text/javascript"'.$attributes.'></script>'.PHP_EOL;
		}

		if ($echo) echo   $tag;
		else       return $tag;
	}

	
	/* ------------------------------------------------------------------------------------------ */
	
	/**
	 * Delete cached files
	 * @param  string $type
	 * @param  string $group
	 */
	public static function clear_cache($type = null, $group = null, $init = true)
	{
		if ($init) self::init();

		// Get all cached files
		$files = directory_map(self::$cache_path, 1);

		if ($files)
		{
			foreach ($files as $file)
			{
				if ($group)
				{

				}
				else
				{
					$file_path = reduce_double_slashes(self::$cache_path.'/'.$file);
					$file_info = pathinfo($file_path);

					if ($type === 'css')
					{
						if (isset($file_info['extension']) and strtolower($file_info['extension']) === 'css') unlink($file_path);
					}
					elseif ($type === 'js')
					{
						if (isset($file_info['extension']) and strtolower($file_info['extension']) === 'js') unlink($file_path);
					}
					else
					{
						if (isset($file_info['extension']) and (strtolower($file_info['extension']) === 'css' or strtolower($file_info['extension']) === 'js')) unlink($file_path);
						if (isset($file_info['extension']) and strtolower($file_info['extension']) === 'cache') unlink($file_path);
					}
				}
			}
		}

		self::_get_cache_info();
	}


	/* ------------------------------------------------------------------------------------------ */
	
	/**
	 * Delete cached CSS files
	 * @param  string $asset_file
	 */
	public static function clear_css_cache($group = null, $init = true)
	{
		return self::clear_cache('css', $group, $init);
	}
	
	
	/* ------------------------------------------------------------------------------------------ */
	
	/**
	 * Delete cached JS files
	 * @param  string $asset_file
	 */
	public static function clear_js_cache($group = null, $init = true)
	{
		return self::clear_cache('js', $group, $init);
	}

	
	/* ------------------------------------------------------------------------------------------ */
	
	public static function clear_assets()
	{
		self::$_assets = array('js' => array(), 'css' => array());
	}

	
	
	/* ------------------------------------------------------------------------------------------ */
	/* !/===> URL / Image helpers */
	/* ------------------------------------------------------------------------------------------ */


	/**
	 * Return url to asset
	 * @param  string $path
	 */
	public static function url($path = null)
	{
		return self::$base_url.'/'.$path;
	}

	
	/* ------------------------------------------------------------------------------------------ */
	
	/**
	 * Return url to image, or event an entire tag
	 * @param  string $path
	 */
	public static function img($path = null, $tag = false, $properties = null)
	{
		self::init();

		$img_path = self::$img_url.'/'.$path;

		// Properties
		if ($properties) $properties['src'] = $img_path;

		// Tag?
		if ($tag)
		{
			$img = img($properties, false);

			// Remove site_url if base_url is set as auto protocol
			if (stripos(self::$base_url, '//') === 0)
			{
				$img = str_replace('src="'.base_url(), 'src="', $img);
			}

			return $img;
		}
		else
		{
			return $img_path;
		}
	}

	
	
	/* ------------------------------------------------------------------------------------------ */
	/* !/===> Initialization and configuration */
	/* ------------------------------------------------------------------------------------------ */
	
	
	/**
	 * Library initialization
	 * @param  array $cfg
	 */
	public static function init($cfg = null)
	{
		// Clean previous assets if needed
		self::clear_assets();

		if ( ! self::$_ci)
		{
			self::$_ci =& get_instance();

			// Start benchmark
			if (self::$_enable_benchmark) self::$_ci->benchmark->mark("Assets::init()_start");

			// Add config to library
			if ($cfg)
			{
				self::configure(array_merge($cfg), config_item('assets'));
			}
			else
			{
				self::configure(config_item('assets'));
			}

			// Get cache info
			self::_get_cache_info();
		}

		// End benchmark
		if (self::$_enable_benchmark) self::$_ci->benchmark->mark("Assets::init()_end");
	}

	
	/* ------------------------------------------------------------------------------------------ */
	
	private static function _init_cssmin()
	{
		if (self::$minify_css and ! self::$freeze and ! self::$_cssmin_loaded)
		{
			// Start benchmark
			if (self::$_enable_benchmark) self::$_ci->benchmark->mark("Assets::init_cssmin()_start");

			// Load
			if (defined('SPARKPATH')) include(reduce_double_slashes(SPARKPATH.'assets/'.ASSETS_VERSION.'/libraries/cssmin/cssmin.php'));
			else                      include(reduce_double_slashes(APPPATH.'/third_party/assets/cssmin/cssmin.php'));
			self::$_cssmin_loaded = true;

			// Set current dir for css min
			if ( ! isset(self::$cssmin_filters['currentDir'])) self::$cssmin_filters['currentDir'] = str_replace(site_url(), '', self::$base_url).'/';

			// End benchmark
			if (self::$_enable_benchmark) self::$_ci->benchmark->mark("Assets::init_cssmin()_end");
		}
	}

	
	/* ------------------------------------------------------------------------------------------ */
	
	private static function _init_jsmin()
	{
		if (self::$minify_js and ! self::$freeze and ! self::$_jsmin_loaded)
		{
			// Start benchmark
			if (self::$_enable_benchmark) self::$_ci->benchmark->mark("Assets::init_jsmin()_start");

			// Load
			if (defined('SPARKPATH')) include(reduce_double_slashes(SPARKPATH.'assets/'.ASSETS_VERSION.'/libraries/jsmin/jsmin.php'));
			else                      include(reduce_double_slashes(APPPATH.'/third_party/assets/jsmin/jsmin.php'));
			self::$_jsmin_loaded = true;

			// End benchmark
			if (self::$_enable_benchmark) self::$_ci->benchmark->mark("Assets::init_jsmin()_end");
		}
	}

	
	/* ------------------------------------------------------------------------------------------ */
	
	private static function _init_jspacker()
	{
		if (self::$pack_js and ! self::$freeze and ! self::$_jspacker_loaded)
		{
			// Start benchmark
			if (self::$_enable_benchmark) self::$_ci->benchmark->mark("Assets::init_jspacker()_start");

			// Load
			if (defined('SPARKPATH')) include(reduce_double_slashes(SPARKPATH.'assets/'.ASSETS_VERSION.'/libraries/jspacker/jspacker.php'));
			else                      include(reduce_double_slashes(APPPATH.'/third_party/assets/jspacker/jspacker.php'));
			self::$_jspacker_loaded = true;

			// End benchmark
			if (self::$_enable_benchmark) self::$_ci->benchmark->mark("Assets::init_jspacker()_end");
		}
	}

	
	/* ------------------------------------------------------------------------------------------ */
	
	private static function _init_less()
	{
		if ( ! self::$freeze and ! self::$_less_loaded)
		{
			// Start benchmark
			if (self::$_enable_benchmark) self::$_ci->benchmark->mark("Assets::init_less()_start");

			// Load LessPHP
			if (defined('SPARKPATH')) include(reduce_double_slashes(SPARKPATH.'assets/'.ASSETS_VERSION.'/libraries/lessc/lessc.php'));
			else                      include(reduce_double_slashes(APPPATH.'/third_party/assets/lessc/lessc.php'));
			
			// Initialize
			self::$_less = new lessc();
			self::$_less->importDir = self::$css_path.'/';
			self::$_less_loaded = true;

			// End benchmark
			if (self::$_enable_benchmark) self::$_ci->benchmark->mark("Assets::init_less()_end");
		}
	}

	
	/* ------------------------------------------------------------------------------------------ */
	
	private static function _init_sass()
	{
		if ( ! self::$freeze and ! self::$_sass_loaded)
		{
			// Start benchmark
			if (self::$_enable_benchmark) self::$_ci->benchmark->mark("Assets::init_sass()_start");

			// Load PHPSass
			if (defined('SPARKPATH')) include(reduce_double_slashes(SPARKPATH.'assets/'.ASSETS_VERSION.'/libraries/phpsass/SassParser.php'));
			else                      include(reduce_double_slashes(APPPATH.'/third_party/assets/phpsass/SassParser.php'));
			
			$options = array(
				'style' => 'expanded',
				'cache' => FALSE,
				'syntax' => self::$base_url,
				'debug' => FALSE,
				'callbacks' => array(
					'warn' => 'warn',
					'debug' => 'debug'
				),
			);

			// Initialize
			self::$_sass = new SassParser( $options );
			self::$_sass_loaded = true;

			// End benchmark
			if (self::$_enable_benchmark) self::$_ci->benchmark->mark("Assets::init_sass()_end");
		}
	}

	
	/* ------------------------------------------------------------------------------------------ */
	
	private static function _init_coffeescript()
	{
		if ( ! self::$freeze and ! self::$_coffeescript_loaded)
		{
			// Start benchmark
			if (self::$_enable_benchmark) self::$_ci->benchmark->mark("Assets::init_coffeescript()_start");

			// Load classes
			if (defined('SPARKPATH')) include(reduce_double_slashes(SPARKPATH.'assets/'.ASSETS_VERSION.'/libraries/coffeescript/Init.php'));
			else                      include(reduce_double_slashes(APPPATH.'/third_party/assets/coffeescript/coffeescript.php'));

			// Initialize
			CoffeeScript\Init::load();
			self::$_coffeescript_loaded = true;

			// End benchmark
			if (self::$_enable_benchmark) self::$_ci->benchmark->mark("Assets::init_coffeescript()_end");
		}
	}

	
	/* ------------------------------------------------------------------------------------------ */
	
	/**
	 * Library configuration
	 * @param  array  $cfg
	 */
	public static function configure($cfg = null)
	{
		// Start benchmark
		if (self::$_enable_benchmark) self::$_ci->benchmark->mark("Assets::configure()_start");

		$cfg = array_merge(config_item('assets'), $cfg);

		if ($cfg and is_array($cfg))
		{
			foreach ($cfg as $key=>$val)
			{
				self::$$key = $val;
				//echo 'CONFIG: ', $key, ' :: ', $val, '<br>';
			}
		}

		// CssMin configuration
		self::$cssmin_filters = self::$_ci->config->item('assets_cssmin_filters');
		self::$cssmin_plugins = self::$_ci->config->item('assets_cssmin_plugins');

		// Prepare all the paths and URI's
		self::_paths();

		// End benchmark
		if (self::$_enable_benchmark) self::$_ci->benchmark->mark("Assets::configure()_end");
	}

	
	/* ------------------------------------------------------------------------------------------ */
	
	/**
	 * Set a different assets path
	 * @param string $path
	 */
	public static function set_path($path = null)
	{
		self::init();

		if ($path) self::$assets_dir = $path;

		self::_paths();
	}

	
	/* ------------------------------------------------------------------------------------------ */
	
	/**
	 * Setup paths
	 */
	private static function _paths()
	{
		// Start benchmark
		if (self::$_enable_benchmark) self::$_ci->benchmark->mark("Assets::paths()_start");

		// Set the assets base path
		self::$base_path = reduce_double_slashes(realpath(self::$assets_dir));
		
		// Now set the assets base URL
		if ( ! self::$base_url) self::$base_url = reduce_double_slashes(config_item('base_url').'/'.self::$assets_dir);
		else                    self::$base_url = self::$base_url.self::$assets_dir;

		// Auto protocol
		if (stripos(self::$base_url, '//') === 0) $slash = '/';
		else                                      $slash = '';

		// And finally the paths and URL's to the css and js assets
		self::$js_path    = reduce_double_slashes(self::$base_path .'/'.self::$js_dir);
		self::$js_url     = $slash.reduce_double_slashes(self::$base_url  .'/'.self::$js_dir);
		self::$css_path   = reduce_double_slashes(self::$base_path .'/'.self::$css_dir);
		self::$css_url    = $slash.reduce_double_slashes(self::$base_url  .'/'.self::$css_dir);
		self::$img_path   = reduce_double_slashes(self::$base_path .'/'.self::$img_dir);
		self::$img_url    = $slash.reduce_double_slashes(self::$base_url  .'/'.self::$img_dir);
		self::$cache_path = reduce_double_slashes(self::$base_path .'/'.self::$cache_dir);
		self::$cache_url  = $slash.reduce_double_slashes(self::$base_url  .'/'.self::$cache_dir);

		if ( ! self::$freeze)
		{
			// Check if all directories exist
			if ( ! is_dir(self::$js_path))
			{
				if ( ! @mkdir(self::$js_path, 0755))    exit('Error with JS directory.');
			}
			
			if ( ! is_dir(self::$css_path))
			{
				if ( ! @mkdir(self::$css_path, 0755))   exit('Error with CSS directory.');
			}
			
			if ( ! is_dir(self::$cache_path))
			{
				if ( ! @mkdir(self::$cache_path, 0777)) exit('Error with CACHE directory.');
			}
			
			// Try to make the cache direcory writable
			if (is_dir(self::$cache_path) and ! is_really_writable(self::$cache_path))
			{
				@chmod(self::$cache_path, 0777);
			}
			
			// If it's still not writable throw error
			if ( ! is_dir(self::$cache_path) or ! is_really_writable(self::$cache_path))
			{
				exit('Error with CACHE directory.');
			}
		}

		// End benchmark
		if (self::$_enable_benchmark) self::$_ci->benchmark->mark("Assets::paths()_end");
	}
	
	/* ------------------------------------------------------------------------------------------ */
	
}


/* End of file assets.php */