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

/**
 * @category   Core
 * @package    Core_Router
 * @subpackage UnitTests
 * @copyright  Copyright (c) 2011. Burza d.o.o. (http://web.burza.hr/en/)
 * @license    proprietary
 * @group      Core_Route
 * @group      Core_Router_Route
 */
class Core_Router_Route_RegexTest extends PHPUnit_Framework_TestCase
{
    /**
     * @var Core_Router_Route_Regex
     */
    protected $_object;

    protected function setUp()
    {
        $this->_object = new Core_Router_Route_Regex;
    }

    /**
     * @covers Core_Router_Route_Regex::setModifiers
     * @covers Core_Router_Route_Regex::getModifiers
     */
    public function testCanSetAndGetPatternModifiers()
    {
        $this->_object->setModifiers('aUgi');
        $this->assertEquals('aUgi', $this->_object->getModifiers());
    }

    /**
     * @covers Core_Router_Route_Regex::getModifiers
     */
    public function testModifiersWillAlwaysIncludeIgnoreCase()
    {
        $this->_object->setModifiers('g');
        $this->assertContains('i', $this->_object->getModifiers());
    }

    /**
     * @covers Core_Router_Route_Regex::match
     */
    public function testRouteRegexMatchingThePathWillReturnTrue()
    {
        $this->_object->setPattern('/path');
        $this->assertTrue($this->_object->match('/path', '/'));
    }

    /**
     * @covers Core_Router_Route_Regex::match
     */
    public function testRouteRegexNotMatchingThePathWillReturnFalse()
    {
        $this->_object->setPattern('/pth');
        $this->assertFalse($this->_object->match('/path', '/'));
    }

    /**
     * @covers Core_Router_Route_Regex::match
     */
    public function testWillDisregardCaseWhenMatchingRoute()
    {
        $this->_object->setPattern('/path');
        $this->assertTrue($this->_object->match('/paTHs', '/'));
    }

    /**
     * @covers Core_Router_Route_Regex::match
     */
    public function testRouteWillExtractNonNumericNamedRegexpGroupAndStoreAsValues()
    {
        $regex = '^(?P<namedValue>ab)cd$';
        $this->_object
            ->setPattern($regex)
            ->match('abcd', '/');

        $routeValues = $this->_object->getValues();
        $this->assertTrue(isset($routeValues['namedValue']));
        $this->assertEquals('ab', $routeValues['namedValue']);
    }

    /**
     * @covers Core_Router_Route_Regex::match
     * @covers Core_Router_Route_Regex::<protected>
     */
    public function testRouteWillNotExtractNumericNamedRegexpGroupAndStoreAsValues()
    {
        $regex = '^(ab)cd$';
        $this->_object
            ->setPattern($regex)
            ->match('abcd', '/');

        $this->assertNotContains('ab', $this->_object->getValues());
    }

    /**
     * @covers Core_Router_Route_Regex::path
     */
    public function testCanAssembleUrl()
    {
        $regex = '^/static$';
        $this->_object->setPattern($regex);

        $this->assertEquals('/static', $this->_object->path('/'));
    }

    /**
     * @covers Core_Router_Route_Regex::path
     * @covers Core_Router_Route_Regex::<protected>
     */
    public function testCanAssembleUrlWithAParam()
    {
        $regex = '^/static/(?P<param>.*)?$';
        $this->_object->setPattern($regex);

        $this->assertEquals('/static/test', $this->_object->path('/', array('param' => 'test')));
    }

    /**
     * @covers Core_Router_Route_Regex::path
     * @covers Core_Router_Route_Regex::<protected>
     */
    public function testWillUrlencodeParams()
    {
        $regex = '^/static/(?P<param>[^/]*)$';
        $this->_object->setPattern($regex);

        $this->assertEquals('/static/ku%C4%87a', $this->_object->path('/', array('param' => 'kuća')));
    }

    /**
     * @covers Core_Router_Route_Regex::path
     * @covers Core_Router_Route_Regex::<protected>
     */
    public function testWillNotUrlencodeWildcardParam()
    {
        $regex = '^/static/(?P<param>.*)$';
        $this->_object->setPattern($regex);

        $this->assertEquals('/static/kuća', $this->_object->path('/', array('param' => 'kuća')));
    }

    /**
     * @covers Core_Router_Route_Regex::path
     * @covers Core_Router_Route_Regex::<protected>
     */
    public function testWillBuiltHttpQueryStringWithNonStringOrNumericParams()
    {
        $regex = '^/static/(?P<param>[^/]*)?$';
        $this->_object->setPattern($regex);

        $this->assertEquals(
            '/static/0%3D1%261%3D2%262%3D3',
            $this->_object->path('/', array('param' => array(1, 2, 3)))
        );
    }

    /**
     * @covers Core_Router_Route_Regex::path
     * @covers Core_Router_Route_Regex::<protected>
     */
    public function testCanAssembleUrlWithALeadingMandatoryParam()
    {
        $regex = '^/(?P<param>.*)/static/$';
        $this->_object->setPattern($regex);

        $this->assertEquals('/test/static/', $this->_object->path('/', array('param' => 'test')));
    }

