<?php
/**
 * core.framework
 *
 * @category   Core
 * @package    Core_Plugin
 * @subpackage UnitTests
 * @copyright  Copyright (c) 2011. Burza d.o.o. (http://web.burza.hr/en/)
 * @license    proprietary
 */

/**
 * @category   Core
 * @package    Core_Plugin
 * @subpackage UnitTests
 * @copyright  Copyright (c) 2011. Burza d.o.o. (http://web.burza.hr/en/)
 * @license    proprietary
 * @group      Core_Plugin
 */
class Core_Plugin_ManagerTest extends CoreTest_Container_TestCase
{
    /**
     * @var Core_Plugin_Interface
     */
    protected $_owner;

    /**
     * @var Core_Plugin_Manager
     */
    protected $_object;

    public function __construct()
    {
        $this->_owner = $this->getMock('Core_Plugin_Interface');
    }

    protected function setUp()
    {
        $this->_object = new Core_Plugin_Manager($this->_owner, array(
            'Invalid_Plugin_Interface' => array(
                'InvalidPlugin',
                array(
                    'mapping' =>
                        array(
                            'Plugin'  => Core_Plugin_Manager::NAME_PLUGIN,
                            'Plugins' => Core_Plugin_Manager::NAME_PLUGINS,
                        ),
                )
            ),
        ));
    }

    protected function tearDown()
    {
        Core_Application::resetInstance();
    }

    /**
     * @covers Core_Plugin_Manager::__construct
     */
    public function testCanAddTypesViaConstructor()
    {
        $object = new Core_Plugin_Manager(
            $this->_owner,
            array(
                'Some_Class' => array(
                'ClassType',
                array(
                    'Element'  => Core_Plugin_Manager::NAME_PLUGIN,
                    'Elements' => Core_Plugin_Manager::NAME_PLUGINS,
                ),
            ))
        );

        $this->assertTrue($object->hasType('Some_Class'));
    }

    /**
     * @covers Core_Plugin_Manager::setOwner
     * @covers Core_Plugin_Manager::getOwner
     */
    public function testCanSetAndGetOwner()
    {
        $owner = $this->getMock('Core_Plugin_Interface');
        $this->_object->setOwner($owner);

        $this->assertEquals($owner, $this->_object->getOwner());
    }

    /**
     * @covers Core_Plugin_Manager::setOwner
     */
    public function testWhenSettingOwnerWillChangeItOnAllInstances()
    {
        $owner  = $this->getMock('Core_Plugin_Interface');

        $plugin = $this->_mockPlugin();
        $plugin
            ->expects($this->any())
            ->method('setOwner')
            ->with($this->equalTo($owner))
            ->will($this->returnValue($plugin));
        $this->_object->addPlugin('Invalid_Plugin_Interface', $plugin);

        $this->_object->setOwner($owner);
    }

    /**
     * @covers Core_Plugin_Manager::addType
     * @covers Core_Plugin_Manager::hasType
     */
    public function testCanAddType()
    {
        $this->_object->addType('Some_Class', 'ClassType', array());

        $this->assertTrue($this->_object->hasType('Some_Class'));
    }

    /**
     * @covers Core_Plugin_Manager::removeType
     * @depends testCanAddType
     */
    public function testCanRemoveType()
    {
        $this->_object->addType('Some_Class', 'ClassType', array());
        $this->_object->removeType('Some_Class');

        $this->assertFalse($this->_object->hasType('Some_Class'));
    }

    /**
     * @covers Core_Plugin_Manager::removeType
     */
    public function testRemovingAnUnknownTypeThrowsAnInvalidArgumentException()
    {
        $this->setExpectedException('InvalidArgumentException');

        $this->_object->removeType('Invalid_Class');
    }

    /**
     * @covers Core_Plugin_Manager::addTypes
     */
    public function testCanAddTypes()
    {
        $this->_object->addTypes(array('Some_Class' => array('ClassType', array())));

        $this->assertTrue($this->_object->hasType('Some_Class'));
    }

    /**
     * @covers Core_Plugin_Manager::setPluginLoader
     * @covers Core_Plugin_Manager::getPluginLoader
     */
    public function testCanSetAndGetPluginLoader()
    {
        $pluginLoader = $this->getMock('Core_Loader_PluginLoader');
        $this->_object->setPluginLoader($pluginLoader);
        $this->assertEquals($pluginLoader, $this->_object->getPluginLoader());
    }

