<?php
/**
 * @version    $Id$
 * @package    IT_Theme_Framework
 * @author     InnoThemes Team <support@innothemes.com>
 * @copyright  Copyright (C) 2012 InnoThemes.com. All Rights Reserved.
 * @license    GNU/GPL v2 or later http://www.gnu.org/licenses/gpl-2.0.html
 *
 * Websites: http://www.innothemes.com
 */

/**
 * Assets initialization.
 *
 * @package  IT_Theme_Framework
 * @since    1.0.0
 */
class IT_Init_Assets {
	/**
	 * Assets to be registered.
	 *
	 * @var  array
	 */
	protected static $assets = array();

	/**
	 * Registered assets.
	 *
	 * @var  array
	 */
	protected static $registered = array();

	/**
	 * Loaded assets.
	 *
	 * @var  array
	 */
	protected static $loads = array();

	/**
	 * Registered inline scripts/styles.
	 *
	 * @var  array
	 */
	protected static $inline = array( 'css' => array(), 'js' => array() );

	/**
	 * Triggered hooks.
	 *
	 * @var  array
	 */
	protected static $triggered_hooks = array();

	/**
	 * Hook into WordPress.
	 *
	 * @return  void
	 */
	public static function hook() {
		// Register action to load assets
		static $registered;

		if ( ! isset( $registered ) ) {
			global $pagenow;

			// Admin or frontend?
			if ( 'customize.php' == $pagenow ) {
				// Register actions
				add_action( 'customize_controls_enqueue_scripts', array( __CLASS__, 'enqueue_scripts' ), 100 );
				add_action( 'customize_controls_print_styles'   , array( __CLASS__, 'head'            ), 100 );
				add_action( 'customize_controls_print_scripts'  , array( __CLASS__, 'footer'          ), 100 );
			} else {
				$prefix = defined( 'WP_ADMIN' ) ? 'admin' : 'wp';

				// Register actions
				add_action( "{$prefix}_enqueue_scripts", array( __CLASS__, 'enqueue_scripts' ), 100 );
				add_action( "{$prefix}_head"           , array( __CLASS__, 'head'            ), 100 );
				add_action( "{$prefix}_footer"         , array( __CLASS__, 'footer'          ), 100 );
			}

			// Register filters to compress assets
			add_filter( 'wp_print_scripts', array( __CLASS__, 'compress_scripts' ), 1000 );
			add_filter( 'wp_print_styles',  array( __CLASS__, 'compress_styles'  ), 1000 );

			$registered = true;
		}
	}

	/**
	 * Load required asset.
	 *
	 * @param   string  $handle  Asset handle, e.g. bootstrap-css, bootstrap-js, jquery-fancybox-css, jquery-fancybox-js, etc.
	 * @param   string  $src     Relative path from IT Plugin Framework directory to asset file.
	 * @param   array   $deps    Array of dependencies.
	 * @param   string  $ver     Asset version.
	 *
	 * @return  void
	 */
	public static function load( $handle, $src = null, $deps = array(), $ver = null ) {
		// Check if we have an array of handle
		if ( is_array( $handle ) ) {
			foreach ( $handle as $key ) {
				self::load( $key );
			}

			return;
		}

		// Store new asset details for register later
		if ( ! isset( self::$assets[ $handle ] ) && ! empty( $src ) ) {
			self::$assets[ $handle ] = array(
				'src'  => $src,
				'deps' => $deps,
				'ver'  => $ver,
			);
		}

		// Check if this is an Ajax request?
		if ( isset( $_GET['ajax'] ) && $_GET['ajax'] ) {
			// Detect asset type
			$type = ( substr( $handle, -4 ) == '-css' ) ? 'style' : 'script';

			// Print HTML tag for loading this asset immediately
			$src = self::$assets[ $handle ]['src'];
			$ver = self::$assets[ $handle ]['ver'];

			if ( 'style' == $type ) {
				echo '<link rel="stylesheet" id="' . $handle . '" href="' . $src . '?ver=' . $ver . '" type="text/css" media="all" />';
			} else {
				echo '<script type="text/javascript" src="' . $src . '?ver=' . $ver . '"></script>';
			}
		} else {
			// Check if required hook is triggered?
			if ( in_array( 'enqueue_scripts', self::$triggered_hooks ) ) {
				self::enqueue_asset( $handle );
			} else {
				self::$loads[] = $handle;
			}
		}
	}

