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

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

    protected function setUp()
    {
        $this->_object = new Core_Dispatcher;
        $this->_object->setEventDispatcher($this->getMock('Core_Event_Dispatcher'));
    }

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

    /**
     * @covers Core_Dispatcher::setEventDispatcher
     * @covers Core_Dispatcher::getEventDispatcher
     */
    public function testCanSetAndGetRequest()
    {
        $eventDispatcher = $this->getMock('Core_Event_Dispatcher');
        $this->_object->setEventDispatcher($eventDispatcher);

        $this->assertSame($eventDispatcher, $this->_object->getEventDispatcher());
    }

    /**
     * @covers Core_Dispatcher::getEventDispatcher
     */
    public function testIfNoEventDispatcherIsSetGetRequestWillPullItFromApplicationInstanceAsEventDispatcher()
    {
        $object          = new Core_Dispatcher;
        
        $eventDispatcher = $this->getMock('Core_Event_Dispatcher');
        $container       = $this->_mockContainer('EventDispatcher', $eventDispatcher);
        Core_Application::getInstance()->setContainer($container);

        $this->assertSame($eventDispatcher, $object->getEventDispatcher());
    }

    /**
     * @covers Core_Dispatcher::setResourceFinder
     * @covers Core_Dispatcher::getResourceFinder
     */
    public function testCanSetAndGetResourceFinder()
    {
        $finder = $this->_mockResourceFinder();
        $this->_object->setResourceFinder($finder);

        $this->assertEquals($finder, $this->_object->getResourceFinder());
    }

    /**
     * @covers Core_Dispatcher::getResourceFinder
     */
    public function testIfNoResourceFinderIsSpecifiedItWillPullAnInstanceFromApplicationAsResourceFinder()
    {
        $finder    = $this->_mockResourceFinder();
        $container = $this->_mockContainer('ResourceFinder', $finder);
        Core_Application::getInstance()->setContainer($container);

        $this->assertEquals($finder, $this->_object->getResourceFinder());
    }
    
    /**
     * @covers Core_Dispatcher::dispatch
     */
    public function testWillDispatchPredispatchEarlyBeforeDoingAnythingElse()
    {
        $eventDispatcher = new Invalid_Dispatcher_Event_Dispatcher;
        $object          = new Core_Dispatcher;
        $object->setEventDispatcher($eventDispatcher);
        $this->setExpectedException('InvalidArgumentException');
        $object->dispatch($this->_mockLocation());
        
        $this->assertEquals(Core_Dispatcher::EVENT_PREDISPATCH_EARLY, $eventDispatcher->event->getName());
    }

    /**
     * @covers Core_Dispatcher::dispatch
     */
    public function testIfModuleIsEmptyDispatchWillThrowAndInvalidArgumentException()
    {
        $this->setExpectedException('InvalidArgumentException');

        $this->_object->dispatch($this->_mockLocation());
    }

    /**
     * @covers Core_Dispatcher::dispatch
     */
    public function testIfModuleDoesNotExistDispatchWillThrowAndInvalidArgumentException()
    {
        $this->setExpectedException('InvalidArgumentException');
        $expected = array(
            'findModulePath' => 'foo',
        );
        $this->_object->setResourceFinder($this->_mockResourceFinder($expected));

        $this->_object->dispatch($this->_mockLocation('foo'));
    }

    /**
     * @covers Core_Dispatcher::dispatch
     */
    public function testIfModuleIsNotADirectoryDispatchWillThrowAndInvalidArgumentException()
    {
        $this->setExpectedException('InvalidArgumentException');
        $expected = array(
            'findModulePath' => __FILE__,
        );
        $this->_object->setResourceFinder($this->_mockResourceFinder($expected));

        $this->_object->dispatch($this->_mockLocation('ignored'));
    }

    /**
     * @covers Core_Dispatcher::dispatch
     */
    public function testIfControllerIsEmptyDispatchWillThrowAndInvalidArgumentException()
    {
        $this->setExpectedException('InvalidArgumentException');
        $expected = array(
            'findModulePath'     => dirname(__FILE__),
            'findControllerPath' => null,
        );
        $this->_object->setResourceFinder($this->_mockResourceFinder($expected));

        $this->_object->dispatch($this->_mockLocation('ignored'));
    }

    /**
     * @covers Core_Dispatcher::dispatch
     */
    public function testIfControllerDoesNotExistsDispatchWillThrowAndInvalidArgumentException()
    {
        $this->setExpectedException('InvalidArgumentException');
        $expected = array(
            'findModulePath'     => dirname(__FILE__),
            'findControllerPath' => 'foo.php',
        );
        $this->_object->setResourceFinder($this->_mockResourceFinder($expected));

        $this->_object->dispatch($this->_mockLocation('ignored'));
    }

    /**
     * @covers Core_Dispatcher::dispatch
     */
    public function testIfControllerClassDoesNotExistsAfterLoadingClassFileDispatchWillThrowAnInvalidArgumentException()
    {
        $modulesDir = TESTS_ROOT .'/resources/fixtures/Core/Dispatcher/modules';

        $this->setExpectedException('InvalidArgumentException');
        $expected = array(
            'findModulePath'      => $modulesDir .'/test1',
            'findControllerPath'  => $modulesDir .'/test1/EmptyFile.php',
            'findControllerClass' => 'Test1_EmptyFile',
        );
        $this->_object->setResourceFinder($this->_mockResourceFinder($expected));

        $this->_object->dispatch($this->_mockLocation('ignored'));
    }

    /**
     * @covers Core_Dispatcher::dispatch
     */
    public function testIfControllerClassIsNotCore_Controller_InterfaceDispatchWillThrowAndInvalidArgumentException()
    {
        $modulesDir = TESTS_ROOT .'/resources/fixtures/Core/Dispatcher/modules';
        $this->setExpectedException('InvalidArgumentException');
        $expected = array(
            'findModulePath'      => $modulesDir .'/test1',
            'findControllerPath'  => $modulesDir .'/test1/NoInterfaceController.php',
            'findControllerClass' => 'Test1_NoInterfaceController',
        );
        $this->_object->setResourceFinder($this->_mockResourceFinder($expected));

        $this->_object->dispatch($this->_mockLocation('ignored'));
    }

    /**
     * @covers Core_Dispatcher::dispatch
     */
    public function testCanDispatchLocation()
    {
        $modulesDir = TESTS_ROOT .'/resources/fixtures/Core/Dispatcher/modules';
        $expected = array(
            'findModulePath'      => $modulesDir .'/test1',
            'findControllerPath'  => $modulesDir .'/test1/ProperController.php',
            'findControllerClass' => 'Test1_ProperController',
        );
        $this->_object
            ->setEventDispatcher($this->getMock('Core_Event_Dispatcher'))
            ->setResourceFinder($this->_mockResourceFinder($expected));

        $this->_object->dispatch($this->_mockLocation('ignored'));
    }

    protected function _mockLocation($module = null)
    {
        $location = $this->getMock('Core_Dispatcher_Location_Interface');
        if ($module) {
            $location
                ->expects($this->once())
                ->method('getModule')
                ->will($this->returnValue($module));
        }
        return $location;
    }

    protected function _mockResourceFinder(array $expected = array())
    {
        $resourceFinder = $this->getMock('Core_Module_ResourceFinder', array_keys($expected));
        if ($expected) {
            foreach ($expected as $method => $return) {
                $resourceFinder
                    ->expects($this->once())
                    ->method($method)
                    ->will($this->returnValue($return));
            }
        }
        return $resourceFinder;
    }
}

class Invalid_Dispatcher_Event_Dispatcher extends Core_Event_Dispatcher
{
    public $event;

    public function dispatch(Core_Event_Interface $event)
    {
        $this->event = $event;
        return 'event dispatcher response';
    }
}