    /**
     * @covers Core_Plugin_Manager::getPluginLoader
     */
    public function testIfNoPluginLoaderSetWillFetchOneFromContainerAsPluginLoader()
    {
        $pluginLoader = $this->getMock('Core_Loader_PluginLoader');
        $container    = $this->_mockContainer('PluginLoader', $pluginLoader);
        Core_Application::getInstance()->setContainer($container);

        $this->assertEquals($pluginLoader, $this->_object->getPluginLoader());
    }

    /**
     * @covers Core_Plugin_Manager::addPlugins
     */
    public function testAddingUnmanagedPluginsWillThrowsAnInvalidArgumentException()
    {
        $this->setExpectedException('InvalidArgumentException');
        $this->_object->addPlugins('Unknown_Plugin_Interface', array());
    }

    /**
     * @covers Core_Plugin_Manager::addPlugins
     */
    public function testCanAddPluginsAsInstances()
    {
        $this->_object->addPlugins('Invalid_Plugin_Interface', array(
            $this->_mockPlugin('someName1'),
            $this->_mockPlugin('someName2'),
        ));

        $this->assertTrue($this->_object->hasPlugin('Invalid_Plugin_Interface', 'SOMENAME1'));
        $this->assertTrue($this->_object->hasPlugin('Invalid_Plugin_Interface', 'somename2'));
    }

    /**
     * @covers Core_Plugin_Manager::addPlugins
     */
    public function testCanAddPluginsAsSpecs()
    {
        $plugin  = $this->_mockPlugin('someName');
        $pLoader = $this->_mockPluginLoader($plugin, 'InvalidPluginFoo', array(
            'opt'   => 'ions',
            'name'  => 'someName',
            'owner' => $this->_owner,
        ));
        $this->_object->setPluginLoader($pLoader);

        $this->_object->addPlugins('Invalid_Plugin_Interface', array(
            'someName' => array(
                'type'    => 'Foo',
                'options' => array(
                    'opt'   => 'ions',
                )
            ),
        ));
        $this->assertTrue($this->_object->hasPlugin('Invalid_Plugin_Interface', 'SOMENAME'));
    }

    /**
     * @covers Core_Plugin_Manager::addPlugins
     */
    public function testCanAddPluginsAsSpecsWithoutOptions()
    {
        $plugin  = $this->_mockPlugin('someName');
        $pLoader = $this->_mockPluginLoader($plugin, 'InvalidPluginFoo', array(
            'name'  => 'someName',
            'owner' => $this->_owner,
        ));
        $this->_object->setPluginLoader($pLoader);

        $this->_object->addPlugins('Invalid_Plugin_Interface', array(
            'someName' => array(
                'type'    => 'Foo',
            ),
        ));
        $this->assertTrue($this->_object->hasPlugin('Invalid_Plugin_Interface', 'SOMENAME'));
    }

    /**
     * @covers Core_Plugin_Manager::addPlugins
     */
    public function testAddingPluginsAsInstancesWithAnInvalidTypeThrowsAnInvalidArgumentException()
    {
        $this->setExpectedException('InvalidArgumentException');
        $this->_object->addPlugins('Invalid_Plugin_Interface', array(
            $this->getMock('Core_Plugin_Interface'),
        ));
    }


    /**
     * @covers Core_Plugin_Manager::addPlugins
     */
    public function testAddingPluginsAsSpecsWithNoTypeThrowsAnInvalidArgumentException()
    {
        $this->setExpectedException('InvalidArgumentException');
        $this->_object->addPlugins('Invalid_Plugin_Interface', array('someName' => array()));
    }

    /**
     * @covers Core_Plugin_Manager::addPlugin
     */
    public function testIfClassPassedToAddPluginIsNotManagedWillThrowAnInvalidArgumentException()
    {
        $this->setExpectedException('InvalidArgumentException');
        $plugin = $this->getMock('Core_Plugin_Interface');

        $this->_object->addPlugin('Core_Plugin_Interface', $plugin);
    }

    /**
     * @covers Core_Plugin_Manager::addPlugin
     */
    public function testIfPluginIsNotAnInstanceOfDeclaredClassAddPluginWillThrowAnInvalidArgumentException()
    {
        $this->setExpectedException('InvalidArgumentException');
        $plugin = $this->getMock('Core_Plugin_Interface');

        $this->_object->addPlugin('Invalid_Plugin_Interface', $plugin);
    }


