<?php
/** 
 * @package     VikRestaurants
 * @subpackage  core
 * @author      E4J s.r.l.
 * @copyright   Copyright (C) 2023 E4J s.r.l. All Rights Reserved.
 * @license     http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 * @link        https://vikwp.com
 */

namespace E4J\VikRestaurants\Cron;

// No direct access
defined('ABSPATH') or die('No script kiddies please!');

use E4J\VikRestaurants\DI\Container;
use E4J\VikRestaurants\DI\ContainerDecorator;

/**
 * This class can be used to easily instantiate the supported cron jobs.
 *
 * @since 1.9
 */
class Dispatcher
{
    /**
     * The object holding all the registered cron jobs.
     * 
     * @var Container
     */
    private $container;

    /**
     * Class constructor.
     * 
     * @param  Container  $container  The container holding the registered cron job providers.
     */
    public function __construct(Container $container)
    {
        $this->container = $container;
    }

    /**
     * Returns the container holding all the registered cron jobs.
     * 
     * @return  Container
     */
    final public function getContainer()
    {
        return $this->container;
    }

    /**
     * Registers a new cron job provider for lazy initialization.
     * 
     * @param   string    $cron      The cron name.
     * @param   callable  $provider  The provider callback.
     * @param   array     $options   An array of preferences to choose whether the
     *                               resource should be `shared` or `protected`.
     * 
     * @return  self      This object to support chaining.
     */
    final public function registerCronProvider(string $cron, $provider, array $options = [])
    {
        $options = new \JObject($options);

        /** @var E4J\VikRestaurants\DI\Resource */
        $this->container->set($cron, $provider)
            ->share($options->get('shared', false))
            ->protect($options->get('protected', false));

        return $this;
    }

    /**
     * Returns the cron job registered with the specified ID.
     * 
     * @param   string  $cron  The cron driver identifier.
     * @param   ?int    $id    The ID of cron job.
     * 
     * @return  Runnable
     * 
     * @throws  \Exception
     */
    public function getCron(string $cron, ?int $id = null)
    {
        if (!$this->container->has($cron))
        {
            // cron job not found
            throw new \Exception(sprintf('Cron job [%s] not found', $cron), 404);
        }

        /** @var Runnable */
        $runnable = $this->container->get($cron, $id);

        // make sure we have a valid runnable instance
        if (!$runnable instanceof Runnable)
        {
            // invalid cron
            throw new \UnexpectedValueException(sprintf('Cron job [%s] is not valid', $cron), 406);
        }

        return $runnable;
    }

    /**
     * Returns a list containing all the supported cron jobs.
     *
     * @return  Runnable[]
     */
    public function getSupportedCrons()
    {
        $crons = [];
        
        foreach ($this->container->keys() as $id)
        {
            try
            {
                /** @var Runnable */
                $cron = $this->getCron($id);

                if (!$cron->isSupported())
                {
                    // ignore in case the cron is not supported
                    continue;
                }

                if (!$cron instanceof Configurable)
                {
                    // only configurable cron jobs can be registered here
                    continue;
                }

                // register cron within the map
                $crons[$id] = $cron;
            }
            catch (\Exception $e)
            {
                // ignore cron job
            }
        }

        // sort cron jobs by title
        uasort($crons, function($a, $b)
        {
            return strcmp($a->getTitle(), $b->getTitle());
        });

        return $crons;
    } 
}