	/**
	 * Register inline scripts / styles.
	 *
	 * @param   string   $type       Either 'css' or 'js'.
	 * @param   string   $text       Inline script/style, do not wrap inside <script> / <style> tags.
	 * @param   boolean  $print_out  Print out immediately instead of schedule till proper hook is triggered.
	 *
	 * @return  void
	 */
	public static function inline( $type, $text, $print_out = false ) {
		if ( isset( self::$inline[$type] ) ) {
			// Print out immediately if proper hook for printing out inline scripts / styles is already triggered
			if ( 'css' == $type && in_array( 'head', self::$triggered_hooks ) ) {
				$print_out = true;
			} elseif ( 'js' == $type && in_array( 'footer', self::$triggered_hooks ) ) {
				$print_out = true;
			}

			// Trim CR / LF character
			$text = trim( $text, "\r\n" );

			if ( $print_out ) {
				self::print_inline( $type, $text );
			} else {
				self::$inline[ $type ][] = $text;
			}
		}
	}

	/**
	 * Do 'admin_enqueue_scripts' / 'wp_enqueue_scripts' action.
	 *
	 * @return  void
	 */
	public static function enqueue_scripts() {
		// Register assets
		self::register();

		foreach ( self::$loads as $handle ) {
			self::enqueue_asset( $handle );
		}

		// Indicate that the hook is triggered
		self::$triggered_hooks[] = 'enqueue_scripts';
	}

	/**
	 * Do 'admin_head' / 'wp_head' action.
	 *
	 * @return  void
	 */
	public static function head() {
		self::print_inline( 'css' );

		// Indicate that the hook is triggered
		self::$triggered_hooks[] = 'head';
	}

	/**
	 * Do 'admin_footer' / 'wp_footer' action.
	 *
	 * @return  void
	 */
	public static function footer() {
		self::print_inline( 'js' );

		// Indicate that the hook is triggered
		self::$triggered_hooks[] = 'footer';

		// Execute action to localize script
		do_action( 'it_assets_localize', self::$loads );
	}

	/**
	 * Enqueue asset.
	 *
	 * @param   string  $handle  Asset handle.
	 *
	 * @return  void
	 */
	protected static function enqueue_asset( $handle ) {
		if ( isset( self::$assets[ $handle ] ) && isset( self::$assets[ $handle ]['site'] ) ) {
			if ( 'admin' == self::$assets[ $handle ]['site'] && ! defined( 'WP_ADMIN' ) ) {
				return;
			}

			if ( 'front' == self::$assets[ $handle ]['site'] && defined( 'WP_ADMIN' ) ) {
				return;
			}
		}

		// Register assets if not already registered
		if ( ! in_array( $handle, self::$registered ) ) {
			self::register();
		}

		// Detect asset type
		$type = ( substr( $handle, -4 ) == '-css' ) ? 'style' : 'script';

		// Enqueue asset
		if ( 'script' == $type && isset( self::$assets[ $handle ] ) && in_array( $handle, self::$registered ) ) {
			// Build arguments to load script in footer so it can be localized at any time
			$args[] = preg_replace( '/-(css|js)$/', '', $handle );
			$args[] = self::$assets[ $handle ]['src'];
			$args[] = isset( self::$assets[ $handle ]['deps'] ) ? self::$assets[$handle]['deps'] : array();
			$args[] = isset( self::$assets[ $handle ]['ver']  ) ? self::$assets[$handle]['ver']  : false;
			$args[] = true;

			call_user_func_array( 'wp_enqueue_script', $args );
		} else {
			call_user_func( "wp_enqueue_{$type}", preg_replace( '/-(css|js)$/', '', $handle ) );
		}
	}

	/**
	 * Generate and print out inline scripts / styles.
	 *
	 * @param   string   $type     Either 'css' or 'js'.
	 * @param   string   $text     Text to be printed out.
	 * @param   boolean  $no_wrap  If set to TRUE, inline script will not be wrapped inside '$( document ).ready' function.
	 *
	 * @return  void
	 */
	protected static function print_inline( $type, $text = null, $no_wrap = false ) {
		// Generate then print inline styles / scripts
		$html = array();

		if ( ! empty( $text ) || count( self::$inline[$type] ) ) {
			if ( 'js' == $type ) {
				$html[] = '<script type="text/javascript">';
				$html[] = '(function($) {';

				$no_wrap || $html[] = "\t$(document).ready(function() {";
			} else {
				$html[] = '<style type="text/css">';
			}

			$html[] = ! empty( $text ) ? $text : implode( "\n\n", self::$inline[$type] );

			if ( 'js' == $type ) {
				$no_wrap || $html[] = "\t});";

				$html[] = '})(jQuery);';
				$html[] = '</script>';
			} else {
				$html[] = '</style>';
			}
		}

		echo '' . implode( "\n", $html ) . "\n";
	}