    /**
     * @covers Core_Plugin_Manager::addPlugin
     */
    public function testAddingAPluginWillRunInitWithPassingOwner()
    {
        $owner  = $this->getMock('Core_Plugin_Interface');
        $this->_object->setOwner($owner);

        $plugin = $this->_mockPlugin();
        $plugin
            ->expects($this->once())
            ->method('setOwner')
            ->with($this->equalTo($owner));

        $this->_object->addPlugin('Invalid_Plugin_Interface', $plugin);
    }
    
    /**
     * @covers Core_Plugin_Manager::addPlugin
     * @covers Core_Plugin_Manager::getPlugins
     */
    public function testCanAddPluginsWhichAreNotManagedOnBehalfOfOwner()
    {
        $plugin = $this->_mockPlugin();
        $this->_object->addPlugin('Invalid_Plugin_Interface', $plugin, false);
        
        $this->assertEmpty($this->_object->getPlugins('Invalid_Plugin_Interface'));
    }

    /**
     * @covers Core_Plugin_Manager::addType
     * @covers Core_Plugin_Manager::addPlugin
     */
    public function testPluginNameMustBeUniqueAcrossAllConfiguredTypesWhenAddingOrAInvalidArgumentExceptionIsThrown()
    {
        $object = new Core_Plugin_Manager($this->_owner, array(
            // first interface, linked with the second
            'Invalid_Plugin_Interface' => array(
                'InvalidPlugin1',
                array(
                    'uniqueAcross' => array(
                        'Invalid_Plugin_Linked_Interface',
                    ),
                    'mapping' =>
                        array(
                            'Plugin'  => Core_Plugin_Manager::NAME_PLUGIN,
                            'Plugins' => Core_Plugin_Manager::NAME_PLUGINS,
                        ),
                )
            ),
            // second interface, linked with the first
            'Invalid_Plugin_Linked_Interface' => array(
                'InvalidPlugin2',
                array(
                    'uniqueAcross' => array(
                        'Invalid_Plugin_Interface',
                    ),
                    'mapping' =>
                        array(
                            'Plugin'  => Core_Plugin_Manager::NAME_PLUGIN,
                            'Plugins' => Core_Plugin_Manager::NAME_PLUGINS,
                        ),
                )
            ),
        ));

        // same name, different type
        $plugin1 = $this->_mockPlugin('sameName', 'Invalid_Plugin_Interface');
        $plugin2 = $this->_mockPlugin('sameName', 'Invalid_Plugin_Linked_Interface');
        $object->addPlugin('Invalid_Plugin_Interface', $plugin1);

        // even if different type, the name must be unique across types
        $this->setExpectedException('InvalidArgumentException');
        $object->addPlugin('Invalid_Plugin_Linked_Interface', $plugin2);
    }

    /**
     * @covers Core_Plugin_Manager::createPlugin
     */
    public function testCanCreatePlugin()
    {
        $plugin  = $this->_mockPlugin('someName');
        $pLoader = $this->_mockPluginLoader($plugin, 'InvalidPluginFoo', array('opt' => 'ions', 'name' => 'someName'));
        $this->_object->setPluginLoader($pLoader);
        $created = $this->_object->createPlugin('Invalid_Plugin_Interface', 'Foo ', 'someName', array('opt' => 'ions'));

        $this->assertEquals($plugin, $created);
    }

    /**
     * @covers Core_Plugin_Manager::createPlugin
     */
    public function testCreatingAPluginWillAddItByDefault()
    {
        $plugin  = $this->_mockPlugin('someName');
        $pLoader = $this->_mockPluginLoader($plugin, 'InvalidPluginFoo', array('name' => 'someName', 'opt' => 'ions'));
        $this->_object->setPluginLoader($pLoader);
        $this->_object->createPlugin('Invalid_Plugin_Interface', 'Foo', 'someName', array('opt' => 'ions'));

        $this->assertTrue($this->_object->hasPlugin('Invalid_Plugin_Interface', 'SoMeNaMe'));
    }

