<?php
/**
 * Manages config collection.
 *
 * @package SmartCrawl
 */

namespace SmartCrawl\Configs;

use SmartCrawl\Logger;
use SmartCrawl\Singleton;
use function smartcrawl_get_array_value;

/**
 * Config Collection class.
 */
class Collection {

	use Singleton;

	const CONFIGS_INDEX_OPTION_ID = 'wds-configs-index';

	const CONFIG_OPTION_ID_PREFIX = 'wds-config-';

	/**
	 * Configs.
	 *
	 * @var Model[]
	 */
	private $configs = array();

	/**
	 * Includes deleted configs.
	 *
	 * @var array
	 */
	private $deleted = array();

	/**
	 * Config Service object.
	 *
	 * @var Service
	 */
	private $service;

	/**
	 * Constructor.
	 */
	protected function __construct() {
		$this->service = new Service();
		$this->load_from_storage();
	}

	/**
	 * Adds config.
	 *
	 * @param Model $config Config model.
	 */
	public function add( $config ) {
		$this->configs[ $this->key( $config ) ] = $config;
	}

	/**
	 * Removes a config.
	 *
	 * @param Model $config Config to be removed.
	 *
	 * @return void
	 */
	public function remove( $config ) {
		$config_key = $this->key( $config );

		if ( array_key_exists( $config_key, $this->configs ) ) {
			unset( $this->configs[ $config_key ] );
			$this->deleted[ $config_key ] = $config;
		}
	}

	/**
	 * Retrieves key.
	 *
	 * @param Model $config Config model.
	 *
	 * @return string
	 */
	private function key( $config ) {
		return $this->id_to_key( $config->get_id() );
	}

	/**
	 * Retrieves config key from ID.
	 *
	 * @param string $id Config ID.
	 *
	 * @return string
	 */
	private function id_to_key( $id ) {
		return 'config-' . $id;
	}

	/**
	 * Loads config from database.
	 *
	 * @return void
	 */
	private function load_from_storage() {
		$config_ids = $this->get_stored_config_ids();

		foreach ( $config_ids as $config_id ) {
			$config_data = $this->get_stored_config_data( $config_id );
			$config      = Model::inflate( $config_data );

			if ( $config->get_id() ) {
				$this->add( $config );
			}
		}
	}

	/**
	 * Saves configs.
	 *
	 * @return bool
	 */
	public function save() {
		$config_ids = array();
		foreach ( $this->configs as $config ) {
			$this->save_config_data_to_storage( $config );
			$config_ids[] = $config->get_id();
		}

		foreach ( $this->deleted as $deleted_key => $deleted_config ) {
			$this->delete_config_data_from_storage( $deleted_config );
			unset( $this->deleted[ $deleted_key ] );
		}

		return update_option( self::CONFIGS_INDEX_OPTION_ID, $config_ids, false );
	}

	/**
	 * Retrieves stored config ids.
	 *
	 * @return array
	 */
	private function get_stored_config_ids() {
		$option = get_option( self::CONFIGS_INDEX_OPTION_ID, array() );

		return empty( $option )
			? array()
			: $option;
	}

	/**
	 * Retrieves stored config data by config ID.
	 *
	 * @param string $config_id Config ID.
	 *
	 * @return array
	 */
	private function get_stored_config_data( $config_id ) {
		$config_data = get_option( $this->config_option_id( $config_id ), array() );

		return empty( $config_data )
			? array()
			: $config_data;
	}

	/**
	 * Saves a config data.
	 *
	 * @param Model $config Config model.
	 *
	 * @return bool
	 */
	private function save_config_data_to_storage( $config ) {
		return update_option( $this->config_option_id( $config->get_id() ), $config->deflate(), false );
	}

	/**
	 * Deletes a config from database.
	 *
	 * @param Model $config Config data.
	 *
	 * @return bool
	 */
	private function delete_config_data_from_storage( $config ) {
		$config_id = $config->get_id();

		if ( ! $config_id ) {
			return false;
		}

		return delete_option( $this->config_option_id( $config_id ) );
	}

	/**
	 * Returns config option id.
	 *
	 * @param string $config_id Config ID.
	 *
	 * @return string
	 */
	private function config_option_id( $config_id ) {
		return self::CONFIG_OPTION_ID_PREFIX . $config_id;
	}

	/**
	 * Returns configs.
	 *
	 * @return Model[]
	 */
	public function get_configs() {
		return empty( $this->configs ) ? array() : $this->configs;
	}

	/**
	 * Sorts configs by comparator and return it.
	 *
	 * @return Model[]
	 */
	public function get_sorted_configs() {
		$configs = $this->get_configs();
		uasort( $configs, array( $this, 'comparator' ) );

		return $configs;
	}

	/**
	 * Comparison function.
	 *
	 * @param Model $first Config model.
	 * @param Model $second Config model.
	 *
	 * @return int
	 */
	private function comparator( $first, $second ) {
		if ( $first->get_timestamp() === $second->get_timestamp() ) {
			return 0;
		}

		return ( $first->get_timestamp() < $second->get_timestamp() ) ? 1 : - 1;
	}

	/**
	 * Retrieves deflated configs.
	 *
	 * @return array
	 */
	public function get_deflated_configs() {
		return array_map(
			function ( $config ) {
				return $config->deflate();
			},
			$this->get_sorted_configs()
		);
	}

	/**
	 * Retrieves config by id.
	 *
	 * @param string $config_id Config ID.
	 *
	 * @return Model|null
	 */
	public function get_by_id( $config_id ) {
		return smartcrawl_get_array_value( $this->configs, $this->id_to_key( $config_id ) );
	}

	/**
	 * Retrieves config by hub ID.
	 *
	 * @param string $hub_id Hub ID.
	 *
	 * @return Model|null
	 */
	public function get_by_hub_id( $hub_id ) {
		if ( ! $hub_id ) {
			return null;
		}

		foreach ( $this->get_configs() as $config ) {
			if ( $config->get_hub_id() === $hub_id ) {
				return $config;
			}
		}

		return null;
	}

	/**
	 * Set service.
	 *
	 * @param Service $service Service.
	 */
	public function set_service( $service ) {
		$this->service = $service;
	}

	/**
	 * Resets configs to empty.
	 *
	 * @return void
	 */
	public function reset() {
		$this->configs = array();
		self::get( true );
	}

	/**
	 * Retrieves Hub configs.
	 *
	 * @return array|false
	 */
	private function get_hub_configs() {
		$hub_configs_array = $this->service->get_configs();

		if ( empty( $hub_configs_array ) ) {
			return false;
		}

		$hub_configs = array();

		foreach ( $hub_configs_array as $hub_config_data ) {
			$hub_config = Model::create_from_hub_data( $hub_config_data );

			if ( ! $hub_config || ! $hub_config->get_hub_id() ) {
				return false;
			}

			$hub_configs[ $this->id_to_key( $hub_config->get_hub_id() ) ] = $hub_config;
		}

		return $hub_configs;
	}
	/**
	 * Update config option wds_blog_tabs settings.
	 *
	 * @param string $config_id Config Model ID.
	 * @param array  $config_data Config Data.
	 *
	 * @return void
	 */
	public function update_config_blog_tabs_settings( $config_id, $config_data ) {
		update_option( $this->config_option_id( $config_id ), $config_data, false );
	}
}
