<?php if (!defined('FW')) die('Forbidden');

abstract class FW_Manifest
{
	/**
	 * @var array
	 */
	protected $manifest;

	/**
	 * The first requirement that was not met
	 * (that marks that the requirements are not met)
	 *
	 * @var array
	 * array(
	 *  'requirement'  => 'wordpress|framework',
	 *  'requirements' => array('min_version' => '1.2.3', ...)
	 * )
	 * or
	 * array(
	 *  'requirement'  => 'extensions',
	 *  'extension'    => 'extension_name',
	 *  'requirements' => array('min_version' => '1.2.3', ...)
	 * )
	 */
	private $not_met_requirement;

	/**
	 * When an requirement that sure will not change is not met and have no sense to execute check_requirements() again
	 * @var bool
	 */
	private $not_met_is_final = false;

	/**
	 * Not met requirement and skipped (not verified) requirements after $this->not_met_requirement was found
	 * @var array
	 */
	private $requirements_for_verification;

	private $requirements_verification_never_called = true;

	/**
	 * @param array $manifest
	 */
	protected function __construct(array $manifest)
	{
		$manifest = array_merge(array(
			'name'        => null, // title
			'uri'         => null,
			'description' => null,
			'version'     => '0.0.0',
			'author'      => null,
			'author_uri'  => null,

			// Custom fields
			'requirements' => array(),
		), $manifest);

		/**
		 * Merge $manifest['requirements']
		 */
		{
			$requirements = $manifest['requirements'];

			$manifest['requirements'] = array();

			foreach ($this->get_default_requirements() as $default_requirement => $default_requirements) {
				$manifest['requirements'][ $default_requirement ] = isset($requirements[$default_requirement])
					? array_merge(
						$default_requirements,
						$requirements[$default_requirement]
					)
					: $default_requirements;
			}

			unset($requirements);
		}

		$this->requirements_for_verification = $manifest['requirements'];

		$this->manifest = $manifest;
	}

	/**
	 * @return array { 'requirement' => array('min_version' => '..', 'max_version' => '..') }
	 */
	abstract protected function get_default_requirements();

	/**
	 * @return bool
	 */
	public function requirements_met()
	{
		if ($this->not_met_is_final) {
			return false;
		}

		if ($this->requirements_verification_never_called) {
			$this->requirements_verification_never_called = false;

			$this->check_requirements();
		}

		return empty($this->requirements_for_verification) && empty($this->not_met_requirement);
	}

	/**
	 * @return bool
	 */
	public function check_requirements()
	{
		if ($this->not_met_is_final) {
			return false;
		}

		if ($this->requirements_met()) {
			return true;
		}

		$this->not_met_requirement = array();

		global $wp_version;

		foreach ($this->requirements_for_verification as $requirement => $requirements) {
			switch ($requirement) {
				case 'php':
					if ( ! function_exists( 'phpversion' ) ) {
						break;
					}
					if (
						isset($requirements['min_version'])
						&&
						version_compare(phpversion(), $requirements['min_version'], '<')
					) {
						$this->not_met_requirement = array(
							'requirement'  => $requirement,
							'requirements' => $requirements
						);
						$this->not_met_is_final = true;
						break 2;
					}

					if (
						isset($requirements['max_version'])
						&&
						version_compare(phpversion(), $requirements['max_version'], '>')
					) {
						$this->not_met_requirement = array(
							'requirement'  => $requirement,
							'requirements' => $requirements
						);
						$this->not_met_is_final = true;
						break 2;
					}

					// met
					unset($this->requirements_for_verification[$requirement]);
					break;
				case 'wordpress':
					if (
						isset($requirements['min_version'])
						&&
						version_compare($wp_version, $requirements['min_version'], '<')
					) {
						$this->not_met_requirement = array(
							'requirement'  => $requirement,
							'requirements' => $requirements
						);
						$this->not_met_is_final = true;
						break 2;
					}

					if (
						isset($requirements['max_version'])
						&&
						version_compare($wp_version, $requirements['max_version'], '>')
					) {
						$this->not_met_requirement = array(
							'requirement'  => $requirement,
							'requirements' => $requirements
						);
						$this->not_met_is_final = true;
						break 2;
					}

					// met
					unset($this->requirements_for_verification[$requirement]);
					break;
				case 'framework':
					if (
						isset($requirements['min_version'])
						&&
						version_compare(fw()->manifest->get_version(), $requirements['min_version'], '<')
					) {
						$this->not_met_requirement = array(
							'requirement'  => $requirement,
							'requirements' => $requirements
						);
						$this->not_met_is_final = true;
						break 2;
					}

					if (
						isset($requirements['max_version'])
						&&
						version_compare(fw()->manifest->get_version(), $requirements['max_version'], '>')
					) {
						$this->not_met_requirement = array(
							'requirement'  => $requirement,
							'requirements' => $requirements
						);
						$this->not_met_is_final = true;
						break 2;
					}

					// met
					unset($this->requirements_for_verification[$requirement]);
					break;
				case 'extensions':
					$extensions =& $requirements;

					foreach ($extensions as $extension => $extension_requirements) {
						$extension_instance = fw()->extensions->get($extension);

						if (!$extension_instance) {
							/**
							 * extension in requirements does not exists
							 * maybe try call this method later and maybe will exist, or it really does not exists
							 */
							$this->not_met_requirement = array(
								'requirement'  => $requirement,
								'extension'    => $extension,
								'requirements' => $extension_requirements
							);
							break 3;
						}

						if (
							isset($extension_requirements['min_version'])
							&&
							version_compare($extension_instance->manifest->get_version(), $extension_requirements['min_version'], '<')
						) {
							$this->not_met_requirement = array(
								'requirement'  => $requirement,
								'extension'    => $extension,
								'requirements' => $extension_requirements
							);
							$this->not_met_is_final = true;
							break 3;
						}

						if (
							isset($extension_requirements['max_version'])
							&&
							version_compare($extension_instance->manifest->get_version(), $extension_requirements['max_version'], '>')
						) {
							$this->not_met_requirement = array(
								'requirement'  => $requirement,
								'extension'    => $extension,
								'requirements' => $extension_requirements
							);
							$this->not_met_is_final = true;
							break 3;
						}

						// met
						unset($this->requirements_for_verification[$requirement][$extension]);
					}

					if (empty($this->requirements_for_verification[$requirement])) {
						// all extensions requirements met
						unset($this->requirements_for_verification[$requirement]);
					}
					break;
			}
		}

		return $this->requirements_met();
	}