    /**
     * @covers Core_Plugin_Manager::createPlugin
     */
    public function testCanPreventAddingAPluginAfterCreatingIt()
    {
        $plugin  = $this->getMock('Invalid_Plugin_Interface');
        $pLoader = $this->_mockPluginLoader($plugin, 'InvalidPluginFoo', array('name' => 'someName', 'opt' => 'ions'));
        $this->_object->setPluginLoader($pLoader);
        $this->_object->createPlugin('Invalid_Plugin_Interface', 'Foo', 'someName', array('opt' => 'ions', '__autoAdd' => false));

        $this->assertFalse($this->_object->hasPlugin('Invalid_Plugin_Interface', 'SoMeNaMe'));
    }

    /**
     * @covers Core_Plugin_Manager::addPlugin
     * @covers Core_Plugin_Manager::hasPlugin
     */
    public function testCanAddPlugin()
    {
        $this->_object->addPlugin('Invalid_Plugin_Interface', $this->_mockPlugin());

        $this->assertTrue($this->_object->hasPlugin('Invalid_Plugin_Interface', 'Somename'));
    }

    /**
     * @covers Core_Plugin_Manager::addPlugin
     */
    public function testAddingAPluginWithANameAlreadyUsedWillThrowAnInvalidArgumentException()
    {
        $this->setExpectedException('InvalidArgumentException');
        $this->_object->addPlugin('Invalid_Plugin_Interface', $this->_mockPlugin('sameName'));
        $this->_object->addPlugin('Invalid_Plugin_Interface', $this->_mockPlugin('SaMeName'));
    }

    /**
     * @covers Core_Plugin_Manager::hasPlugin
     */
    public function testIfClassPassedToHasPluginIsNotManagedWillThrowAnInvalidArgumentException()
    {
        $this->setExpectedException('InvalidArgumentException');
        $this->_object->hasPlugin('Core_Plugin_Interface', 'name');
    }

    /**
     * @covers Core_Plugin_Manager::getPlugin
     */
    public function testCanGetPlugin()
    {
        $plugin = $this->_mockPlugin('someName');
        $this->_object->addPlugin('Invalid_Plugin_Interface', $plugin);
        $this->assertEquals($plugin, $this->_object->getPlugin('Invalid_Plugin_Interface', 'sOmEnAme'));
    }

    /**
     * @covers Core_Plugin_Manager::getPlugin
     */
    public function testFetchingAnUnknownPluginWillThrowAnInvalidArgumentException()
    {
        $this->setExpectedException('InvalidArgumentException');

        $this->_object->getPlugin('Invalid_Plugin_Interface', 'sOmEnAme');
    }

    /**
     * @covers Core_Plugin_Manager::getPlugins
     */
    public function testCanGetOwnPlugins()
    {
        $plugin1 = $this->_mockPlugin('someName1');
        $plugin2 = $this->_mockPlugin('someName2');
        $this->_object->addPlugin('Invalid_Plugin_Interface', $plugin1);
        $this->_object->addPlugin('Invalid_Plugin_Interface', $plugin2);

        $plugins = array(
            'somename1' => $plugin1,
            'somename2' => $plugin2,
        );

        $this->assertEquals($plugins, $this->_object->getPlugins('Invalid_Plugin_Interface'));
    }
    
    /**
     * @covers Core_Plugin_Manager::getPlugins
     */
    public function testCanGetNamedPlugins()
    {
        $plugin1 = $this->_mockPlugin('someName1');
        $plugin2 = $this->_mockPlugin('someName2');
        $this->_object->addPlugin('Invalid_Plugin_Interface', $plugin1);
        $this->_object->addPlugin('Invalid_Plugin_Interface', $plugin2);

        $plugins = array(
            'somename2' => $plugin2,
        );

        $this->assertEquals($plugins, $this->_object->getPlugins('Invalid_Plugin_Interface', array('somename2')));
    }
    
    /**
     * @covers Core_Plugin_Manager::getPlugins
     */
    public function testCanGetAllPlugins()
    {
        $plugin1 = $this->_mockPlugin('someName1');
        $plugin2 = $this->_mockPlugin('someName2');
        $plugin3 = $this->_mockPlugin('someName3');
        $this->_object->addPlugin('Invalid_Plugin_Interface', $plugin1);
        $this->_object->addPlugin('Invalid_Plugin_Interface', $plugin2);
        $this->_object->addPlugin('Invalid_Plugin_Interface', $plugin3, false);

        $plugins = array(
            'somename1' => $plugin1,
            'somename2' => $plugin2,
            'somename3' => $plugin3,
        );

        $this->assertEquals($plugins, $this->_object->getPlugins('Invalid_Plugin_Interface', null));
    }

