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

/**
 * All framework extensions should extend this
 */
abstract class FW_Extension
{
	/**
	 * Called after all extensions instances was created
	 * @internal
	 */
	abstract protected function _init();

	/** @var FW_Extension_Manifest */
	public $manifest;

	/** @var string Key used in FW_Cache to store data about extensions */
	private static $cache_key = 'fw_ext';

	/** @var FW_Access_Key */
	private static $access_key;

	/**
	 * Extension name, equal to directory name
	 * @var string
	 */
	private $name;

	/**
	 * Parent extension instance
	 * @var FW_Extension|null
	 */
	private $parent;

	/**
	 * @var string
	 */
	private $rel_path;

	/**
	 * @var string
	 */
	private $path;

	/**
	 * @var string
	 */
	private $uri;

	/**
	 * On what directory depth is the extension
	 *
	 * 1 - Root extension
	 * 2 - Their children
	 * 3 - Sub children
	 * ...
	 *
	 * @var int
	 */
	private $depth;

	/**
	 * Locations where the extension can look for customizations (overwrite views, options; extend config)
	 * @var array {'/path' => 'https://uri.to/path'}
	 */
	private $customizations_locations;

	final public function __construct($data)
	{
		if (!self::$access_key) {
			self::$access_key = new FW_Access_Key('extension');
		}

		$this->rel_path = $data['rel_path'];
		$this->path     = $data['path'];
		$this->uri      = $data['uri'];
		$this->parent   = $data['parent'];
		$this->depth    = $data['depth'];
		$this->customizations_locations = $data['customizations_locations'];
		$this->manifest = _FW_Component_Extensions::_get_manifest($this->get_name(), self::$access_key);
	}

	/**
	 * Cache key for this extension
	 *
	 * Usage:
	 * FW_Cache::get( $this->get_cache_key('/some/key') )
	 *
	 * @param string $sub_key
	 * @return string
	 */
	final public function get_cache_key($sub_key = '')
	{
		return self::$cache_key .'/'. $this->get_name() . $sub_key;
	}

	/**
	 * @param string $name View file name (without .php) from <extension>/views directory
	 * @param  array $view_variables Keys will be variables names within view
	 * @param   bool $return In some cases, for memory saving reasons, you can disable the use of output buffering
	 * @return string HTML
	 */
	final protected function render_view($name, $view_variables = array(), $return = true)
	{
		$full_path = $this->locate_path('/views/'. $name .'.php');

		if (!$full_path) {
			trigger_error('Extension view not found: '. $name, E_USER_WARNING);
			return '';
		}

		return fw_render_view($full_path, $view_variables, $return);
	}

	/**
	 * @internal
	 * @param FW_Access_Key $access_key
	 * @return mixed
	 */
	final public function _call_init($access_key)
	{
		if ($access_key->get_key() !== 'fw_extensions') {
			trigger_error(__METHOD__ .' denied', E_USER_ERROR);
		}

		return $this->_init();
	}

	/**
	 * Tree array with all sub extensions
	 * @return array
	 */
	final public function get_tree()
	{
		return fw()->extensions->_get_extension_tree(self::$access_key, $this->get_name());
	}

	/**
	 * @param string $rel_path '/views/test.php'
	 * @return false|string    '/var/www/.../extensions/<extension>/views/test.php'
	 */
	final public function locate_path($rel_path)
	{
		$locations = $this->customizations_locations;
		$locations[$this->get_path()] = $this->get_uri();

		foreach ($locations as $path => $uri) {
			if (file_exists($path . $rel_path)) {
				return $path . $rel_path;
			}
		}

		return false;
	}

	final public function locate_URI($rel_path)
	{
		$locations = $this->customizations_locations;
		$locations[$this->get_path()] = $this->get_uri();

		foreach ($locations as $path => $uri) {
			if (file_exists($path . $rel_path)) {
				return $uri . $rel_path;
			}
		}

		return false;
	}

	/**
	 * @return FW_Extension|null if has no parent extension
	 */
	final public function get_parent()
	{
		return $this->parent;
	}

