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

/**
 * @category   Core
 * @package    Core_QueryLanguage
 * @subpackage UnitTests
 * @copyright  Copyright (c) 2011. Burza d.o.o. (http://web.burza.hr/en/)
 * @license    proprietary
 * @group      Core_QueryLanguage
 * @group      Core_QueryLanguage_Adapter
 */
class Core_QueryLanguage_Adapter_DoctrineTest extends PHPUnit_Framework_TestCase
{
    const ENTITY_ALIAS = 'tb';

    protected $_object;

    protected function setup()
    {
        $this->_object = new Core_QueryLanguage_Adapter_Doctrine;
    }

    /**
     * @covers Core_QueryLanguage_Adapter_Doctrine::setInitialQuery
     */
    public function testCanSetInitialQuery()
    {
        $doctrineQueryMock = $this->getMock('Doctrine_Query', array('getDqlPart'), array(), '', false);
        $doctrineQueryMock
            ->expects($this->any())
            ->method('getDqlPart')
            ->will($this->returnValue(array('Dummy AS '. self::ENTITY_ALIAS)));

        $query = Core_QueryLanguage_Query::factory(array());

        $this->_object->setQuery($query);
        $this->_object->setInitialQuery($doctrineQueryMock);
        $this->assertSame($doctrineQueryMock, $this->_object->getQuery());
    }

    /**
     * @covers Core_QueryLanguage_Adapter_Doctrine::setQuery
     */
    public function testCanSetQuery()
    {
        $query = Core_QueryLanguage_Query::factory(array());
        $this->assertSame($this->_object, $this->_object->setQuery($query));
    }

    /**
     * @covers Core_QueryLanguage_Adapter_Doctrine::getQuery
     * @expectedException LogicException
     */
    public function testCallingGetQueryThrowsExceptionIfNoQueryIsSet()
    {
        $object = new Core_QueryLanguage_Adapter_Doctrine;
        $object->getQuery();
    }

    /**
     * @covers Core_QueryLanguage_Adapter_Doctrine::getQuery
     * @expectedException LogicException
     */
    public function testThrowsExceptionIfQueryContainsArrayValuesForSomeOperators()
    {
        $this->_object->setQuery(Core_QueryLanguage_Query::factory(array(
            'query' => array(
                'field' => array(
                    Core_QueryLanguage_Query::OPERATOR_GREATER => array('a')
                )
            )
        )));

        $doctrineQueryMock = $this->getMock('Doctrine_Query', array('getDqlPart'), array(), '', false);
        $doctrineQueryMock
            ->expects($this->any())
            ->method('getDqlPart')
            ->will($this->returnValue(array('Dummy AS '. self::ENTITY_ALIAS)));

        $this->_object->setInitialQuery($doctrineQueryMock);
        $this->_object->getQuery();
    }

    /**
     * @covers Core_QueryLanguage_Adapter_Doctrine::getQuery
     * @covers Core_QueryLanguage_Adapter_Doctrine::_getSelectParts
     * @covers Core_QueryLanguage_Adapter_Doctrine::_getJoins
     * @covers Core_QueryLanguage_Adapter_Doctrine::_processRelations
     * @covers Core_QueryLanguage_Adapter_Doctrine::_getDoctrineQuery
     * @covers Core_QueryLanguage_Adapter_Doctrine::_getRelationAlias
     * @covers Core_QueryLanguage_Adapter_Doctrine::_getAlias
     * @covers Core_QueryLanguage_Adapter_Doctrine::_getWhereParts
     * @covers Core_QueryLanguage_Adapter_Doctrine::_getOrderByParts
     *
     * @dataProvider getQueryData
     */
    public function testCanGetQuery($query, $expected, $mockedMethods = null)
    {
        $this->markTestIncomplete('TODO');
                
        $this->_object->setQuery(Core_QueryLanguage_Query::factory($query));

        $mockedMethods = !is_null($mockedMethods) ? $mockedMethods : array(
            'addSelect',
            'addOrderBy',
            'offset',
            'limit',
            'andWhereIn',
            'andWhere',
            'getDqlPart'
        );

        $doctrineQueryMock = $this->getMock('Doctrine_Query', $mockedMethods, array(), '', false);

        foreach ($expected as $method => $arguments) {

            if (isset($arguments['method'])) {
                $method = $arguments['method'];
            }

            // How many times do we expect to call ...
            $expect = $doctrineQueryMock->expects($arguments['cnt']);

            // ... which method
            $method = $expect->method($method);

            // ... with which arguments
            if (isset($arguments['with'])) {
                call_user_func_array(array($method, 'with'), (array) $arguments['with']);
            }

            // ... and what will it return
            if (isset($arguments['will'])) {
                $method->will($arguments['will']);
            }
        }

        $this->_object->setInitialQuery($doctrineQueryMock);

        $this->_object->getQuery();

    }