    /**
     * @covers Core_Plugin_Manager::getPlugins
     */
    public function testFetchingUnmanagedPluginsWillThrowAnInvalidArgumentException()
    {
        $this->setExpectedException('InvalidArgumentException');

        $this->_object->getPlugins('Unknown_Plugin_Interface');
    }

    /**
     * @covers Core_Plugin_Manager::removePlugin
     */
    public function testCanRemovePlugin()
    {
        $plugin = $this->_mockPlugin('someName');
        $this->_object->addPlugin('Invalid_Plugin_Interface', $plugin);
        $this->_object->removePlugin('Invalid_Plugin_Interface', 'sOmEnAme');

        $this->assertFalse($this->_object->hasPlugin('Invalid_Plugin_Interface', 'someName'));
    }

    /**
     * @covers Core_Plugin_Manager::removePlugin
     */
    public function testRemovingAnUnknownPluginWillThrowAnInvalidArgumentException()
    {
        $this->setExpectedException('InvalidArgumentException');

        $this->_object->removePlugin('Invalid_Plugin_Interface', 'sOmEnAme');
    }

    /**
     * @covers Core_Plugin_Manager::isManaged
     */
    public function testWillManageKnownPluginsWithKnownActions()
    {
        $this->assertTrue($this->_object->isManaged('addPlugin', array()));
    }

    /**
     * @covers Core_Plugin_Manager::isManaged
     */
    public function testWillNotManagePluginsWithUnknownAction()
    {
        $this->assertFalse($this->_object->isManaged('findElement', array()));
    }

    /**
     * @covers Core_Plugin_Manager::isManaged
     */
    public function testWillNotManagePluginsOfUnknownType()
    {
        $this->assertFalse($this->_object->isManaged('addPitcher', array()));
    }

    /**
     * @covers Core_Plugin_Manager::manage
     */
    public function testWillManageKnownPluginTypes()
    {
        $object = $this->getMock('Core_Plugin_Manager', array('createPlugin'), array($this->_owner, array(
            'Core_Form_Element_Interface' => array(
                'FormElement',
                array('mapping' => array(
                        'Element'  => Core_Plugin_Manager::NAME_PLUGIN,
                        'Elements' => Core_Plugin_Manager::NAME_PLUGINS,
                    )
                ),
            ))));
        $object
            ->expects($this->once())
            ->method('createPlugin')
            ->with($this->equalTo('Core_Form_Element_Interface'), $this->equalTo('type'), $this->equalTo(array(1, 2, 3)))
            ->will($this->returnValue('bla'));

        $this->assertEquals('bla', $object->manage('createElement', array('type', array(1, 2, 3))));
    }

    /**
     * @covers Core_Plugin_Manager::manage
     */
    public function testCallingNonManagedMethodsWillReturnNull()
    {
        $this->assertNull($this->_object->manage('addPitcher', array()));
        $this->assertNull($this->_object->manage('findPlugin', array()));
    }

    /**
     * @covers Core_Plugin_Manager::__clone
     */
    public function testWillCloneInstancesWhileCloning()
    {
        $this->_object->addPlugin('Invalid_Plugin_Interface', $this->_mockPlugin('someName'));
        $object = clone $this->_object;

        $this->assertNotSame(
            $object->getPlugin('Invalid_Plugin_Interface', 'someName'),
            $this->_object->getPlugin('Invalid_Plugin_Interface', 'someName')
        );
    }

    protected function _mockPlugin($name = 'someName', $type = 'Invalid_Plugin_Interface')
    {
        $plugin = $this->getMock($type, array('getName', 'init', 'setOwner'));
        $plugin
            ->expects($this->once())
            ->method('getName')
            ->will($this->returnValue($name));
        return $plugin;
    }

    protected function _mockPluginLoader(Core_Plugin_Interface $plugin, $type, array $options)
    {
        $pluginLoader = $this->getMock('Core_Loader_PluginLoader', array('initializePlugin'));
        $pluginLoader
            ->expects($this->once())
            ->method('initializePlugin')
            ->with($this->equalTo($type), $this->equalTo($options))
            ->will($this->returnValue($plugin));
        return $pluginLoader;
    }
}

interface Invalid_Plugin_Interface extends Core_Plugin_Interface
{
}

interface Invalid_Plugin_Linked_Interface extends Core_Plugin_Interface
{
}