    /**
     * @covers Core_Router_Route_Regex::path
     * @covers Core_Router_Route_Regex::<protected>
     */
    public function testAssemblingAnUrlWithoutALeadingMandatoryParamThrowsAnInvalidArgumentException()
    {
        $this->setExpectedException('InvalidArgumentException');
        $regex = '^/(?P<param>.*)/static/$';
        $this->_object
            ->setPattern($regex)
            ->path('/');
    }

    /**
     * @covers Core_Router_Route_Regex::path
     * @covers Core_Router_Route_Regex::<protected>
     */
    public function testCanAssembleUrlWithALeadingOptionalParam()
    {
        $regex = '^(/(?P<param>.*))?/static/$';
        $this->_object->setPattern($regex);

        $this->assertEquals('/test/static/', $this->_object->path('/', array('param' => 'test')));
    }

    /**
     * @covers Core_Router_Route_Regex::path
     * @covers Core_Router_Route_Regex::<protected>
     */
    public function testCanAssembleUrlWithoutALeadingOptionalParam()
    {
        $regex = '^(/(?P<param>.*))?/static/$';
        $this->_object->setPattern($regex);

        $this->assertEquals('/static/', $this->_object->path('/'));
    }

    /**
     * @covers Core_Router_Route_Regex::path
     * @covers Core_Router_Route_Regex::<protected>
     */
    public function testCanAssembleUrlWithATrailingSeparator()
    {
        $regex = '^/static/((?P<param>.*)/)?$';
        $this->_object->setPattern($regex);

        $this->assertEquals('/static/',      $this->_object->path('/'));
        $this->assertEquals('/static/test/', $this->_object->path('/', array('param' => 'test')));
    }

    /**
     * @covers Core_Router_Route_Regex::path
     * @covers Core_Router_Route_Regex::<protected>
     */
    public function testCanAssembleUrlWithoutATrailingSeparator()
    {
        $regex = '^/static(/(?P<param>.*))?$';
        $this->_object->setPattern($regex);

        $this->assertEquals('/static',      $this->_object->path('/'));
        $this->assertEquals('/static/test', $this->_object->path('/', array('param' => 'test')));
    }

    /**
     * @covers Core_Router_Route_Regex::setPattern
     */
    public function testWillProperlyHandleChangingOfTokens()
    {
        // we cache the pattern tokens, but if the pattern changes, so must the tokens!

        // create the token cache!
        $this->_object->setPattern('^/static/(?P<param>.*)/$');
        $this->assertEquals('/static/foo/', $this->_object->path('/', array('param' => 'foo')));

        // and now change the pattern!
        $this->_object->setPattern('^/static/$');
        $this->assertEquals('/static/', $this->_object->path('/'));
    }

    /**
     * @covers Core_Router_Route_Regex::setRegex
     */
    public function testSetRegexProxiesToSetPattern()
    {
        $this->_object->setRegex('/abc/d');
        $this->assertEquals('/abc/d', $this->_object->getPattern());
    }

    /**
     * @covers Core_Router_Route_Regex::getRegex
     */
    public function testGetRegexProxiesToGetPattern()
    {
        $this->_object->setPattern('/abc/e');
        $this->assertEquals('/abc/e', $this->_object->getRegex());
    }

    /**
     * @covers Core_Router_Route_Regex::match
     * @covers Core_Router_Route_Regex::<protected>
     */
    public function testCanHaveStaticHostname()
    {
        $this->_object
            ->setPattern('/abc')
            ->setHostname('http://dkarlovi.example.com');

        $this->assertTrue($this->_object->match('/abc', '/', null, 'http://dkarlovi.example.com'));
    }

    /**
     * @covers Core_Router_Route_Regex::match
     * @covers Core_Router_Route_Regex::<protected>
     */
    public function testCanIncludeVariableInHostname()
    {
        $this->_object
            ->setPattern('/abc')
            ->setHostname('http://(?<username>[^\.]*).example.com');

        $this->assertTrue($this->_object->match('/abc', '/', null, 'http://dkarlovi.example.com'));
    }

    /**
     * @covers Core_Router_Route_Regex::match
     * @covers Core_Router_Route_Regex::<protected>
     */
    public function testWillExtractVariableFromHostname()
    {
        $this->_object
            ->setPattern('/abc')
            ->setHostname('http://(?<username>[^\.]*).example.com');
        $this->_object->match('/abc', '/', null, 'http://dkarlovi.example.com');
        $values = $this->_object->getValues();

        $this->assertArrayHasKey('username', $values);
        $this->assertEquals('dkarlovi',      $values['username']);
    }

    /**
     * @covers Core_Router_Route_Regex::match
     * @covers Core_Router_Route_Regex::<protected>
     */
    public function testWillExtractVariableFromHostnameAndPath()
    {
        $this->_object
            ->setPattern('/abc/(?P<param>.*)')
            ->setHostname('http://(?<username>[^\.]*).example.com');
        $this->_object->match('/abc/value1', '/', null, 'http://dkarlovi.example.com');
        $values = $this->_object->getValues();

        $this->assertEquals('dkarlovi', $values['username']);
        $this->assertEquals('value1',   $values['param']);
    }

