<?php
/**
 * @package   FOF
 * @copyright Copyright (c)2010-2021 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 2, or later
 */

namespace FOF40\Utils;

defined('_JEXEC') || die;

use Exception;
use FOF40\Container\Container;
use JDatabaseDriver;
use Joomla\CMS\Factory;
use Joomla\Registry\Registry;

/**
 * Class MediaVersion
 *
 * @since   3.5.3
 */
class MediaVersion
{
	/**
	 * Cached the version and date of FOF-powered components
	 *
	 * @var   array
	 * @since 3.5.3
	 */
	protected static $componentVersionCache = [];

	/**
	 * @var string|mixed
	 */
	public $componentName;

	/**
	 * The current component's container
	 *
	 * @var   Container
	 * @since 3.5.3
	 */
	protected $container;

	/**
	 * The configured media query version
	 *
	 * @var   string|null;
	 * @since 3.5.3
	 */
	protected $mediaVersion;

	/**
	 * MediaVersion constructor.
	 *
	 * @param   Container  $c  The component container
	 *
	 * @since   3.5.3
	 */
	public function __construct(Container $c)
	{
		$this->container = $c;
	}

	/**
	 * Get a component's version and date
	 *
	 * @param   string           $component
	 * @param   JDatabaseDriver  $db
	 *
	 * @return  array
	 * @since   3.5.3
	 */
	protected static function getComponentVersionAndDate(string $component, JDatabaseDriver $db): array
	{
		if (array_key_exists($component, self::$componentVersionCache))
		{
			return self::$componentVersionCache[$component];
		}

		$version = '0.0.0';
		$date    = date('Y-m-d H:i:s');

		try
		{
			$query = $db->getQuery(true)
				->select([
					$db->qn('manifest_cache'),
				])->from($db->qn('#__extensions'))
				->where($db->qn('type') . ' = ' . $db->q('component'))
				->where($db->qn('name') . ' = ' . $db->q($component));

			$db->setQuery($query);

			$json   = $db->loadResult() ?? '{}';
			$params = new Registry($json);

			$version = $params->get('version', $version);
			$date    = $params->get('creationDate', $date);
		}
		catch (Exception $e)
		{
		}

		self::$componentVersionCache[$component] = [$version, $date];

		return self::$componentVersionCache[$component];
	}

	/**
	 * Serialization helper
	 *
	 * This is for the benefit of legacy components which might use Joomla's JS/CSS inclusion directly passing
	 * $container->mediaVersion as the version argument. In FOF 3.5.2 and lower that was always string or null, making
	 * it a safe bet. In FOF 3.5.3 and later it's an object. It's not converted to a string until Joomla builds its
	 * template header. However, Joomla's cache system will try to serialize all CSS and JS definitions, including their
	 * parameters of which version is one. Therefore, for those legacy applications, Joomla would be trying to serialize
	 * the MediaVersion object which would try to serialize the container. That would cause an immediate failure since
	 * we protect the Container from being serialized.
	 *
	 * Our Template service knows about this and stringifies the MediaVersion before passing it to Joomla. Legacy apps
	 * may not do that. Using the __sleep and __wakeup methods in this class we make sure that we are essentially
	 * storing nothing but strings in the serialized representation and we reconstruct the container upon
	 * unseralization. That said, it's a good idea to use the Template service instead of $container->mediaVersion
	 * directly or, at the very least, use (string) $container->mediaVersion when using the Template service is not a
	 * viable option.
	 *
	 * @return  string[]
	 */
	public function __sleep(): array
	{
		$this->componentName = $this->container->componentName;

		return [
			'mediaVersion',
			'componentName',
		];
	}

	/**
	 * Unserialization helper
	 *
	 * @return  void
	 * @see     __sleep
	 */
	public function __wakeup(): void
	{
		if (isset($this->componentName))
		{
			$this->container = Container::getInstance($this->componentName);
		}
	}

	/**
	 * Returns the media query version string
	 *
	 * @return  string
	 * @since   3.5.3
	 */
	public function __toString(): string
	{
		if (empty($this->mediaVersion))
		{
			$this->mediaVersion = $this->getDefaultMediaVersion();
		}

		return $this->mediaVersion;
	}

	/**
	 * Sets the media query version string
	 *
	 * @param   string|null  $mediaVersion
	 *
	 * @since   3.5.3
	 */
	public function setMediaVersion(?string $mediaVersion): void
	{
		$this->mediaVersion = $mediaVersion;
	}

	/**
	 * Returns the default media query version string if none is already defined
	 *
	 * @return  string
	 * @since   3.5.3
	 */
	protected function getDefaultMediaVersion(): string
	{
		// Initialise
		[$version, $date] = self::getComponentVersionAndDate($this->container->componentName, $this->container->db);

		// Get the site's secret
		try
		{
			$app = Factory::getApplication();

			if (method_exists($app, 'get'))
			{
				$secret = $app->get('secret');
			}
		}
		catch (Exception $e)
		{
		}

		// Generate the version string
		return md5($version . $date . $secret);
	}
}