<?php
/**
 * YAWIK
 *
 * @filesource
 * @license    MIT
 * @copyright  2013 - 2016 Cross Solution <http://cross-solution.de>
 */

/** */
namespace CoreTestUtils\Mock\ServiceManager;

use PHPUnit\Framework\TestCase;

use Interop\Container\ContainerInterface;
use Laminas\ServiceManager\AbstractPluginManager;
use Laminas\ServiceManager\Exception;
use Laminas\ServiceManager\ServiceLocatorInterface;

/**
 * Mock of a plugin manager.
 *
 * Allows tracking of method calls (with service name arguments and even passed options array).
 *
 * @author Mathias Gelhausen <gelhausen@cross-solution.de>
 * @author Anthonius Munthi <me@itstoni.com>
 *
 * @since  0.25
 */
class PluginManagerMock extends AbstractPluginManager
{
    protected $callCount = [
        'get' => [],
        'has' => [],
        'getServiceLocator' => [],
    ];

    protected $expectedCallCount = [];

    public function validate($plugin)
    {
        // all plugins are valid. We do not care here!
    }

    /**
     * Sets the parent service locator.
     *
     * @param ServiceLocatorInterface $serviceLocator
     * @param int                     $count expected call count for getServiceLocator
     *
     * @return AbstractPluginManager
     * @TODO: [ZF3] service locator should be removed in zf3
     */
    //public function setServiceLocatorDeprecated(ContainerInterface $container, $count = 1)
    //{
    //    $this->setExpectedCallCount('getServiceLocator', $count);
    //
    //    return parent::setServiceLocator($container);
    //}

    //public function getServiceLocator()
    //{
    //    $this->incrementCallCount('getServiceLocator');
    //    return parent::getServiceLocator(); // TODO: Change the autogenerated stub
    //}


    public function get($name, array $options = null, $usePeeringServiceManagers = true)
    {
        $this->incrementCallCount('get', $name, $options);

        return parent::get($name, $options, $usePeeringServiceManagers);
    }

    public function has($name, $checkAbstractFactories = true, $usePeeringServiceManagers = true)
    {
        if (is_string($name)) { // internally called with an array [normalizedName, requestedName].
            $this->incrementCallCount('has', $name);
        }

        return parent::has($name, $checkAbstractFactories, $usePeeringServiceManagers);
    }

    /**
     * Increment the call count.
     *
     * @param string $method
     * @param string|int $key
     * @param null|array|int $options
     * @param int    $count
     */
    protected function incrementCallCount($method, $key = 'none', $options = null, $count = 1)
    {
        if (is_int($key)) {
            $count = $key;
            $key = 'none';
        } elseif (is_int($options)) {
            $count = $options;
            $options = null;
        }

        $optionsHash = $this->getOptionsHash($options);

        if (isset($this->callCount[$method][$key][$optionsHash])) {
            $this->callCount[$method][$key][$optionsHash] += $count;
        } else {
            $this->callCount[$method][$key][$optionsHash] = $count;
        }
    }

    protected function getOptionsHash($options)
    {
        return null === $options ? 'none' : md5(serialize($options));
    }

    /**
     * Sets the expected call count.
     *
     * @param string          $method
     * @param string|int $key
     * @param int|array|null $options
     * @param int             $count
     */
    public function setExpectedCallCount($method, $key = 'none', $options = null, $count = 1)
    {
        if (is_int($key)) {
            $count = $key;
            $key   = '*';
            $options = '*';
        } elseif (is_int($options)) {
            $count = $options;
            $options = '*';
        } elseif (is_array($options)) {
            $options = $this->getOptionsHash($options);
        }

        $this->expectedCallCount[$method][$key][$options] = $count;
    }

    /**
     * Verifies the call count.
     *
     * @param null|string $method
     * @param null|string $service
     * @param null|array $options
     *
     * @throws \PHPUnit_Framework_AssertionFailedError
     */
    public function verifyCallCount($method = null, $service = null, $options = null)
    {
        $optHash = $this->getOptionsHash($options);

        foreach ($this->expectedCallCount as $methodName => $services) {
            if (null !== $method && $method != $methodName) {
                continue;
            }
            foreach ($services as $name => $optHashMap) {
                if (null !== $service && $service != $name) {
                    continue;
                }
                foreach ($optHashMap as $currentOptHash => $count) {
                    if (null !== $options && $currentOptHash != $optHash) {
                        continue;
                    }



                    $actual = $this->getCallCount($methodName, $name, $currentOptHash);

                    if ($actual != $count) {
                        throw new \PHPUnit_Framework_AssertionFailedError(
                            sprintf(
                                '%s::%s(%s) was expected to be called %d times%s, but was actually called %d times.',
                                get_class($this),
                                $methodName,
                                '*' == $name ? '' : $name,
                                $count,
                                null === $options ? '' : ' with options hash ' . $optHash,
                                $actual
                            )
                        );
                    }
                }
            }
        }
    }

    /**
     * Gets the actual call count
     *
     * @param string $method
     * @param null|string   $key
     * @param null|array|string $options
     *
     * @return int
     */
    public function getCallCount($method, $key = null, $options = null)
    {
        if (null === $key || '*' == $key) {
            return array_sum(array_map('array_sum', $this->callCount[$method]));
        }

        if (null === $options || '*' == $options) {
            return array_sum($this->callCount[$method][$key]);
        }

        $hash = is_array($options) ? $this->getOptionsHash($options) : $options;

        return isset($this->callCount[$method][$key][$hash]) ? $this->callCount[$method][$key][$hash] : 0;
    }
}