	/**
	 * Register InnoThemes's assets with WordPress.
	 *
	 * @return  void
	 */
	protected static function register() {
		// Filter assets to be registered
		self::$assets = apply_filters( 'it_assets_register', self::$assets );

		foreach ( self::$assets as $key => $value ) {
			// If asset is registered, continue the loop
			if ( in_array( $key, self::$registered ) ) {
				continue;
			}

			// Store asset being registered
			self::$registered[] = $key;

			// Set default value for missing data
			isset( $value['deps'] ) || $value['deps'] = array();
			isset( $value['ver' ] ) || $value['ver' ] = null;

			// Detect asset type
			$type = substr( $key, -4 ) == '-css' ? 'style' : 'script';

			// Shorten asset and dependency keys
			$key = preg_replace( '/-(css|js)$/', '', $key );

			foreach ( $value['deps'] as $k => $v ) {
				if ( array_key_exists( $v, self::$assets ) ) {
					$value['deps'][ $k ] = preg_replace( '/-(css|js)$/', '', $v );
				}
			}

			// Register asset
			call_user_func( "wp_register_{$type}", $key, $value['src'], $value['deps'], $value['ver'] );
		}
	}

	/**
	 * Apply 'wp_print_scripts' to compress Javascript files.
	 *
	 * @return  void
	 */
	public static function compress_scripts() {
		global $it_options, $wp_scripts;

		// Check if script compression is enabled?
		if ( defined( 'WP_ADMIN' ) || ! isset( $it_options ) || ! in_array( 'js', $it_options['assets_compression'] ) ) {
			return;
		}

		if ( is_a( $wp_scripts, 'WP_Scripts' ) ) {
			if ( ! ( $path = self::compress_location() ) ) {
				return;
			}

			// Use WordPress's global file-system connector
			global $wp_filesystem;

			// Process current scripts queue
			$queue = array();

			// Separate header and footer scripts
			$wp_scripts->all_deps( $wp_scripts->queue );

			foreach ( $wp_scripts->to_do as $k => $handle ) {
				if ( ! $wp_scripts->registered[ $handle ]->src ) {
					continue;
				}

				// Prepare source
				@list( $source, $query ) = explode( '?', $wp_scripts->registered[ $handle ]->src, 2 );

				if ( preg_match( '#^(https?:|//)#i', $source ) ) {
					if ( stripos( $source, site_url() ) === false ) {
						// Do not compress remote script
						$queue[ $handle ] = $k;

						continue;
					}

					if ( '.js' != substr( $source, -3 ) ) {
						// Do not compress dynamic generation script
						$queue[ $handle ] = $k;

						continue;
					}
				}

				// Determine the position this script should be loaded in
				$position = 'header';

				if ( isset( $wp_scripts->registered[ $handle ]->extra['group'] ) && 1 == $wp_scripts->registered[ $handle ]->extra['group'] ) {
					$position = 'footer';
				}

				// Store script's relative path to compression queue
				$script_path = str_replace( site_url(), '', $source );
				$script_size = $wp_filesystem->size( ABSPATH . trim( $script_path, '\\/' ) );
				$version     = $wp_scripts->registered[ $handle ]->ver;

				$queue["{$position}-scripts"][] = $script_path . '?' . ( $query ? "{$query}&" : '' ) . ( $version ? "v={$version}&" : '' ) . 's=' . $script_size;

				// Unset some properties
				$wp_scripts->registered[ $handle ]->src  = false;
				$wp_scripts->registered[ $handle ]->deps = array();
			}

			// Create loader file for header and footer scripts
			$tracking = array( 'js' => array() );
			$update = false;

			// Track changes
			if ( $wp_filesystem->is_file( "{$path}/tracking.php" ) ) {
				include "{$path}/tracking.php";
			} else {
				$update = true;
			}

			// Process compression
			$upload  = wp_upload_dir();
			$scripts = array();

			foreach ( $queue as $k => $group ) {
				if ( ! is_array( $group ) ) {
					continue;
				}

				// Generate loader file name
				$script = 'js-' . md5( serialize( $group ) ) . '.php';

				// Create loader file only if not already exists
				if ( ! $wp_filesystem->is_file( "{$path}/{$script}" ) ) {
					$update = true;

					// Create loader file
					$content = '<?php
global $it_scripts;
$it_scripts = json_decode(\'' . json_encode( $group ) . '\', true);
require \'' . dirname( __FILE__ ) . '/assets/load-scripts.php\';
';

					if ( ! $wp_filesystem->put_contents( "{$path}/{$script}", $content ) ) {
						return;
					}
				}

				// Register new script with WordPress
				$wp_scripts->add(
					$k,                                                                         // Handle
					str_replace( $upload['basedir'], $upload['baseurl'], "{$path}/{$script}" ), // Source URL
					array(),                                                                    // Dependency
					false,                                                                      // Version
					null                                                                        // Arguments
				);

				if  ( strpos( $k, 'footer' ) !== false ) {
					$wp_scripts->add_data( $k, 'group', 1 );
				}

				// Store loader for tracking
				$scripts[] = $script;
			}

			// Update tracking file
			if ( $update ) {
				// Remove obsolete loader file
				$removes = array();

				if ( @is_array( $tracking['js'][ $_SERVER['REQUEST_URI'] ] ) ) {
					foreach ( $tracking['js'][ $_SERVER['REQUEST_URI'] ] as $script ) {
						if ( ! in_array( $script, $scripts ) ) {
							$removes[ $script ] = '';
						}
					}
				}

				// Update tracking
				$tracking['js'][ $_SERVER['REQUEST_URI'] ] = $scripts;

				foreach ( $tracking['js'] as $k => $scripts ) {
					if ( $_SERVER['REQUEST_URI'] != $k ) {
						foreach ( $scripts as $script ) {
							if ( array_key_exists( $script, $removes ) ) {
								unset( $removes[ $script ] );
							}
						}
					}
				}

				if ( count( $removes ) ) {
					$removes = array_keys( $removes );

					foreach ( $removes as $script ) {
						$wp_filesystem->delete( "{$path}/{$script}" );
					}
				}

				// Update tracking file
				$content = '<?php
$tracking = json_decode(\'' . json_encode( $tracking ) . '\', true);
';

				if ( ! $wp_filesystem->put_contents( "{$path}/tracking.php", $content ) ) {
					return;
				}
			}

			// Update scripts queue
			$wp_scripts->reset();

			$wp_scripts->queue = array_keys( $queue );
		}
	}

	/**
	 * Apply 'wp_print_styles' to compress stylesheets.
	 *
	 * @return  void
	 */
	public static function compress_styles() {
		global $it_options, $wp_styles;

		// Check if stylesheet compression is enabled?
		if ( defined( 'WP_ADMIN' ) || ! isset( $it_options ) || ! in_array( 'css', $it_options['assets_compression'] ) ) {
			return;
		}

		if ( is_a( $wp_styles, 'WP_Styles' ) ) {
			if ( ! ( $path = self::compress_location() ) ) {
				return;
			}

			// Use WordPress's global file-system connector
			global $wp_filesystem;

			// Process current stylesheets queue
			$queue = array();

			// Separate stylesheets by media
			$wp_styles->all_deps( $wp_styles->queue );

			foreach ( $wp_styles->to_do as $k => $handle ) {
				if ( ! $wp_styles->registered[ $handle ]->src ) {
					continue;
				}

				// Prepare source
				@list( $source, $query ) = explode( '?', $wp_styles->registered[ $handle ]->src, 2 );

				if ( preg_match( '#^(https?:|//)#i', $source ) ) {
					if ( stripos( $source, site_url() ) === false ) {
						// Do not compress remote stylesheet
						$queue[ $handle ] = $k;

						continue;
					}

					if ( '.css' != substr( $source, -4 ) ) {
						// Do not compress dynamic generation stylesheet
						$queue[ $handle ] = $k;

						continue;
					}
				}

				// Determine the media this stylesheet associated with
				$media = 'all';

				if ( isset( $wp_styles->registered[ $handle ]->args ) && 'all' != $wp_styles->registered[ $handle ]->args ) {
					$media = $wp_styles->registered[ $handle ]->args;
				}

				// Store stylesheet's relative path to compression queue
				$stylesheet_path = str_replace( site_url(), '', $source );
				$stylesheet_size = $wp_filesystem->size( ABSPATH . trim( $stylesheet_path, '\\/' ) );
				$version         = $wp_styles->registered[ $handle ]->ver;

				$queue["{$media}-stylesheets"][] = $stylesheet_path . '?' . ( $query ? "{$query}&" : '' ) . ( $version ? "v={$version}&" : '' ) . 's=' . $stylesheet_size;

				// Unset some properties
				$wp_styles->registered[ $handle ]->src  = false;
				$wp_styles->registered[ $handle ]->deps = array();
			}

			// Create loader files
			$tracking = array( 'css' => array() );
			$update = false;

			// Track changes
			global $wp_filesystem;

			if ( $wp_filesystem->is_file( "{$path}/tracking.php" ) ) {
				include "{$path}/tracking.php";
			} else {
				$update = true;
			}

			// Process compression
			$upload      = wp_upload_dir();
			$stylesheets = array();

			foreach ( $queue as $k => $group ) {
				if ( ! is_array( $group ) ) {
					continue;
				}

				// Generate loader file name
				$stylesheet = 'css-' . md5( serialize( $group ) ) . '.php';

				// Create loader file only if not already exists
				if ( ! $wp_filesystem->is_file( "{$path}/{$stylesheet}" ) ) {
					$update = true;

					// Create loader file
					$content = '<?php
global $it_styles;
$it_styles = json_decode(\'' . json_encode( $group ) . '\', true);
require \'' . dirname( __FILE__ ) . '/assets/load-styles.php\';
';

					if ( ! $wp_filesystem->put_contents( "{$path}/{$stylesheet}", $content ) ) {
						return;
					}
				}

				// Register new style with WordPress
				$wp_styles->add(
					$k,                                                                             // Handle
					str_replace( $upload['basedir'], $upload['baseurl'], "{$path}/{$stylesheet}" ), // Source URL
					array(),                                                                        // Dependency
					false,                                                                          // Version
					str_replace( '-stylesheets', '', $k )                                           // Media
				);

				// Store loader for tracking
				$stylesheets[] = $stylesheet;
			}

			// Update tracking file
			if ( $update ) {
				// Remove obsolete loader file
				$removes = array();

				if ( @is_array( $tracking['css'][ $_SERVER['REQUEST_URI'] ] ) ) {
					foreach ( $tracking['css'][ $_SERVER['REQUEST_URI'] ] as $stylesheet ) {
						if ( ! in_array( $stylesheet, $stylesheets ) ) {
							$removes[ $stylesheet ] = '';
						}
					}
				}

				// Update tracking
				$tracking['css'][ $_SERVER['REQUEST_URI'] ] = $stylesheets;

				foreach ( $tracking['css'] as $k => $stylesheets ) {
					if ( $_SERVER['REQUEST_URI'] != $k ) {
						foreach ( $stylesheets as $stylesheet ) {
							if ( array_key_exists( $stylesheet, $removes ) ) {
								unset( $removes[ $stylesheet ] );
							}
						}
					}
				}

				if ( count( $removes ) ) {
					$removes = array_keys( $removes );

					foreach ( $removes as $stylesheet ) {
						$wp_filesystem->delete( "{$path}/{$stylesheet}" );
					}
				}

				// Update tracking file
				$content = '<?php
$tracking = json_decode(\'' . json_encode( $tracking ) . '\', true);
';

				if ( ! $wp_filesystem->put_contents( "{$path}/tracking.php", $content ) ) {
					return;
				}
			}

			// Update styles queue
			$wp_styles->reset();

			$wp_styles->queue = array_keys( $queue );
		}
	}

	/**
	 * Prepare location for storing loader files that do assets compression.
	 *
	 * @return  string
	 */
	protected function compress_location() {
		// Init WordPress Filesystem Abstraction
		function_exists( 'WP_Filesystem' ) || include_once ABSPATH . 'wp-admin/includes/file.php';

		global $wp_filesystem;

		isset( $wp_filesystem ) || WP_Filesystem();

		// Get path to WordPress's upload directory
		$upload = wp_upload_dir();
		$path   = $upload['basedir'] . '/' . IT_THEME_ID . '/asset-loader';

		// Prepare directory for storing loader file
		if ( ! $wp_filesystem->is_dir( $path ) ) {
			$parts = explode( '/', str_replace( '\\', '/', $path ) );

			if ( $n = count( $parts ) ) {
				$tmp = $parts[0];

				for ( $i = 1; $i < $n; $i++ ) {
					$tmp .= "/{$parts[ $i ]}";

					if ( ! $wp_filesystem->is_dir( $tmp ) && ! $wp_filesystem->mkdir( $tmp ) ) {
						return;
					}
				}
			}
		}

		return $path;
	}
}