    /**
     * Data provider for self::testCanGetQuery()
     *
     * @return array
     */
    public function getQueryData()
    {        
        $data = array();

        $data['nothing_ever'] = array(
            'query'     => array(),
            'expected'  => array(
                'addSelect' => array(
                    'cnt'       => $this->never()
                ),
                'addOrderBy' => array(
                    'cnt'       => $this->never()
                ),
                'offset' => array(
                    'cnt'       => $this->never()
                ),
                'limit' => array(
                    'cnt'       => $this->never()
                ),
                'innerJoin' => array(
                    'cnt'       => $this->never()
                ),
                'andWhere' => array(
                    'cnt'       => $this->never()
                ),
                'andWhereIn' => array(
                    'cnt'       => $this->never()
                ),
            )
        );

        $data['selectWithOneField'] = array(
            'query'     => array(
                'select'    => array('field'),
            ),
            'expected'  => array(
                'addSelect'    => array(
                    'cnt'       => $this->once(),
                    'with'      => 'field'
                )
            )
        );

        $atCnt = 0;
        $data['selectWithMultipleFields'] = array(
            'query'     => array(
                'select'    => array('field', 'another_field'),
            ),
            'expected'  => array(
                'addSelect:field'    => array(
                    'method'    => 'addSelect',
                    'cnt'       => $this->at($atCnt++),
                    'with'      => 'field'
                ),
                'addSelect:another_field'    => array(
                    'method'    => 'addSelect',
                    'cnt'       => $this->at($atCnt++),
                    'with'      => 'another_field'
                ),
            )
        );

        $atCnt = 0;
        $data['selectWithMultipleFieldsAndRelations'] = array(
            'query'     => array(
                'select'    => array(
                    'field',
                    'another_field',
                    'Relation' => array(
                        'field',
                        'another_field',
                        'SubRelation' => array(
                            'sub_field'
                        ),
                    ),
                    'AnotherRelation' => array(
                        '*'
                    )
                )
            ),
            'expected'  => array(
                'addSelect:field'    => array(
                    'method'    => 'addSelect',
                    'cnt'       => $this->at($atCnt++),
                    'with'      => 'field'
                ),
                'addSelect:another_field'    => array(
                    'method'    => 'addSelect',
                    'cnt'       => $this->at($atCnt++),
                    'with'      => 'another_field'
                ),
                'addSelect:Relation:field'    => array(
                    'method'    => 'addSelect',
                    'cnt'       => $this->at($atCnt++),
                    'with'      => 'Relation.field'
                ),
                'addSelect:Relation:another_field'    => array(
                    'method'    => 'addSelect',
                    'cnt'       => $this->at($atCnt++),
                    'with'      => 'Relation.another_field'
                ),
                'addSelect:Relation:SubRelation:sub_field'    => array(
                    'method'    => 'addSelect',
                    'cnt'       => $this->at($atCnt++),
                    'with'      => 'Relation_SubRelation.sub_field'
                ),
                'addSelect:AnotherRelation:*'    => array(
                    'method'    => 'addSelect',
                    'cnt'       => $this->at($atCnt++),
                    'with'      => 'AnotherRelation.*'
                ),
                // Need this mock, after select, joins will request alias
                'getDqlPart'        => array(
                    'cnt'       => $this->at($atCnt++),
                    'will'      => $this->returnValue(array('Dummy AS '. self::ENTITY_ALIAS))
                ),
            )
        );

        $data['queryWithOneField'] = array(
            'query'     => array(
                'query'    => array(
                    'field' => array(
                        Core_QueryLanguage_Query::OPERATOR_EQUAL => 'value'
                    )
                ),
            ),
            'expected'  => array(
                'andWhere'    => array(
                    'method'    => 'andWhere',
                    'cnt'       => $this->once(),
                    'with'      => array('field = :field', array('field' => 'value'))
                )
            )
        );

        $atCnt = 0;
        $data['queryWithMultipleFields'] = array(
            'query'     => array(
                'query'    => array(
                    'field' => array(
                        Core_QueryLanguage_Query::OPERATOR_EQUAL => 'value'
                    ),
                    'Related' => array(
                        'related_field' => array(
                            Core_QueryLanguage_Query::OPERATOR_EQUAL => 'bla',
                        ),
                        'related_array_field' => array(
                            Core_QueryLanguage_Query::OPERATOR_EQUAL => array('alfa', 'beta', 'gamma')
                        ),
                        'another_related_field' => array(
                            Core_QueryLanguage_Query::OPERATOR_EQUAL => 'another related value'
                        ),
                    ),
                    'another_field' => array(
                        Core_QueryLanguage_Query::OPERATOR_EQUAL => 'another value'
                    ),
                    'array_field' => array(
                        Core_QueryLanguage_Query::OPERATOR_NOT_EQUAL => array('alfa', 'beta', 'gamma')
                    )
                ),
            ),
            'expected'  => array(
                'getDqlPart'        => array(
                    'cnt'       => $this->at($atCnt++),
                    'will'      => $this->returnValue(array('Dummy AS '. self::ENTITY_ALIAS))
                ),
                'andWhere:field'    => array(
                    'method'    => 'andWhere',
                    'cnt'       => $this->at($atCnt++),
                    'with'      => array('field = :field', array('field' => 'value'))
                ),
                'andWhere:Related:field'    => array(
                    'method'    => 'andWhere',
                    'cnt'       => $this->at($atCnt++),
                    'with'      => array(
                        'Related.related_field = :Related_related_field',
                        array('Related_related_field' => 'bla')
                    )
                ),
                'andWhereIn:Related:array_field'    => array(
                    'method'    => 'andWhereIn',
                    'cnt'       => $this->at($atCnt++),
                    'with'      => array(
                        'Related.related_array_field',
                        array('alfa', 'beta', 'gamma'),
                        false
                    )
                ),
                'andWhere:Related:another_field'    => array(
                    'method'    => 'andWhere',
                    'cnt'       => $this->at($atCnt++),
                    'with'      => array(
                        'Related.another_related_field = :Related_another_related_field',
                        array('Related_another_related_field' => 'another related value')
                    )
                ),
                'andWhere:another_field'    => array(
                    'method'    => 'andWhere',
                    'cnt'       => $this->at($atCnt++),
                    'with'      => array(
                        'another_field = :another_field',
                        array('another_field' => 'another value')
                    )
                ),
                'andWhereIn:array_field'    => array(
                    'method'    => 'andWhereIn',
                    'cnt'       => $this->at($atCnt++),
                    'with'      => array(
                        'array_field',
                        array('alfa', 'beta', 'gamma'),
                        true
                    )
                ),
            )
        );

        $data['queryWithEqualOperator'] = array(
            'query'     => array(
                'query'    => array(
                    'field' => array(
                        Core_QueryLanguage_Query::OPERATOR_EQUAL => 'value'
                    ),
                    'array_field' => array(
                        Core_QueryLanguage_Query::OPERATOR_EQUAL => array('a','b','c')
                    )
                ),
            ),
            'expected'  => array(
                'andWhere'    => array(
                    'method'    => 'andWhere',
                    'cnt'       => $this->once(),
                    'with'      => array('field = :field', array('field' => 'value'))
                ),
                'andWhereIn'    => array(
                    'method'    => 'andWhereIn',
                    'cnt'       => $this->once(),
                    'with'      => array('array_field', array('a','b','c'), false)
                ),
            )
        );

        $data['queryWithNotEqualOperator'] = array(
            'query'     => array(
                'query'    => array(
                    'field' => array(
                        Core_QueryLanguage_Query::OPERATOR_NOT_EQUAL => 'value'
                    ),
                    'array_field' => array(
                        Core_QueryLanguage_Query::OPERATOR_NOT_EQUAL => array('a','b','c')
                    )
                ),
            ),
            'expected'  => array(
                'andWhere'    => array(
                    'method'    => 'andWhere',
                    'cnt'       => $this->once(),
                    'with'      => array('field != :field', array('field' => 'value'))
                ),
                'andWhereIn'    => array(
                    'method'    => 'andWhereIn',
                    'cnt'       => $this->once(),
                    'with'      => array('array_field', array('a','b','c'), true)
                ),
            )
        );

        $data['queryWithContainsOperator'] = array(
            'query'     => array(
                'query'    => array(
                    'field' => array(
                        Core_QueryLanguage_Query::OPERATOR_CONTAINS => 'value'
                    ),
                    'array_field' => array(
                        Core_QueryLanguage_Query::OPERATOR_CONTAINS => array('a','b','c')
                    )
                ),
            ),
            'expected'  => array(
                'andWhere'    => array(
                    'method'    => 'andWhere',
                    'cnt'       => $this->once(),
                    'with'      => array('field LIKE :field', array('field' => '%value%'))
                ),
                'andWhereIn'    => array(
                    'method'    => 'andWhereIn',
                    'cnt'       => $this->once(),
                    'with'      => array('array_field', array('a','b','c'), false)
                ),
            )
        );

        $data['queryWithDoesNotContainOperator'] = array(
            'query'     => array(
                'query'    => array(
                    'field' => array(
                        Core_QueryLanguage_Query::OPERATOR_DOES_NOT_CONTAIN => 'value'
                    ),
                    'array_field' => array(
                        Core_QueryLanguage_Query::OPERATOR_DOES_NOT_CONTAIN => array('a','b','c')
                    )
                ),
            ),
            'expected'  => array(
                'andWhere'    => array(
                    'method'    => 'andWhere',
                    'cnt'       => $this->once(),
                    'with'      => array('field NOT LIKE :field', array('field' => '%value%'))
                ),
                'andWhereIn'    => array(
                    'method'    => 'andWhereIn',
                    'cnt'       => $this->once(),
                    'with'      => array('array_field', array('a','b','c'), true)
                ),
            )
        );

        $data['queryWithGreaterOperator'] = array(
            'query'     => array(
                'query'    => array(
                    'field' => array(
                        Core_QueryLanguage_Query::OPERATOR_GREATER => 'value'
                    )
                ),
            ),
            'expected'  => array(
                'andWhere'    => array(
                    'method'    => 'andWhere',
                    'cnt'       => $this->once(),
                    'with'      => array('field > :field', array('field' => 'value'))
                )
            )
        );

        $data['queryWithGreaterOrEqualOperator'] = array(
            'query'     => array(
                'query'    => array(
                    'field' => array(
                        Core_QueryLanguage_Query::OPERATOR_GREATER_OR_EQUAL => 'value'
                    )
                ),
            ),
            'expected'  => array(
                'andWhere'    => array(
                    'method'    => 'andWhere',
                    'cnt'       => $this->once(),
                    'with'      => array('field >= :field', array('field' => 'value'))
                )
            )
        );

        $data['queryWithLowerOperator'] = array(
            'query'     => array(
                'query'    => array(
                    'field' => array(
                        Core_QueryLanguage_Query::OPERATOR_LOWER => 'value'
                    )
                ),
            ),
            'expected'  => array(
                'andWhere'    => array(
                    'method'    => 'andWhere',
                    'cnt'       => $this->once(),
                    'with'      => array('field < :field', array('field' => 'value'))
                )
            )
        );

        $data['queryWithLowerOrEqualOperator'] = array(
            'query'     => array(
                'query'    => array(
                    'field' => array(
                        Core_QueryLanguage_Query::OPERATOR_LOWER_OR_EQUAL => 'value'
                    )
                ),
            ),
            'expected'  => array(
                'andWhere'    => array(
                    'method'    => 'andWhere',
                    'cnt'       => $this->once(),
                    'with'      => array('field <= :field', array('field' => 'value'))
                )
            )
        );

        $atCnt = 0;
        $data['joinBecauseOfSelect'] = array(
            'query'     => array(
                'select'    => array(
                    'Relation' => array(
                        'field',
                        'SubRelation' => array(
                            'sub_field'
                        ),
                    )
                )
            ),
            'expected'  => array(
                'getDqlPart'        => array(
                    'cnt'       => $this->at($atCnt++),
                    'will'      => $this->returnValue(array('Dummy AS '. self::ENTITY_ALIAS))
                ),
                'innerJoin:Relation'    => array(
                    'method'    => 'innerJoin',
                    'cnt'       => $this->at($atCnt++),
                    'with'      => self::ENTITY_ALIAS .'.Relation AS Relation'
                ),
                'innerJoin:Relation:SubRelation'    => array(
                    'method'    => 'innerJoin',
                    'cnt'       => $this->at($atCnt++),
                    'with'      => 'Relation.SubRelation AS Relation_SubRelation'
                )
            ),
            'mockMethods' => array('innerJoin', 'getDqlPart')
        );

        $atCnt = 0;
        $data['joinBecauseOfQuery'] = array(
            'query'     => array(
                'query'    => array(
                    'Relation' => array(
                        'related_field' => array(
                            Core_QueryLanguage_Query::OPERATOR_EQUAL => 'bla',
                        ),
                        'SubRelation' => array(
                            'sub_field' => array(
                                Core_QueryLanguage_Query::OPERATOR_EQUAL => array('alfa', 'beta', 'gamma')
                            ),
                        )
                    ),
                )
            ),
            'expected'  => array(
                'getDqlPart'        => array(
                    'cnt'       => $this->at($atCnt++),
                    'will'      => $this->returnValue(array('Dummy AS '. self::ENTITY_ALIAS))
                ),
                'innerJoin:Relation'    => array(
                    'method'    => 'innerJoin',
                    'cnt'       => $this->at($atCnt++),
                    'with'      => self::ENTITY_ALIAS .'.Relation AS Relation'
                ),
                'innerJoin:Relation:SubRelation'    => array(
                    'method'    => 'innerJoin',
                    'cnt'       => $this->at($atCnt++),
                    'with'      => 'Relation.SubRelation AS Relation_SubRelation'
                )
            ),
            'mockMethods' => array('innerJoin', 'getDqlPart')
        );

        $atCnt = 0;
        $data['joinBecauseOfSelectAndQuery'] = array(
            'query'     => array(
                'select'    => array(
                    'CommonRelation' => array(
                        'some_field'
                    ),
                    'SelectRelation' => array(
                        'field',
                        'SubRelation' => array(
                            'sub_field'
                        ),
                    )
                ),
                'query'    => array(
                    'CommonRelation' => array(
                        'field' => array(
                            Core_QueryLanguage_Query::OPERATOR_EQUAL => 'bla',
                        )
                    ),
                    'QueryRelation' => array(
                        'related_field' => array(
                            Core_QueryLanguage_Query::OPERATOR_EQUAL => 'bla',
                        ),
                        'SubRelation' => array(
                            'sub_field' => array(
                                Core_QueryLanguage_Query::OPERATOR_EQUAL => 'bla'
                            ),
                        )
                    ),
                )
            ),
            'expected'  => array(
                'select:CommonRelation:some_field' => array(
                    'method'    => 'addSelect',
                    'cnt'       => $this->at($atCnt++),
                    'with'      => 'CommonRelation.some_field'
                ),
                'select:SelectRelation:field' => array(
                    'method'    => 'addSelect',
                    'cnt'       => $this->at($atCnt++),
                    'with'      => 'SelectRelation.field'
                ),
                'select:SelectRelation:SubRelation.sub_field' => array(
                    'method'    => 'addSelect',
                    'cnt'       => $this->at($atCnt++),
                    'with'      => 'SelectRelation_SubRelation.sub_field'
                ),
                // Need this mock before joins or wheres
                'getDqlPart'        => array(
                    'cnt'       => $this->at($atCnt++),
                    'will'      => $this->returnValue(array('Dummy AS '. self::ENTITY_ALIAS))
                ),
                'innerJoin:CommonRelation'    => array(
                    'method'    => 'innerJoin',
                    'cnt'       => $this->at($atCnt++),
                    'with'      => self::ENTITY_ALIAS .'.CommonRelation AS CommonRelation'
                ),
                'innerJoin:SelectRelation'    => array(
                    'method'    => 'innerJoin',
                    'cnt'       => $this->at($atCnt++),
                    'with'      => self::ENTITY_ALIAS .'.SelectRelation AS SelectRelation'
                ),
                'innerJoin:SelectRelation:SubRelation'    => array(
                    'method'    => 'innerJoin',
                    'cnt'       => $this->at($atCnt++),
                    'with'      => 'SelectRelation.SubRelation AS SelectRelation_SubRelation'
                ),
                'innerJoin:QueryRelation'    => array(
                    'method'    => 'innerJoin',
                    'cnt'       => $this->at($atCnt++),
                    'with'      => self::ENTITY_ALIAS .'.QueryRelation AS QueryRelation'
                ),
                'innerJoin:QueryRelation:SubRelation'    => array(
                    'method'    => 'innerJoin',
                    'cnt'       => $this->at($atCnt++),
                    'with'      => 'QueryRelation.SubRelation AS QueryRelation_SubRelation'
                ),
                'andWhere:CommonRelation:field'    => array(
                    'method'    => 'andWhere',
                    'cnt'       => $this->at($atCnt++),
                    'with'      => array('CommonRelation.field = :CommonRelation_field', array('CommonRelation_field' => 'bla'))
                ),
                'andWhere:QueryRelation:related_field'    => array(
                    'method'    => 'andWhere',
                    'cnt'       => $this->at($atCnt++),
                    'with'      => array('QueryRelation.related_field = :QueryRelation_related_field', array('QueryRelation_related_field' => 'bla'))
                ),
                'andWhere:QueryRelation:SubRelation:sub_field'    => array(
                    'method'    => 'andWhere',
                    'cnt'       => $this->at($atCnt++),
                    'with'      => array('QueryRelation.SubRelation.sub_field = :QueryRelation_SubRelation_sub_field', array('QueryRelation_SubRelation_sub_field' => 'bla'))
                ),
            ),
            'mockMethods' => array('addSelect', 'innerJoin', 'andWhere', 'getDqlPart')
        );

        $data['orderWithOneField'] = array(
            'query'     => array(
                'order'    => 'field ASC',
            ),
            'expected'  => array(
                'addOrderBy'    => array(
                    'cnt'       => $this->once(),
                    'with'      => 'field ASC'
                )
            )
        );

        $atCnt = 0;
        $data['orderWithMultipleFields'] = array(
            'query'     => array(
                'order'    => array('field' => 'ASC', 'otherField' => 'DESC'),
            ),
            'expected'  => array(
                'addOrderBy:field'    => array(
                    'method'    => 'addOrderBy',
                    'cnt'       => $this->at($atCnt++),
                    'with'      => 'field ASC'
                ),
                'addOrderBy:otherField'    => array(
                    'method'    => 'addOrderBy',
                    'cnt'       => $this->at($atCnt++),
                    'with'      => 'otherField DESC'
                )
            )
        );

        $data['offsetAndLimit'] = array(
            'query'     => array(
                'offset'    => 10,
                'limit'     => 100
            ),
            'expected'  => array(
                'offset'    => array(
                    'cnt'       => $this->once(),
                    'with'      => 10
                ),
                'limit'    => array(
                    'cnt'       => $this->once(),
                    'with'      => 100
                )
            )
        );

        return $data;
    }
}