	public function get_version()
	{
		return $this->manifest['version'];
	}

	public function get_name()
	{
		return $this->manifest['name'];
	}

	/**
	 * @param string $multi_key
	 * @param mixed $default_value
	 * @return mixed
	 */
	public function get( $multi_key, $default_value = null ) {
		return fw_akg( $multi_key, $this->manifest, $default_value );
	}

	/**
	 * Get entire manifest.
	 * @return array
	 */
	public function get_manifest() {
		return $this->manifest;
	}

	/**
	 * Call this only after check_requirements() failed
	 * @return array
	 */
	public function get_not_met_requirement()
	{
		return $this->not_met_requirement;
	}

	/**
	 * Return user friendly requirement as text
	 * Call this only after check_requirements() failed
	 * @return string
	 */
	public function get_not_met_requirement_text()
	{
		if (!$this->not_met_requirement) {
			return '';
		}

		$requirement = array();

		foreach ($this->not_met_requirement['requirements'] as $req_key => $req) {
			switch ($req_key) {
				case 'min_version':
					$requirement[] = __('minimum required version is', 'fw') .' '. $req;
					break;
				case 'max_version':
					$requirement[] = __('maximum required version is', 'fw') .' '. $req;
					break;
			}
		}

		$requirement = implode(' '. __('and', 'fw') .' ', $requirement);

		switch ($this->not_met_requirement['requirement']) {
			case 'php':
				if ( ! function_exists( 'phpversion' ) ) {
					break;
				}

				$requirement = sprintf(
					__('Current PHP version is %s, %s', 'fw'),
					phpversion(), $requirement
				);
				break;
			case 'wordpress':
				global $wp_version;

				$requirement = sprintf(
					__('Current WordPress version is %s, %s', 'fw'),
					$wp_version, $requirement
				);
				break;
			case 'framework':
				$requirement = sprintf(
					__('Current Framework version is %s, %s', 'fw'),
					fw()->manifest->get_version(), $requirement
				);
				break;
			case 'extensions':
				$extension = fw()->extensions->get($this->not_met_requirement['extension']);

				if ($extension) {
					$requirement = sprintf(
						__('Current version of the %s extension is %s, %s', 'fw'),
						$extension->manifest->get_name(), $extension->manifest->get_version(), $requirement
					);
				} else {
					if (empty($requirement)) {
						$requirement = sprintf(
							__('%s extension is required', 'fw'),
							ucfirst($this->not_met_requirement['extension'])
						);
					} else {
						$requirement = sprintf(
							__('%s extension is required (%s)', 'fw'),
							ucfirst($this->not_met_requirement['extension']), $requirement
						);
					}
				}
				break;
			default:
				$requirement = 'Unknown requirement "'. $this->not_met_requirement['requirement'] .'"';
		}

		return $requirement;
	}
}

