<?php
/**
 * Cache for results.
 *
 * @package Ealab\Deployer
 */

namespace Ealab\Deployer\Utility;

use Dakwamine\Component\ComponentBasedObject;

/**
 * Cache for results.
 *
 * @package Ealab\Deployer
 */
class ResultsCache extends ComponentBasedObject {
	/**
	 * The cached results.
	 *
	 * @var array
	 */
	protected $cached_results = array();

	/**
	 * The current popularity index for each category.
	 *
	 * @var array
	 */
	protected $category_current_popularity_index = array();

	/**
	 * Maximum number of cached results per category.
	 *
	 * @var int
	 */
	protected $max_cached_results = 500;

	/**
	 * Clears all cached results.
	 */
	public function clearAllCachedResults(): void {
		$this->cached_results = array();
	}

	/**
	 * Clears a cached result.
	 *
	 * @param string $category The category of the cached result.
	 * @param string $key The key of the cached result.
	 */
	public function clearCachedResults( string $category, string $key ): void {
		unset( $this->cached_results[ $category ][ $key ] );
	}

	/**
	 * Clears all cached results for a category.
	 *
	 * @param string $category The category of the cached result.
	 */
	public function clearCachedResultsByCategory( string $category ): void {
		$this->cached_results[ $category ] = array();
	}

	/**
	 * Gets a cached result.
	 *
	 * @param string     $category The category of the cached result.
	 * @param string     $key The key of the cached result.
	 * @param mixed|null $return_on_unset The value to return if the cached result is not set.
	 *   Defaults to null.
	 *
	 * @return mixed|null The cached result or $return_on_unset if the cached result is not set.
	 */
	public function getCachedResults( string $category, string $key, $return_on_unset = null ) {
		if ( ! isset( $this->cached_results[ $category ][ $key ]['value'] ) ) {
			return $return_on_unset;
		}

		++$this->cached_results[ $category ][ $key ]['popularity'];

		return $this->cached_results[ $category ][ $key ]['value'];
	}

	/**
	 * Sets a cached result.
	 *
	 * @param string $category The category of the cached result. It could be the name
	 *   of the class or the method that is caching the result.
	 * @param string $key The key of the cached result.
	 * @param mixed  $value The value of the cached result.
	 */
	public function setCachedResults( string $category, string $key, $value ): void {
		if ( ! isset( $this->cached_results[ $category ][ $key ] ) ) {
			// This is a new entry.
			if ( count( $this->cached_results[ $category ] ?? array() ) >= $this->max_cached_results ) {
				// The cache is full, remove the 20% least popular entries.
				$sorted = $this->cached_results[ $category ];

				uasort( $sorted, function ( $a, $b ) {
					return $a['popularity'] <=> $b['popularity'];
				} );

				$sorted = array_slice( $sorted, ceil( $this->max_cached_results * 0.2 ) );

				$this->cached_results[ $category ] = $sorted;

				// Update the current popularity index.
				$this->category_current_popularity_index[ $category ] = end( $sorted )['popularity'] ?? 0;
			}
		}

		$this->cached_results[ $category ][ $key ]['value']      = $value;
		$this->cached_results[ $category ][ $key ]['popularity'] = $this->category_current_popularity_index[ $category ] ?? 0;
	}
}
