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

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

    protected function setUp()
    {
        $this->_object = $this->getMockForAbstractClass('Core_Form_Entity_Collection');
    }

    /**
     * @covers Core_Form_Entity_Collection::__construct
     * @covers Core_Form_Entity_Collection::__call
     */
    public function testCanManageArrayValidators()
    {
        $object = $this->getMockForAbstractClass('Core_Form_Entity_Collection');

        $validator = $this->getMockForAbstractClass('Core_Validate_Array_Abstract', array(), '', true, true, true, array('getName'));
        $validator
            ->expects($this->once())
            ->method('getName')
            ->will($this->returnValue('myValidator'));
        $object->addValidator($validator);

        $this->assertTrue($object->hasValidator('MyValidator'));
    }

    /**
     * @covers Core_Form_Entity_Collection::getPrototypeMarker
     */
    public function testCanSetAndGetPrototypeMarker()
    {
        $this->_object->setId('my_group');

        $this->assertEquals('{my_group_prototype}', $this->_object->getPrototypeMarker());
    }

    /**
     * @covers Core_Form_Entity_Collection::setPrototypeInstance
     * @covers Core_Form_Entity_Collection::getPrototypeInstance
     */
    public function testCanSetAndGetPrototypeInstance()
    {
        $prototype = $this->getMockForAbstractClass('Core_Form_Group_Abstract', array(), '', true, true, true, array('setOwner', 'setName', 'setPrototype'));
        $prototype
            ->expects($this->once())
            ->method('setName')
            ->with($this->equalTo('{my_group_prototype}'))
            ->will($this->returnValue($prototype));
        $prototype
            ->expects($this->once())
            ->method('setOwner')
            ->with($this->equalTo($this->_object))
            ->will($this->returnValue($prototype));
        $prototype
            ->expects($this->once())
            ->method('setPrototype')
            ->with($this->equalTo(true))
            ->will($this->returnValue($prototype));
        $this->_object
            ->setId('my_group')
            ->setPrototypeInstance($prototype);

        $this->assertEquals($prototype, $this->_object->getPrototypeInstance());
    }

    /**
     * @covers Core_Form_Entity_Collection::setMin
     * @covers Core_Form_Entity_Collection::getMin
     */
    public function testCanSetAndGetMin()
    {
        $object          = new Invalid_Form_Group_Collection;
        $object->test    = $this;
        $object->options = array('min' => 23);
        $object->setMin(23);

        $this->assertEquals(23, $object->getMin());
    }

    /**
     * @covers Core_Form_Entity_Collection::setMin
     * @covers Core_Form_Entity_Collection::<protected>
     */
    public function testSettingMinWillAutoAddArrayMinValidator()
    {
        $object          = new Invalid_Form_Group_Collection;
        $object->test    = $this;
        $object->options = array('min' => 23);
        $object->setMin(23);
    }

    /**
     * @covers Core_Form_Entity_Collection::setMax
     * @covers Core_Form_Entity_Collection::getMax
     */
    public function testCanSetAndGetMax()
    {
        $object          = new Invalid_Form_Group_Collection;
        $object->test    = $this;
        $object->options = array('max' => 33);
        $object->setMax(33);

        $this->assertEquals(33, $object->getMax());
    }

    /**
     * @covers Core_Form_Entity_Collection::setMax
     * @covers Core_Form_Entity_Collection::<protected>
     */
    public function testSettingMaxWillAutoAddArrayMaxValidator()
    {
        $object          = new Invalid_Form_Group_Collection;
        $object->test    = $this;
        $object->options = array('max' => 33);
        $object->setMax(33);
    }

    /**
     * @covers Core_Form_Entity_Collection::setInitial
     * @covers Core_Form_Entity_Collection::getInitial
     */
    public function testCanSetAndGetInitial()
    {
        $this->_object->setInitial(3);

        $this->assertEquals(3, $this->_object->getInitial());
    }

    /**
     * @covers Core_Form_Entity_Collection::getInitial
     */
    public function testDefaultInitialIsMin()
    {
        $object          = new Invalid_Form_Group_Collection;
        $object->test    = $this;
        $object->options = array('min' => 9);
        $object->setMin(9);

        $this->assertEquals(9, $object->getInitial());
    }

    /**
     * @covers Core_Form_Entity_Collection::__construct
     * @covers Core_Form_Entity_Collection::<protected>
     */
    public function testCanSetOptionsViaConstructor()
    {
        $object = $this->getMockForAbstractClass('Core_Form_Entity_Collection', array(array('name' => 'foo', 'collection' => array('initial' => 11))));

        $this->assertEquals(11,    $object->getInitial());
        $this->assertEquals('foo', $object->getName());
    }

    /**
     * @covers Core_Form_Entity_Collection::__construct
     * @covers Core_Form_Entity_Collection::<protected>
     */
    public function testCanSetConfigViaConstructor()
    {
        $config = $this->getMock('Zend_Config', null, array(array('collection' => array('initial' => 13))));
        $object = $this->getMockForAbstractClass('Core_Form_Entity_Collection', array($config));

        $this->assertEquals(13, $object->getInitial());
    }

    /**
     * @covers Core_Form_Entity_Collection::getNextIndex
     */
    public function testDefaultNextIndexIsEqualToStart()
    {
        $this->_object->setStart(2);
        
        $this->assertEquals(2, $this->_object->getNextIndex());
    }

    /**
     * @covers Core_Form_Entity_Collection::__clone
     */
    public function testWillClonePrototypeWhileCloning()
    {
        $prototype = $this->getMockForAbstractClass('Core_Form_Group_Abstract', array(), '', true, true, true, array('setName', 'setOwner', 'setPrototype'));
        $prototype->expects($this->exactly(2))->method('setName')->will($this->returnValue($prototype));
        $prototype->expects($this->exactly(2))->method('setOwner')->will($this->returnValue($prototype));
        $prototype->expects($this->exactly(3))->method('setPrototype')->will($this->returnValue($prototype));
        $this->_object->setPrototypeInstance($prototype);

        $object = clone $this->_object;

        $this->assertNotSame($object->getPrototypeInstance(), $this->_object->getPrototypeInstance());
    }

    /**
     * @covers Core_Form_Entity_Collection::setErrorMessage
     * @covers Core_Form_Entity_Collection::getErrorMessage
     */
    public function testCanSetAndGetErrorMessage()
    {
        $this->_object->setErrorMessage('This is an error message');

        $this->assertEquals('This is an error message', $this->_object->getErrorMessage());
    }

    /**
     * @covers Core_Form_Entity_Collection::setError
     * @covers Core_Form_Entity_Collection::getError
     */
    public function testCanSetAndGetError()
    {
        $this->_object->setError('This is an actual error');

        $this->assertEquals('This is an actual error', $this->_object->getError());
    }

    /**
     * @covers Core_Form_Entity_Collection::hasError
     */
    public function testCanTestIfHasError()
    {
        $this->_object->setError('This is an actual error');

        $this->assertTrue($this->_object->hasError());
    }

    /**
     * @covers Core_Form_Entity_Collection::resetError
     */
    public function testCanResetError()
    {
        $this->_object->setError('This is an actual error');
        $this->_object->resetError();

        $this->assertNull($this->_object->getError());
    }

    /**
     * @covers Core_Form_Entity_Collection::getErrors
     */
    public function testGetErrorsWillReturnErrorAsStringIfNoChildErrorsGiven()
    {
        $this->_object->setError('My error');

        $this->assertEquals('My error', $this->_object->getErrors());
    }

    /**
     * @covers Core_Form_Entity_Collection::isValid
     */
    public function testIfAnAttachedValidatorIsInvalidTheCollectionIsInvalid()
    {
        $group1 = $this->getMockForAbstractClass('Core_Form_Group_Abstract');
        $group2 = $this->getMockForAbstractClass('Core_Form_Group_Abstract');

        $validator = $this->getMockForAbstractClass('Core_Validate_Abstract', array(), '', true, true, true, array('isValid', 'getErrorMessage'));
        $validator
            ->expects($this->once())
            ->method('isValid')
            ->with($this->equalTo(array($group1, $group2)))
            ->will($this->returnValue(false));
        $validator
            ->expects($this->once())
            ->method('getErrorMessage')
            ->with($this->equalTo('Collection-level error message'))
            ->will($this->returnValue('Oh noez!'));

        $object = $this->getMock('Core_Form_Entity_Collection', array('getValidators', 'hasErrors', '_getItems'));
        $object
            ->expects($this->once())
            ->method('hasErrors')
            ->will($this->returnValue(false));
        $object
            ->expects($this->once())
            ->method('_getItems')
            ->will($this->returnValue(array($group1, $group2)));
        $object
            ->expects($this->once())
            ->method('getValidators')
            ->will($this->returnValue(array($validator)));
        $object->setErrorMessage('Collection-level error message');

        $this->assertFalse($object->isValid());
    }

    /**
     * @covers Core_Form_Entity_Collection::<protected>
     */
    public function testWillAutoCreateMissingGroupsFromPrototypeOnGetGroupsUpToInitial()
    {
        $object          = new Invalid_Form_Group_Collection;
        $object->test    = $this;
        $object->options = array('min' => 5);
        $object->items   = array(
            5 => 'createdGroup',
            6 => 'createdGroup',
            7 => 'createdGroup',
        );

        $pluginManager = $this->getMock('Core_Plugin_Manager', array('manage'), array($this->_object));
        $pluginManager
            ->expects($this->any())
            ->method('manage')
            ->with($this->equalTo('getGroups'), $this->equalTo(array()))
            ->will($this->returnValue(array(0 => 'initialGroup', 4 => 'initialGroup')));
        $object
            ->setPluginManager($pluginManager)
            ->setMin(5);

        $this->assertEquals(array(
            0 => 'initialGroup',
            4 => 'initialGroup',
            5 => 'createdGroup',
            6 => 'createdGroup',
            7 => 'createdGroup',
        ), $object->getGroups());
    }


    /**
     * @covers Core_Form_Entity_Collection::<protected>
     */
    public function testWillAutoCreateNonExistingEntityOnAccessFromPrototype()
    {
        $prototype = $this->getMockForAbstractClass('Core_Form_Group_Abstract', array(), '', true, true, true, array('setName'));
        $prototype
            ->expects($this->once())
            ->method('setName')
            ->with($this->equalTo(23));

        $pluginManager = $this->getMock('Core_Plugin_Manager', array('manage'), array($this->_object));
        $pluginManager
            ->expects($this->at(0))
            ->method('manage')
            ->with($this->equalTo('hasGroup'), $this->equalTo(array(23)))
            ->will($this->returnValue(false));

        $object       = new Invalid_Form_Group_Collection;
        $object->test = $this;
        $object->_prototypeInstance = $prototype;
        $object->setPluginManager($pluginManager);

        $object->getGroup(23);
    }

    /**
     * @covers Core_Form_Entity_Collection::<protected>
     */
    public function testWillReturnExistingGroupOnAccessFromPrototype()
    {
        $object        = new Invalid_Form_Group_Collection;

        $pluginManager = $this->getMock('Core_Plugin_Manager', array('manage'), array($this->_object));
        $pluginManager
            ->expects($this->at(0))
            ->method('manage')
            ->with($this->equalTo('hasGroup'), $this->equalTo(array(33)))
            ->will($this->returnValue(true));
        $pluginManager
            ->expects($this->at(1))
            ->method('manage')
            ->with($this->equalTo('getGroup'), $this->equalTo(array(33)));
        $object->setPluginManager($pluginManager);

        $object->getGroup(33);
    }

    /**
     * @covers Core_Form_Entity_Collection::getPrototypeInstance
     * @covers Core_Form_Entity_Collection::<protected>
     */
    public function testIfNoPrototypeInstanceIsGivenWillBuildPrototypeInstanceFromOptions()
    {
        $prototype = $this->getMockForAbstractClass('Core_Form_Group_Abstract', array(), '', true, true, true, array('setName', 'setOwner', 'setPrototype'));
        $prototype->expects($this->once())->method('setName')->will($this->returnValue($prototype));
        $prototype->expects($this->once())->method('setOwner')->will($this->returnValue($prototype));
        $prototype->expects($this->once())->method('setPrototype')->will($this->returnValue($prototype));

        $pluginManager = $this->getMock('Core_Plugin_Manager', array('manage'), array($this->_object));
        $pluginManager
            ->expects($this->at(0))
            ->method('manage')
            ->with($this->equalTo('createGroup'), $this->equalTo(array('foo', '_prototype', array(
                'groups'    => array('a' => 'b'),
                'elements'  => array('c' => 'd'),
            ))))
            ->will($this->returnValue($prototype));
        $pluginManager
            ->expects($this->at(1))
            ->method('manage')
            ->with($this->equalTo('removeGroup'), $this->equalTo(array('_prototype')));
        $object = new Invalid_Form_Group_Collection;
        $object->_prototypeOptions = array(
            'prototype' => 'foo',
            'groups'    => array('a' => 'b'),
            'elements'  => array('c' => 'd'),
        );
        $object->setPluginManager($pluginManager);

        $this->assertEquals($prototype, $object->getPrototypeInstance());
    }

    /**
     * @covers Core_Form_Entity_Collection::getPrototypeInstance
     * @covers Core_Form_Entity_Collection::<protected>
     */
    public function testIfNoPrototypeTypeIsProvidedWillThrowARuntimeExceptionWhileBuildingPrototype()
    {
        $this->setExpectedException('RuntimeException');

        $object = new Invalid_Form_Group_Collection;
        $object->_prototypeOptions = array(
            'groups'    => array('a' => 'b'),
            'elements'  => array('c' => 'd'),
        );

        $object->getPrototypeInstance();
    }

    /**
     * @covers Core_Form_Entity_Collection::getNextIndex
     */
    public function testNextIndexWillBeBiggestCurrentIndexPlusOne()
    {
        $pluginManager = $this->getMock('Core_Plugin_Manager', array('manage'), array($this->_object));
        $pluginManager
            ->expects($this->once())
            ->method('manage')
            ->with($this->equalTo('getGroups'), $this->equalTo(array()))
            ->will($this->returnValue(array(3 => 'initialGroup')));

        $object = new Invalid_Form_Group_Collection;
        $object->setPluginManager($pluginManager);

        $this->assertEquals(4, $object->getNextIndex());
    }


    /**
     * @covers Core_Form_Entity_Collection::populate
     * @covers Core_Form_Entity_Collection::<protected>
     */
    public function testWillPopulateCollection()
    {
        $group1 = $this->getMockForAbstractClass('Core_Form_Group_Abstract', array(), '', true, true, true, array('populate'));
        $group1
            ->expects($this->once())
            ->method('populate')
            ->with($this->equalTo(array('a' => 1, 'b' => 2)));
        $group2 = $this->getMockForAbstractClass('Core_Form_Group_Abstract', array(), '', true, true, true, array('populate'));
        $group2
            ->expects($this->once())
            ->method('populate')
            ->with($this->equalTo(array('a' => 3, 'b' => 4)));

        $object = new Invalid_Form_Group_Collection;
        $object->items = array(
            23 => $group1,
            33 => $group2
        );

        $object->populate(array(
            23 => array('a' => 1, 'b' => 2),
            33 => array('a' => 3, 'b' => 4),
        ));
    }

    /**
     * @covers Core_Form_Entity_Collection::isValid
     * @covers Core_Form_Entity_Collection::<protected>
     */
    public function testWillUseCollectionToValidateAgainst()
    {
        $validator = $this->getMockForAbstractClass('Core_Validate_Array_Abstract', array(), '', true, true, true, array('isValid'));
        $validator
            ->expects($this->once())
            ->method('isValid')
            ->with($this->equalTo(array('some', 'items')))
            ->will($this->returnValue(true));
        $object = $this->getMock('Core_Form_Entity_Collection', array('getValidators', '_getItems'));
        $object
            ->expects($this->once())
            ->method('_getItems')
            ->will($this->returnValue(array('some', 'items')));
        $object
            ->expects($this->once())
            ->method('getValidators')
            ->will($this->returnValue(array($validator)));

        $this->assertTrue($object->isValid());
    }

    /**
     * @covers Core_Form_Entity_Collection::getAngularRepresentation
     */
    public function testCanGetAngularRepresentation()
    {
        $validator1 = $this->getMockForAbstractClass('Core_Validate_Abstract', array(), '', true, true, true, array('getName', 'getAngularRepresentation'));
        $validator1
            ->expects($this->once())
            ->method('getName')
            ->will($this->returnValue('myValidator1'));
        $validator1
            ->expects($this->exactly(2))
            ->method('getAngularRepresentation')
            ->with($this->equalTo('Collection error message'))
            ->will($this->returnValue(array('required' => array('errorMessage' => 'Collection error message', 'config' => 'required'))));
        $this->_object->addValidator($validator1);

        $validator2 = $this->getMockForAbstractClass('Core_Validate_Abstract', array(), '', true, true, true, array('getName', 'getAngularRepresentation'));
        $validator2
            ->expects($this->once())
            ->method('getName')
            ->will($this->returnValue('myValidator2'));
        $validator2
            ->expects($this->exactly(2))
            ->method('getAngularRepresentation')
            ->with($this->equalTo('Collection error message'))
            ->will($this->returnValue(array('min' => array('errorMessage' => 'Collection error message', 'config' => 3))));
        $this->_object->addValidator($validator2);

        $this->_object->setErrorMessage('Collection error message');

        $this->assertEquals(array(
            'required' => 'Collection error message',
            'min'      => 'Collection error message',
        ), $this->_object->getAngularRepresentation());

        $this->assertEquals(array(
            'required' => 'required',
            'min'      => 3,
        ), $this->_object->getAngularRepresentation(true));
    }
    
    /**
     * @covers Core_Form_Entity_Collection::setStart
     * @covers Core_Form_Entity_Collection::getStart
     */
    public function testCanSetAndGetStartIndex()
    {
        $this->_object->setStart(2);
        $this->assertEquals(2, $this->_object->getStart());
    }
    
    /**
     * @covers Core_Form_Entity_Collection::getStart
     */
    public function testStartIsZeroByDefault()
    {
        $this->assertEquals(0, $this->_object->getStart());
    }
}

class Invalid_Form_Group_Collection extends Core_Form_Entity_Collection
{
    public $test;
    public $options = array();
    public $items;

    public $_type = 'group';
    public $_prototypeType;
    public $_prototypeOptions;
    public $_prototypeInstance;

    protected $_getItemsMethod   = 'getGroups';
    protected $_getItemMethod    = 'getGroup';
    protected $_hasItemMethod    = 'hasGroup';
    protected $_addItemMethod    = 'addGroup';
    protected $_removeItemMethod = 'removeGroup';
    protected $_createItemMethod = 'createGroup';

    public function hasValidator($name)
    {
        return true;
    }

    public function getValidator($name)
    {
        $validator = $this->test->getMockForAbstractClass('Core_Validate_Abstract', array(), '', true, true, true, array('setOptions'));
        $validator
            ->expects($this->test->once())
            ->method('setOptions')
            ->with($this->test->equalTo($this->options));
        return $validator;
    }

    public function _getItem($idx)
    {
        if ($this->items) {
            return $this->items[$idx];
        }
        return parent::_getItem($idx);
    }

    public function getGroups()
    {
        return $this->_getItems();
    }

    public function getGroup($idx)
    {
        return $this->_getItem($idx);
    }
}