	/**
	 * @return string
	 */
	final public function get_name()
	{
		if ($this->name === null) {
			$this->name = basename($this->path);
		}

		return $this->name;
	}

	/**
	 * @return string
	 * @deprecated
	 */
	final public function get_declared_source()
	{
		return 'deprecated';
	}

	/**
	 * @param string $append_rel_path E.g. '/includes/something.php'
	 * @return string
	 * @deprecated
	 */
	final public function get_declared_path($append_rel_path = '')
	{
		return $this->get_path($append_rel_path);
	}

	final public function get_path($append_rel_path = '')
	{
		return $this->path . $append_rel_path;
	}

	/**
	 * @param string $append_rel_path E.g. '/includes/foo/bar/script.js'
	 * @return string
	 * @deprecated
	 */
	final public function get_declared_URI($append_rel_path = '')
	{
		return $this->get_uri($append_rel_path);
	}

	/**
	 * @param string $append_rel_path E.g. '/includes/foo/bar/script.js'
	 * @return string
	 */
	final public function get_uri($append_rel_path = '')
	{
		return $this->uri . $append_rel_path;
	}

	/**
	 * @param string $child_extension_name
	 * @return FW_Extension|null
	 */
	final public function get_child($child_extension_name)
	{
		$active_tree = $this->get_tree();

		if (isset($active_tree[$child_extension_name])) {
			return fw()->extensions->get($child_extension_name);
		} else {
			return null;
		}
	}

	/**
	 * Return all child extensions
	 * Only one level, not all sub levels
	 * @return FW_Extension[]
	 */
	final public function get_children()
	{
		$active_tree = $this->get_tree();

		$result = array();

		foreach ($active_tree as $extension_name => &$sub_extensions) {
			$result[$extension_name] = fw()->extensions->get($extension_name);
		}
		unset($sub_extensions);

		return $result;
	}
	final public function get_config($key = null)
	{
		$cache_key = $this->get_cache_key() .'/config';

		try {
			$config = FW_Cache::get($cache_key);
		} catch (FW_Cache_Not_Found_Exception $e) {
			$config = array();

			$locations = $this->customizations_locations;
			$locations[$this->get_path()] = $this->get_uri();

			foreach (array_reverse($locations) as $path => $uri) {
				$config_path = $path .'/config.php';

				if (file_exists($config_path)) {
					$variables = fw_get_variables_from_file($config_path, array('cfg' => null));

					if (!empty($variables['cfg'])) {
						$config = array_merge($config, $variables['cfg']);
						unset($variables);
					}
				}
			}

			FW_Cache::set($cache_key, $config);
		}

		return $key === null ? $config : fw_call( fw_akg( $key, $config ) );
	}

	/**
	 * Return array with options from specified name/path
	 * @param string $name Examples: 'framework', 'posts/portfolio'
	 * @param array $variables These will be available in options file (like variables for view)
	 * @return array
	 */
	final public function get_options($name, array $variables = array())
	{
		try {
			return FW_Cache::get($cache_key = $this->get_cache_key('/options/'. $name));
		} catch (FW_Cache_Not_Found_Exception $e) {
			if ($path = $this->locate_path('/options/'. $name .'.php')) {
				$variables = fw_get_variables_from_file($path, array('options' => array()), $variables);
			} else {
				$variables = array('options' => array());
			}

			FW_Cache::set($cache_key, $variables['options']);

			return $variables['options'];
		}
	}

	final public function get_settings_options()
	{
		try {
			return FW_Cache::get($cache_key = $this->get_cache_key('/settings_options'));
		} catch (FW_Cache_Not_Found_Exception $e) {
			if (file_exists($path = $this->get_path('/settings-options.php'))) {
				$variables = fw_get_variables_from_file($path, array('options' => array()));
			} else {
				$variables = array('options' => array());
			}

			FW_Cache::set($cache_key, $variables['options']);

			return $variables['options'];
		}
	}

	/**
	 * @since 2.6.9
	 */
	final public function get_rendered_docs() {
		$docs_path = $this->get_path('/readme.md.php');

		if (! file_exists($docs_path)) {
			return false;
		}

		return fw()->backend->get_markdown_parser()->text(
			/**
			 * TODO: Perhaps send here some values in order to make extension docs
			 * more dynamic???
			 */
			fw_render_view($docs_path, array())
		);
	}

