<?php

/**
 * Matomo - free/libre analytics platform
 *
 * @link    https://matomo.org
 * @license https://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
 */
namespace Piwik\Plugin;

use Piwik\Log;
use Piwik\Plugin\Manager as PluginManager;
use Exception;
/**
 * Factory class with methods to find and instantiate Plugin components.
 */
class ComponentFactory
{
    /**
     * Create a component instance that exists within a specific plugin. Uses the component's
     * unqualified class name and expected base type.
     *
     * This method will only create a class if it is located within the component type's
     * associated subdirectory.
     *
     * @param string $pluginName The name of the plugin the component is expected to belong to,
     *                           eg, `'DevicesDetection'`.
     * @param string $componentClassSimpleName The component's class name w/o namespace, eg,
     *                                         `"GetKeywords"`.
     * @param string $componentTypeClass The fully qualified class name of the component type, eg,
     *                                   `"Piwik\Plugin\Report"`.
     *
     * @template T of object
     * @phpstan-param class-string<T> $componentTypeClass
     *
     * @return T|null A new instance of the desired component or null if not found. If the
     *                plugin is not loaded or activated or the component is not located in
     *                in the sub-namespace specified by `$componentTypeClass::COMPONENT_SUBNAMESPACE`,
     *                this method will return null.
     */
    public static function factory($pluginName, $componentClassSimpleName, $componentTypeClass)
    {
        if (empty($pluginName) || empty($componentClassSimpleName)) {
            Log::debug("ComponentFactory::%s: empty plugin name or component simple name requested (%s, %s)", __FUNCTION__, $pluginName, $componentClassSimpleName);
            return null;
        }
        $plugin = self::getActivatedPlugin(__FUNCTION__, $pluginName);
        if (empty($plugin)) {
            return null;
        }
        $subnamespace = $componentTypeClass::COMPONENT_SUBNAMESPACE;
        $desiredComponentClass = 'Piwik\\Plugins\\' . $pluginName . '\\' . $subnamespace . '\\' . $componentClassSimpleName;
        $components = $plugin->findMultipleComponents($subnamespace, $componentTypeClass);
        foreach ($components as $class) {
            if ($class === $desiredComponentClass) {
                return new $class();
            }
        }
        Log::debug("ComponentFactory::%s: Could not find requested component (args = ['%s', '%s', '%s']).", __FUNCTION__, $pluginName, $componentClassSimpleName, $componentTypeClass);
        return null;
    }
    /**
     * Finds a component instance that satisfies a given predicate.
     *
     * @param string $componentTypeClass The fully qualified class name of the component type, eg,
     *                                   `"Piwik\Plugin\Report"`.
     * @param string $pluginName|false The name of the plugin the component is expected to belong to,
     *                                 eg, `'DevicesDetection'`.
     * @param callable $predicate
     *
     * @template T of object
     * @phpstan-param class-string<T> $componentTypeClass
     * @phpstan-param callable(T): bool $predicate
     *
     * @return T|null The component that satisfies $predicate or null if not found.
     */
    public static function getComponentIf($componentTypeClass, $pluginName, $predicate)
    {
        $pluginManager = PluginManager::getInstance();
        // get components to search through
        $subnamespace = $componentTypeClass::COMPONENT_SUBNAMESPACE;
        if (empty($pluginName)) {
            $components = $pluginManager->findMultipleComponents($subnamespace, $componentTypeClass);
        } else {
            $plugin = self::getActivatedPlugin(__FUNCTION__, $pluginName);
            if (empty($plugin)) {
                return null;
            }
            $components = $plugin->findMultipleComponents($subnamespace, $componentTypeClass);
        }
        // find component that satisfieds predicate
        foreach ($components as $class) {
            $component = new $class();
            if ($predicate($component)) {
                return $component;
            }
        }
        Log::debug("ComponentFactory::%s: Could not find component that satisfies predicate (args = ['%s', '%s', '%s']).", __FUNCTION__, $componentTypeClass, $pluginName, get_class($predicate));
        return null;
    }
    /**
     * @param string $function
     * @param string $pluginName
     * @return null|\Piwik\Plugin
     */
    private static function getActivatedPlugin($function, $pluginName)
    {
        $pluginManager = PluginManager::getInstance();
        try {
            if (!$pluginManager->isPluginActivated($pluginName)) {
                Log::debug("ComponentFactory::%s: component for deactivated plugin ('%s') requested.", $function, $pluginName);
                return null;
            }
            return $pluginManager->getLoadedPlugin($pluginName);
        } catch (Exception $e) {
            Log::debug($e);
            return null;
        }
    }
}