    /**
     * @covers Core_Router_Route_Regex::url
     * @covers Core_Router_Route_Regex::<protected>
     */
    public function testWillProcessHostnameVariablesWhenBuilingUrl()
    {
        $this->_object
            ->setPattern('/abc/(?P<param>.*)')
            ->setHostname('http://(?<username>[^\.]*).example.com');

        $this->assertEquals(
            'http://dkarlovi.example.com/abc/value1',
            $this->_object->url('/', array('username' => 'dkarlovi', 'param' => 'value1'))
        );
    }

    /**
     * @covers Core_Router_Route_Regex::path
     */
    public function testWillAppendParamsAsQueryStringIfUseQueryStringIsEnabled()
    {
        $this->_object
            ->setPattern('/abc/(?P<param>.*)')
            ->setUseQuery(true);

        $this->assertEquals('/abc/bcd?page=2', $this->_object->path('/', array('param' => 'bcd', 'page' => 2), true));
    }

    /**
     * @covers Core_Router_Route_Regex::path
     */
    public function testWillAppendParamsAsQueryStringIfNoParamsAreGivenInPattern()
    {
        $this->_object
            ->setPattern('/abc/')
            ->setUseQuery(true);

        $path = $this->_object->path('/', array('param' => 'bcd', 'page' => 2), true);
        $this->assertEquals('/abc/?param=bcd&page=2', $path);
    }
    
    /**
     * @covers Core_Router_Route_Static::path
     */
    public function testWillUseDefaultsWhenBuildingAPath()
    {
        $this->_object
            ->setPattern('/abc/(?P<fromDefaults>.*)/(?P<fromParams>.*)')
            ->setDefaults(array('fromDefaults' => 'bla', 'someOther' => 'nope', 'mgm' => 'nope'));
        
        $this->assertEquals('/abc/bla/ae', $this->_object->path('/', array('fromParams' => 'ae', 'mgm' => 'yep')));
    }
    
    /**
     * @covers Core_Router_Route_Static::path
     */
    public function testDefaultsShouldNotEndUpInQueryString()
    {
        $this->_object
            ->setUseQuery(true)
            ->setPattern('/abc/(?P<fromDefaults>.*)/(?P<fromParams>.*)')
            ->setDefaults(array('fromDefaults' => 'bla', 'someOther' => 'nope', 'mgm' => 'nope'));
        
        $this->assertEquals('/abc/bla/ae?mgm=yep', $this->_object->path('/', array('fromParams' => 'ae', 'mgm' => 'yep')));
    }
    
    /**
     * @covers Core_Router_Route_Static::path
     */
    public function testParamsShouldNotEndUpInTheQueryStringIfIdenticalToDefaults()
    {
        $this->_object
            ->setUseQuery(true)
            ->setPattern('/abc/(?P<fromDefaults>.*)/(?P<fromParams>.*)')
            ->setDefaults(array('fromDefaults' => 'bla', 'someOther' => 'nope', 'mgm' => 'nope', 'dunno' => 'yes'));
        
        $this->assertEquals('/abc/bla/ae?mgm=yep', $this->_object->path('/', array('fromParams' => 'ae', 'mgm' => 'yep', 'dunno' => 'yes')));
    }

    /**
     * @param string $pattern      Route pattern
     * @param string $expectedPath Path expected from the route to produce
     * @param array  $params       Route params, used for generating the path and expected to pull from the path
     *
     * @covers Core_Router_Route_Regex::url
     * @covers Core_Router_Route_Regex::path
     * @covers Core_Router_Route_Regex::match
     * @dataProvider routeToPath
     */
    public function testRouteWillMatchThePathItProduces($pattern, $expectedPath, $params = array())
    {
        $this->_object->setPattern($pattern);
        $path = $this->_object->path('/', $params);

        // path should match the expected path
        $this->assertEquals($expectedPath, $path);

        // the route matches the path it created
        $this->assertTrue($this->_object->match($path, '/'));

        // params are retained
        $this->assertEquals($params, $this->_object->getValues());
    }

    public function routeToPath()
    {
        // pattern, expected path, params = array()
        return array(
            array('^/$',                       '/'),
            array('^/static$',                 '/static'),
            array('^/static/$',                '/static/'),
            array('^/static/partial',          '/static/partial'),
            array('/static/partial$',          '/static/partial'),
            array('/static/partial',           '/static/partial'),
            array('^/static/(?P<bar>[^/]+)?',  '/static/'),
            array('^/static/(?P<bar>[^/]+)',   '/static/param',  array('bar' => 'param')),
            array('^/static/(?P<bar>[^/]+)',   '/static/pa-ram', array('bar' => 'pa-ram')),

            array(
                '^/static/(?P<bar>[^/]+)/(?P<bar2>[^/]+)?',
                '/static/param/',
                array('bar' => 'param'),
            ),
            array(
                '^/static/(?P<bar>[^/]+)/(?P<bar2>[^/]+)',
                '/static/param/param2',
                array('bar' => 'param', 'bar2' => 'param2')
            ),
        );
    }
}