	/**
	 * Get extension's settings option value from the database
	 *
	 * @param string|null $option_id
	 * @param null|mixed $default_value If no option found in the database, this value will be returned
	 * @param null|bool $get_original_value REMOVED  /issues/1676
	 *
	 * @return mixed|null
	 */
	final public function get_db_settings_option( $option_id = null, $default_value = null, $get_original_value = null ) {

		return fw_get_db_ext_settings_option( $this->get_name(), $option_id, $default_value, $get_original_value );
	}

	/**
	 * Set extension's setting option value in database
	 *
	 * @param string|null $option_id
	 * @param mixed $value
	 */
	final public function set_db_settings_option( $option_id = null, $value = '' ) {
		fw_set_db_ext_settings_option( $this->get_name(), $option_id, $value );
	}

	/**
	 * Get extension's data from the database
	 *
	 * @param string|null $multi_key The key of the data you want to get. null - all data
	 * @param null|mixed $default_value If no option found in the database, this value will be returned
	 * @param null|bool $get_original_value REMOVED  /issues/1676
	 *
	 * @return mixed|null
	 */
	final public function get_db_data( $multi_key = null, $default_value = null, $get_original_value = null ) {
		return fw_get_db_extension_data( $this->get_name(), $multi_key, $default_value, $get_original_value );
	}

	/**
	 * Set some extension's data in database
	 *
	 * @param string|null $multi_key The key of the data you want to set. null - all data
	 * @param mixed $value
	 */
	final public function set_db_data( $multi_key = null, $value = '' ) {
		fw_set_db_extension_data( $this->get_name(), $multi_key, $value );
	}

	/**
	 * Get extension's data from user meta
	 *
	 * @param int $user_id
	 * @param string|null $keys
	 *
	 * @return mixed|null
	 */
	final public function get_user_data( $user_id, $keys = null ) {
		return fw_get_db_extension_user_data($user_id, $this->get_name(), $keys);
	}

	/**
	 * et some extension's data in user meta
	 *
	 * @param int $user_id
	 * @param mixed $value
	 * @param string|null $keys
	 *
	 * @return bool|int
	 */
	final public function set_user_data( $user_id, $value, $keys = null ) {
		return fw_set_db_extension_user_data($user_id, $this->get_name(), $value, $keys);
	}

	final public function get_post_options($post_type)
	{
		return $this->get_options('posts/'. $post_type);
	}

	final public function get_taxonomy_options($taxonomy)
	{
		return $this->get_options('taxonomies/'. $taxonomy);
	}

	/**
	 * @param string $name File name without extension, located in <extension>/static/js/$name.js
	 * @return string URI
	 */
	final public function locate_js_URI($name)
	{
		return $this->locate_URI('/static/js/'. $name .'.js');
	}

	/**
	 * @param string $name File name without extension, located in <extension>/static/js/$name.js
	 * @return string URI
	 */
	final public function locate_css_URI($name)
	{
		return $this->locate_URI('/static/css/'. $name .'.css');
	}

	/**
	 * @param string $name File name without extension, located in <extension>/views/$name.php
	 * @return false|string
	 */
	final public function locate_view_path($name)
	{
		return $this->locate_path('/views/'. $name .'.php');
	}

	final public function get_depth()
	{
		return $this->depth;
	}

	final public function get_customizations_locations()
	{
		return $this->customizations_locations;
	}

	final public function get_rel_path()
	{
		return $this->rel_path;
	}

	/**
	 * Check if child extension is valid
	 *
	 * Used for special cases when an extension requires its child extensions to extend some special class
	 *
	 * @param FW_Extension $child_extension_instance
	 * @return bool
	 * @internal
	 */
	public function _child_extension_is_valid($child_extension_instance)
	{
		return is_subclass_of($child_extension_instance, 'FW_Extension');
	}

	/**
	 * Get link to the page created by this extension in dashboard
	 * (Used on the extensions page)
	 * @internal
	 * @return string
	 */
	public function _get_link()
	{
		return false;
	}
}
