<?php

/**
 * Wrap library calls to date subsystem.
 *
 * Meant to increase performance and work around known bugs in environment.
 *
 * @author       Time.ly Network, Inc.
 * @since        2.0
 * @package      Ai1EC
 * @subpackage   Ai1EC.Date
 */

class Ai1ec_Date_System extends Ai1ec_Base {

	/**
	 * @var array List of local time (key '0') and GMT time (key '1').
	 */
	protected $_current_time = array();

	/**
	 * @var Ai1ec_Cache_Memory
	 */
	protected $_gmtdates;

	/**
	 * Initiate current time list.
	 *
	 * @param Ai1ec_Registry_Object $registry
	 *
	 * @return void
	 */
	public function __construct( Ai1ec_Registry_Object $registry ) {
		parent::__construct( $registry );
		$gmt_time = ( version_compare( PHP_VERSION, '5.1.0' ) >= 0 )
			? time()
			: gmmktime();
		$requestTime = isset( $_SERVER['REQUEST_TIME'] ) ? (int)$_SERVER['REQUEST_TIME'] : time();
		$this->_current_time     = array(
			$requestTime,
			$gmt_time,
		);
		$this->_gmtdates = $registry->get( 'cache.memory' );
	}

	/**
	 * Get current time UNIX timestamp.
	 *
	 * Uses in-memory value, instead of re-calling `time()` / `gmmktime()`.
	 *
	 * @param bool $is_gmt Set to true to get GMT timestamp.
	 *
	 * @return int Current time UNIX timestamp
	 */
	public function current_time( $is_gmt = false ) {
		return $this->_current_time[(int)( (bool)$is_gmt )];
	}

	/**
	 * Returns the associative array of date patterns supported by the plugin.
	 *
	 * Currently the formats are:
	 *   array(
	 *     'def' => 'd/m/yyyy',
	 *     'us'  => 'm/d/yyyy',
	 *     'iso' => 'yyyy-m-d',
	 *     'dot' => 'm.d.yyyy',
	 *   );
	 *
	 * 'd' or 'dd' represent the day, 'm' or 'mm' represent the month, and 'yy'
	 * or 'yyyy' represent the year.
	 *
	 * @return array List of supported date patterns.
	 */
	public function get_date_patterns() {
		return array(
			'def' => 'd/m/yyyy',
			'us'  => 'm/d/yyyy',
			'iso' => 'yyyy-m-d',
			'dot' => 'm.d.yyyy',
		);
	}

	/**
	 * Get acceptable date format.
	 *
	 * Returns the date pattern (in the form 'd-m-yyyy', for example) associated
	 * with the provided key, used by plugin settings. Simply a static map as
	 * follows:
	 *
	 * @param string $key Key for the date format.
	 *
	 * @return string Associated date format pattern.
	 */
	public function get_date_pattern_by_key( $key = 'def' ) {
		$patterns = $this->get_date_patterns();
		if ( ! isset( $patterns[$key] ) ) {
			return (string)current( $patterns );
		}
		return $patterns[$key];
	}

	/**
	 * Format timestamp into URL safe, user selected representation.
	 *
	 * Returns a formatted date given a timestamp, based on the given date
	 * format, with any '/' characters replaced with URL-friendly '-'
	 * characters.
	 *
	 * @see Ai1ec_Date_System::get_date_patterns() for supported date formats.
	 *
	 * @param int    $timestamp UNIX timestamp representing a date.
	 * @param string $pattern   Key of date pattern (@see
	 *                          self::get_date_format_patter()) to
	 *                          format date with
	 *
	 * @return string Formatted date string.
	 */
	public function format_date_for_url( $timestamp, $pattern = 'def' ) {
		$date = $this->format_date( $timestamp, $pattern );
		$date = str_replace( '/', '-', $date );
		return $date;
	}

	/**
	 * Similar to {@see format_date_for_url} just using new DateTime interface.
	 *
	 * @param Ai1ec_Date_Time $datetime Instance of datetime to format.
	 * @param string          $pattern  Target format to use.
	 *
	 * @return string Formatted datetime string.
	 */
	public function format_datetime_for_url(
		Ai1ec_Date_Time $datetime,
		$pattern = 'def'
	) {
		$date = $datetime->format( $this->get_date_format_patter( $pattern ) );
		return str_replace( '/', '-', $date );
	}