class FW_Framework_Manifest extends FW_Manifest
{
	public function __construct(array $manifest)
	{
		if (empty($manifest['name'])) {
			$manifest['name'] = __('Framework', 'fw');
		}

		parent::__construct($manifest);
	}

	protected function get_default_requirements()
	{
		return array(
			'php' => array(
				'min_version' => '5.2.4',
				/*'max_version' => '10000.0.0',*/
			),
			'wordpress' => array(
				'min_version' => '4.0',
				/*'max_version' => '10000.0.0',*/
			),
		);
	}
}

class FW_Theme_Manifest extends FW_Manifest
{
	public function __construct(array $manifest)
	{
		$manifest_defaults = array(
			/**
			 * You can use this in a wp_option id,
			 * so that option value will be different on a theme with different id.
			 *
			 * fixme: default value should be get_option( 'stylesheet' ) but it can't be changed now
			 * because there can be themes that has saved Theme Settings in wp_option: 'fw_theme_settings_options:default'
			 * changing this default value will result in Theme Settings options "reset".
			 */
			'id' => 'default',
			'supported_extensions' => array(
				/*
				'extension_name' => array(),
				*/
                'backups' => array(),
			),
		);

		$theme = wp_get_theme();

		foreach(array(
			'name'        => 'Name',
			'uri'         => 'ThemeURI',
			'description' => 'Description',
			'version'     => 'Version',
			'author'      => 'Author',
			'author_uri'  => 'AuthorURI',
		) as $manifest_key => $stylesheet_header) {
			$header_value = trim($theme->get($stylesheet_header));

			if ( is_child_theme() && $theme->parent() ) {
				switch ($manifest_key) {
					case 'version':
					case 'uri':
					case 'author':
					case 'author_uri':
					case 'license':
						// force parent theme value
						$header_value = $theme->parent()->get($stylesheet_header);
						break;
					default:
						if (!$header_value) {
							// use parent theme value only if child theme value is empty
							$header_value = $theme->parent()->get($stylesheet_header);
						}
				}
			}

			if ($header_value) {
				$manifest_defaults[$manifest_key] = $header_value;
			}
		}

		parent::__construct(array_merge($manifest_defaults, $manifest));
	}

	protected function get_default_requirements()
	{
		return array(
			'php' => array(
				'min_version' => '5.2.4',
				/*'max_version' => '10000.0.0',*/
			),
			'wordpress' => array(
				'min_version' => '4.0',
				/*'max_version' => '10000.0.0',*/
			),
			'framework' => array(
				/*'min_version' => '0.0.0',
				'max_version' => '1000.0.0'*/
			),
			'extensions' => array(
				/*'extension_name' => array(
					'min_version' => '0.0.0',
					'max_version' => '1000.0.0'
				)*/
			)
		);
	}

	public function get_id()
	{
		return $this->manifest['id'];
	}
}

class FW_Extension_Manifest extends FW_Manifest
{
	public function __construct(array $manifest)
	{
		parent::__construct($manifest);

		unset($manifest);

		// unset unnecessary keys
		unset($this->manifest['id']);

		$this->manifest = array_merge(array(
			/**
			 * @type bool Display on the Extensions page or it's a hidden extension
			 */
			'display' => false,
			/**
			 * @type bool If extension can exist alone
			 * false - There is no sense for it to exist alone, it exists only when is required by some other extension.
			 * true  - Can exist alone without bothering about other extensions.
			 */
			'standalone' => false,
			/**
			 * @type string Thumbnail used on the Extensions page
			 * All framework extensions has thumbnails set in the available extensions list
			 * but if your extension is not in that list and id located in the theme, you can set the thumbnail via this parameter
			 */
			'thumbnail' => null,
		), $this->manifest);
	}

	protected function get_default_requirements()
	{
		return array(
			'php' => array(
				'min_version' => '5.2.4',
				/*'max_version' => '10000.0.0',*/
			),
			'wordpress' => array(
				'min_version' => '4.0',
				/*'max_version' => '10000.0.0',*/
			),
			'framework' => array(
				/*'min_version' => '0.0.0',
				'max_version' => '1000.0.0'*/
			),
			'extensions' => array(
				/*'extension_name' => array(
					'min_version' => '0.0.0',
					'max_version' => '1000.0.0'
				)*/
			)
		);
	}

	public function get_required_extensions()
	{
		return $this->manifest['requirements']['extensions'];
	}
}