	/**
	 * Returns the date formatted with new pattern from a given date and old pattern.
	 *
	 * @see  self::get_date_patterns() for supported date formats.
	 *
	 * @param  string $date          Formatted date string
	 * @param  string $old_pattern   Key of old date pattern (@see
	 *                               self::get_date_format_patter())
	 * @param  string $new_pattern   Key of new date pattern (@see
	 *                               self::get_date_format_patter())
	 * @return string                Formatted date string with new pattern
	 */
	public function convert_date_format( $date, $old_pattern, $new_pattern ) {
		// Convert old date to timestamp
		$timeArray = date_parse_from_format( $this->get_date_format_patter( $old_pattern ), $date );

		$timestamp = mktime(
			$timeArray['hour'], $timeArray['minute'], $timeArray['second'],
			$timeArray['month'], $timeArray['day'], $timeArray['year']
		);

		// Convert to new date pattern
		return $this->format_date( $timestamp, $new_pattern );
	}

	/**
	 * Returns a formatted date given a timestamp, based on the given date format.
	 *
	 * @see  self::get_date_patterns() for supported date formats.
	 *
	 * @param  int $timestamp    UNIX timestamp representing a date (in GMT)
	 * @param  string $pattern   Key of date pattern (@see
	 *                           self::get_date_format_patter()) to
	 *                           format date with
	 * @return string            Formatted date string
	 */
	public function format_date( $timestamp, $pattern = 'def' ) {
		return gmdate( $this->get_date_format_patter( $pattern ), $timestamp );
	}

	public function get_date_format_patter( $requested ) {
		$pattern = $this->get_date_pattern_by_key( $requested );
		$pattern = str_replace(
			array( 'dd', 'd', 'mm', 'm', 'yyyy', 'yy' ),
			array( 'd',  'j', 'm',  'n', 'Y',    'y' ),
			$pattern
		);
		return $pattern;
	}

	/**
	 * Returns human-readable version of the GMT offset.
	 *
	 * @param string $timezone_name Olsen Timezone name [optional=null]
	 *
	 * @return string GMT offset expression
	 */
	public function get_gmt_offset_expr( $timezone_name = null ) {
		$timezone = $this->get_gmt_offset( $timezone_name );
		$offset_h = (int)( $timezone / 60 );
		$offset_m = absint( $timezone - $offset_h * 60 );
		$timezone = sprintf(
			Ai1ec_I18n::__( 'GMT%+d:%02d' ),
			$offset_h,
			$offset_m
		);

		return $timezone;
	}

	/**
	 * Get current GMT offset in seconds.
	 *
	 * @param string $timezone_name Olsen Timezone name [optional=null]
	 *
	 * @return int Offset from GMT in seconds.
	 */
	public function get_gmt_offset( $timezone_name = null ) {
		if ( null === $timezone_name ) {
			$timezone_name = 'sys.default';
		}
		$current = $this->_registry->get(
			'date.time',
			'now',
			$timezone_name
		);
		return $current->get_gmt_offset();
	}

	/**
	 * gmgetdate method
	 *
	 * Get date/time information in GMT
	 *
	 * @param int $timestamp Timestamp at which information shall be evaluated
	 *
	 * @return array Associative array of information related to the timestamp
	 */
	public function gmgetdate( $timestamp = NULL ) {
		if ( NULL === $timestamp ) {
			$timestamp = isset( $_SERVER['REQUEST_TIME'] ) ? (int)$_SERVER['REQUEST_TIME'] : time();
		}
		if ( NULL === ( $date = $this->_gmtdates->get( $timestamp ) ) ) {
			$particles = explode(
				',',
				gmdate( 's,i,G,j,w,n,Y,z,l,F,U', $timestamp )
			);
			$date      = array_combine(
				array(
					'seconds',
					'minutes',
					'hours',
					'mday',
					'wday',
					'mon',
					'year',
					'yday',
					'weekday',
					'month',
					0
				),
				$particles
			);
			$this->_gmtdates->set( $timestamp, $date );
		}
		return $date;
	}

	/**
	 * Returns current rounded time as unix integer.
	 *
	 * @param int $shift Shift value.
	 *
	 * @return int Unix timestamp.
	 */
	public function get_current_rounded_time( $shift = 11 ) {
		return $this->current_time() >> $shift << $shift;
	}
}
