<?php

/*
 * This file is part of the DataGridBundle.
 *
 * (c) Abhoryo <abhoryo@free.fr>
 * (c) Stanislav Turza
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 *
 */

namespace APY\DataGridBundle\Grid;

use APY\DataGridBundle\Grid\Action\MassActionInterface;
use APY\DataGridBundle\Grid\Action\RowActionInterface;
use APY\DataGridBundle\Grid\Column\ActionsColumn;
use APY\DataGridBundle\Grid\Column\Column;
use APY\DataGridBundle\Grid\Column\MassActionColumn;
use APY\DataGridBundle\Grid\Export\ExportInterface;
use APY\DataGridBundle\Grid\Source\Entity;
use APY\DataGridBundle\Grid\Source\Source;
use Symfony\Component\DependencyInjection\Container;
use Symfony\Component\DependencyInjection\ContainerAwareInterface;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;

class Grid implements GridInterface
{
    const REQUEST_QUERY_MASS_ACTION_ALL_KEYS_SELECTED = '__action_all_keys';
    const REQUEST_QUERY_MASS_ACTION = '__action_id';
    const REQUEST_QUERY_EXPORT = '__export_id';
    const REQUEST_QUERY_TWEAK = '__tweak_id';
    const REQUEST_QUERY_PAGE = '_page';
    const REQUEST_QUERY_LIMIT = '_limit';
    const REQUEST_QUERY_ORDER = '_order';
    const REQUEST_QUERY_TEMPLATE = '_template';
    const REQUEST_QUERY_RESET = '_reset';

    const SOURCE_ALREADY_SETTED_EX_MSG = 'The source of the grid is already set.';
    const SOURCE_NOT_SETTED_EX_MSG = 'The source of the grid must be set.';
    const TWEAK_MALFORMED_ID_EX_MSG = 'Tweak id "%s" is malformed. The id have to match this regex ^[0-9a-zA-Z_\+-]+';
    const TWIG_TEMPLATE_LOAD_EX_MSG = 'Unable to load template';
    const NOT_VALID_LIMIT_EX_MSG = 'Limit has to be array or integer';
    const NOT_VALID_PAGE_NUMBER_EX_MSG = 'Page must be a positive number';
    const NOT_VALID_MAX_RESULT_EX_MSG = 'Max results must be a positive number.';
    const MASS_ACTION_NOT_DEFINED_EX_MSG = 'Action %s is not defined.';
    const MASS_ACTION_CALLBACK_NOT_VALID_EX_MSG = 'Callback %s is not callable or Controller action';
    const EXPORT_NOT_DEFINED_EX_MSG = 'Export %s is not defined.';
    const PAGE_NOT_VALID_EX_MSG = 'Page must be a positive number';
    const COLUMN_ORDER_NOT_VALID_EX_MSG = '%s is not a valid order.';
    const DEFAULT_LIMIT_NOT_VALID_EX_MSG = 'Limit must be a positive number';
    const LIMIT_NOT_DEFINED_EX_MSG = 'Limit %s is not defined in limits.';
    const NO_ROWS_RETURNED_EX_MSG = 'Source have to return Rows object.';
    const INVALID_TOTAL_COUNT_EX_MSG = 'Source function getTotalCount need to return integer result, returned: %s';
    const NOT_VALID_TWEAK_ID_EX_MSG = 'Tweak with id "%s" doesn\'t exists';
    const GET_FILTERS_NO_REQUEST_HANDLED_EX_MSG = 'getFilters method is only available in the manipulate callback function or after the call of the method isRedirected of the grid.';
    const HAS_FILTER_NO_REQUEST_HANDLED_EX_MSG = 'hasFilters method is only available in the manipulate callback function or after the call of the method isRedirected of the grid.';
    const TWEAK_NOT_DEFINED_EX_MSG = 'Tweak %s is not defined.';

    /**
     * @var \Symfony\Component\DependencyInjection\Container
     */
    protected $container;

    /**
     * @var \Symfony\Component\Routing\Router
     */
    protected $router;

    /**
     * @var \Symfony\Component\HttpFoundation\Session\Session;
     */
    protected $session;

    /**
     * @var \Symfony\Component\HttpFoundation\Request
     */
    protected $request;

    /**
     * @var \Symfony\Component\Security\Core\Authorization\AuthorizationChecker
     */
    protected $securityContext;

    /**
     * @var string
     */
    protected $id;

    /**
     * @var string
     */
    protected $hash;

    /**
     * @var string
     */
    protected $routeUrl;

    /**
     * @var array
     */
    protected $routeParameters;

    /**
     * @var \APY\DataGridBundle\Grid\Source\Source
     */
    protected $source;

    /**
     * @var bool
     */
    protected $prepared = false;

    /**
     * @var int
     */
    protected $totalCount;

    /**
     * @var int
     */
    protected $page = 0;

    /**
     * @var int
     */
    protected $limit;

    /**
     * @var array
     */
    protected $limits = [];

    /**
     * @var \APY\DataGridBundle\Grid\Columns|\APY\DataGridBundle\Grid\Column\Column[]
     */
    protected $columns;

    /**
     * @var \APY\DataGridBundle\Grid\Rows
     */
    protected $rows;

    /**
     * @var \APY\DataGridBundle\Grid\Action\MassAction[]
     */
    protected $massActions = [];

    /**
     * @var \APY\DataGridBundle\Grid\Action\RowAction[]
     */
    protected $rowActions = [];

    /**
     * @var bool
     */
    protected $showFilters = true;

    /**
     * @var bool
     */
    protected $showTitles = true;

    /**
     * @var array|object request
     */
    protected $requestData;

    /**
     * @var array|object session
     */
    protected $sessionData = [];

    /**
     * @var string
     */
    protected $prefixTitle = '';

    /**
     * @var bool
     */
    protected $persistence = false;

    /**
     * @var bool
     */
    protected $newSession = false;

    /**
     * @var string
     */
    protected $noDataMessage;

    /**
     * @var string
     */
    protected $noResultMessage;

    /**
     * @var \APY\DataGridBundle\Grid\Export\Export[]
     */
    protected $exports = [];

    /**
     * @var bool
     */
    protected $redirect = null;

    /**
     * @var bool
     */
    protected $isReadyForExport = false;

    /**
     * @var Response
     */
    protected $exportResponse;

    /**
     * @var Response
     */
    protected $massActionResponse;

    /**
     * @var int
     */
    protected $maxResults;

    /**
     * @var array
     */
    protected $items = [];

    /**
     * Data junction of the grid.
     *
     * @var int
     */
    protected $dataJunction = Column::DATA_CONJUNCTION;

    /**
     * Permanent filters.
     *
     * @var array
     */
    protected $permanentFilters = [];

    /**
     * Default filters.
     *
     * @var array
     */
    protected $defaultFilters = [];

    /**
     * Default order (e.g. my_column_id|asc).
     *
     * @var string
     */
    protected $defaultOrder;

    /**
     * Default limit.
     *
     * @var int
     */
    protected $defaultLimit;

    /**
     * Default page.
     *
     * @var int
     */
    protected $defaultPage;

    /**
     * Tweaks.
     *
     * @var array
     */
    protected $tweaks = [];

    /**
     * Default Tweak.
     *
     * @var string
     */
    protected $defaultTweak;

    /**
     * Filters in session.
     *
     * @var array
     */
    protected $sessionFilters;

    // Lazy parameters
    protected $lazyAddColumn = [];
    protected $lazyHiddenColumns = [];
    protected $lazyVisibleColumns = [];
    protected $lazyHideShowColumns = [];

    // Lazy parameters for the action column
    protected $actionsColumnSize;
    protected $actionsColumnTitle;

    /**
     * The grid configuration.
     *
     * @var GridConfigInterface
     */
    private $config;

    /**
     * Constructor.
     *
     * @param Container                $container
     * @param string                   $id        set if you are using more then one grid inside controller
     * @param GridConfigInterface|null $config    The grid configuration.
     */
    public function __construct($container, $id = '', GridConfigInterface $config = null)
    {
        // @todo: why the whole container is injected?
        $this->container = $container;
        $this->config = $config;

        $this->router = $container->get('router');
        $this->request = $container->get('request_stack')->getCurrentRequest();
        $this->session = $this->request->getSession();
        $this->securityContext = $container->get('security.authorization_checker');

        $this->id = $id;

        $this->columns = new Columns($this->securityContext);

        $this->routeParameters = $this->request->attributes->all();
        foreach (array_keys($this->routeParameters) as $key) {
            if (substr($key, 0, 1) == '_') {
                unset($this->routeParameters[$key]);
            }
        }
    }

    /**
     * {@inheritdoc}
     */
    public function initialize()
    {
        $config = $this->config;

        if (!$config) {
            return $this;
        }

        $this->setPersistence($config->isPersisted());

        // Route parameters
        $routeParameters = $config->getRouteParameters();
        if (!empty($routeParameters)) {
            foreach ($routeParameters as $parameter => $value) {
                $this->setRouteParameter($parameter, $value);
            }
        }

        // Route
        if (null !== $config->getRoute()) {
            $this->setRouteUrl($this->router->generate($config->getRoute(), $routeParameters));
        }

        // Route
        if (null !== $config->getRoute()) {
            $this->setRouteUrl($this->router->generate($config->getRoute(), $routeParameters));
        }

        // Columns
        foreach ($this->lazyAddColumn as $columnInfo) {
            /** @var Column $column */
            $column = $columnInfo['column'];

            if (!$config->isFilterable()) {
                $column->setFilterable(false);
            }

            if (!$config->isSortable()) {
                $column->setSortable(false);
            }
        }

        // Source
        $source = $config->getSource();

        if (null !== $source) {
            $this->source = $source;

            $source->initialise($this->container);

            if ($source instanceof Entity) {
                $groupBy = $config->getGroupBy();
                if (null !== $groupBy) {
                    if (!is_array($groupBy)) {
                        $groupBy = [$groupBy];
                    }

                    // Must be set after source because initialize method reset groupBy property
                    $source->setGroupBy($groupBy);
                }
            }
        }

        // Order
        if (null !== $config->getSortBy()) {
            $this->setDefaultOrder($config->getSortBy(), $config->getOrder());
        }

        if (null !== $config->getMaxPerPage()) {
            $this->setLimits($config->getMaxPerPage());
        }

        $this
            ->setMaxResults($config->getMaxResults())
            ->setPage($config->getPage());

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function handleRequest(Request $request)
    {
        if (null === $this->source) {
            throw new \LogicException(self::SOURCE_NOT_SETTED_EX_MSG);
        }

        $this->request = $request;
        $this->session = $request->getSession();

        $this->createHash();

        $this->requestData = $request->get($this->hash);

        $this->processPersistence();

        $this->sessionData = (array) $this->session->get($this->hash);

        $this->processLazyParameters();

        if (!empty($this->requestData)) {
            $this->processRequestData();
        }

        if ($this->newSession) {
            $this->setDefaultSessionData();
        }

        $this->processPermanentFilters();

        $this->processSessionData();

        $this->prepare();

        return $this;
    }

    /**
     * Sets Source to the Grid.
     *
     * @param $source
     *
     * @throws \InvalidArgumentException
     *
     * @return self
     */
    public function setSource(Source $source)
    {
        if ($this->source !== null) {
            throw new \InvalidArgumentException(self::SOURCE_ALREADY_SETTED_EX_MSG);
        }

        $this->source = $source;

        $this->source->initialise($this->container);

        // Get columns from the source
        $this->source->getColumns($this->columns);

        return $this;
    }

    public function getSource()
    {
        return $this->source;
    }

    /**
     * Handle the grid redirection, export, etc..
     */
    public function isReadyForRedirect()
    {
        if ($this->source === null) {
            throw new \Exception(self::SOURCE_NOT_SETTED_EX_MSG);
        }

        if ($this->redirect !== null) {
            return $this->redirect;
        }

        $this->createHash();

        $this->requestData = (array) $this->request->get($this->hash);

        $this->processPersistence();

        $this->sessionData = (array) $this->session->get($this->hash);

        $this->processLazyParameters();

        // isReadyForRedirect ?
        if (!empty($this->requestData)) {
            $this->processRequestData();

            $this->redirect = true;
        }

        if ($this->redirect === null || ($this->request->isXmlHttpRequest() && !$this->isReadyForExport)) {
            if ($this->newSession) {
                $this->setDefaultSessionData();
            }

            $this->processPermanentFilters();

            //Configures the grid with the data read from the session.
            $this->processSessionData();

            $this->prepare();

            $this->redirect = false;
        }

        return $this->redirect;
    }

    protected function getCurrentUri()
    {
        return $this->request->getScheme() . '://' . $this->request->getHttpHost() . $this->request->getBaseUrl() . $this->request->getPathInfo();
    }

    protected function processPersistence()
    {
        $referer = strtok($this->request->headers->get('referer'), '?');

        // Persistence or reset - kill previous session
        if ((!$this->request->isXmlHttpRequest() && !$this->persistence && $referer != $this->getCurrentUri())
            || isset($this->requestData[self::REQUEST_QUERY_RESET])) {
            $this->session->remove($this->hash);
        }

        if ($this->session->get($this->hash) === null) {
            $this->newSession = true;
        }
    }

    protected function processLazyParameters()
    {
        // Additional columns
        foreach ($this->lazyAddColumn as $column) {
            $this->columns->addColumn($column['column'], $column['position']);
        }

        // Hidden columns
        foreach ($this->lazyHiddenColumns as $columnId) {
            $this->columns->getColumnById($columnId)->setVisible(false);
        }

        // Visible columns
        if (!empty($this->lazyVisibleColumns)) {
            $columnNames = [];
            foreach ($this->columns as $column) {
                $columnNames[] = $column->getId();
            }

            foreach (array_diff($columnNames, $this->lazyVisibleColumns) as $columnId) {
                $this->columns->getColumnById($columnId)->setVisible(false);
            }
        }

        // Hide and Show columns
        foreach ($this->lazyHideShowColumns as $columnId => $visible) {
            $this->columns->getColumnById($columnId)->setVisible($visible);
        }
    }

    /**
     * Reads data from the request and write this data to the session.
     */
    protected function processRequestData()
    {
        $this->processMassActions($this->getFromRequest(self::REQUEST_QUERY_MASS_ACTION));

        if ($this->processExports($this->getFromRequest(self::REQUEST_QUERY_EXPORT))
            || $this->processTweaks($this->getFromRequest(self::REQUEST_QUERY_TWEAK))) {
            return;
        }

        $filtering = $this->processRequestFilters();

        $this->processPage($this->getFromRequest(self::REQUEST_QUERY_PAGE), $filtering);

        $this->processOrder($this->getFromRequest(self::REQUEST_QUERY_ORDER));

        $this->processLimit($this->getFromRequest(self::REQUEST_QUERY_LIMIT));

        $this->saveSession();
    }

    /**
     * Process mass actions.
     *
     * @param int $actionId
     *
     * @throws \RuntimeException
     * @throws \OutOfBoundsException
     */
    protected function processMassActions($actionId)
    {
        if ($actionId > -1 && '' !== $actionId) {
            if (array_key_exists($actionId, $this->massActions)) {
                $action = $this->massActions[$actionId];
                $actionAllKeys = (boolean) $this->getFromRequest(self::REQUEST_QUERY_MASS_ACTION_ALL_KEYS_SELECTED);
                $actionKeys = $actionAllKeys === false ? array_keys((array) $this->getFromRequest(MassActionColumn::ID)) : [];

                $this->processSessionData();
                if ($actionAllKeys) {
                    $this->page = 0;
                    $this->limit = 0;
                }

                $this->prepare();

                if ($actionAllKeys === true) {
                    foreach ($this->rows as $row) {
                        $actionKeys[] = $row->getPrimaryFieldValue();
                    }
                }

                if (is_callable($action->getCallback())) {
                    $this->massActionResponse = call_user_func($action->getCallback(), $actionKeys, $actionAllKeys, $this->session, $action->getParameters());
                } elseif (strpos($action->getCallback(), ':') !== false) {
                    $path = array_merge(
                        [
                            'primaryKeys'    => $actionKeys,
                            'allPrimaryKeys' => $actionAllKeys,
                            '_controller'    => $action->getCallback(),
                        ],
                        $action->getParameters()
                    );

                    $subRequest = $this->request->duplicate([], null, $path);

                    $this->massActionResponse = $this->container->get('http_kernel')->handle($subRequest, \Symfony\Component\HttpKernel\HttpKernelInterface::SUB_REQUEST);
                } else {
                    throw new \RuntimeException(sprintf(self::MASS_ACTION_CALLBACK_NOT_VALID_EX_MSG, $action->getCallback()));
                }
            } else {
                throw new \OutOfBoundsException(sprintf(self::MASS_ACTION_NOT_DEFINED_EX_MSG, $actionId));
            }
        }
    }

    /**
     * Process exports.
     *
     * @param int $exportId
     *
     * @throws \OutOfBoundsException
     *
     * @return bool
     */
    protected function processExports($exportId)
    {
        if ($exportId > -1 && '' !== $exportId) {
            if (array_key_exists($exportId, $this->exports)) {
                $this->isReadyForExport = true;

                $this->processSessionData();
                $this->page = 0;
                $this->limit = 0;
                $this->prepare();

                $export = $this->exports[$exportId];
                if ($export instanceof ContainerAwareInterface) {
                    $export->setContainer($this->container);
                }
                $export->computeData($this);

                $this->exportResponse = $export->getResponse();

                return true;
            } else {
                throw new \OutOfBoundsException(sprintf(self::EXPORT_NOT_DEFINED_EX_MSG, $exportId));
            }
        }

        return false;
    }

    /**
     * Process tweaks.
     *
     * @param int $tweakId
     *
     * @throws \OutOfBoundsException
     *
     * @return bool
     */
    protected function processTweaks($tweakId)
    {
        if ($tweakId !== null) {
            if (array_key_exists($tweakId, $this->tweaks)) {
                $tweak = $this->tweaks[$tweakId];
                $saveAsActive = false;

                if (isset($tweak['reset'])) {
                    $this->sessionData = [];
                    $this->session->remove($this->hash);
                }

                if (isset($tweak['filters'])) {
                    $this->defaultFilters = [];
                    $this->setDefaultFilters($tweak['filters']);
                    $this->processDefaultFilters();
                    $saveAsActive = true;
                }

                if (isset($tweak['order'])) {
                    $this->processOrder($tweak['order']);
                    $saveAsActive = true;
                }

                if (isset($tweak['massAction'])) {
                    $this->processMassActions($tweak['massAction']);
                }

                if (isset($tweak['page'])) {
                    $this->processPage($tweak['page']);
                    $saveAsActive = true;
                }

                if (isset($tweak['limit'])) {
                    $this->processLimit($tweak['limit']);
                    $saveAsActive = true;
                }

                if (isset($tweak['export'])) {
                    $this->processExports($tweak['export']);
                }

                if ($saveAsActive) {
                    $activeTweaks = $this->getActiveTweaks();
                    $activeTweaks[$tweak['group']] = $tweakId;
                    $this->set('tweaks', $activeTweaks);
                }

                if (isset($tweak['removeActiveTweaksGroups'])) {
                    $removeActiveTweaksGroups = (array) $tweak['removeActiveTweaksGroups'];
                    $activeTweaks = $this->getActiveTweaks();
                    foreach ($removeActiveTweaksGroups as $id) {
                        if (isset($activeTweaks[$id])) {
                            unset($activeTweaks[$id]);
                        }
                    }

                    $this->set('tweaks', $activeTweaks);
                }

                if (isset($tweak['removeActiveTweaks'])) {
                    $removeActiveTweaks = (array) $tweak['removeActiveTweaks'];
                    $activeTweaks = $this->getActiveTweaks();
                    foreach ($removeActiveTweaks as $id) {
                        if (array_key_exists($id, $this->tweaks)) {
                            if (isset($activeTweaks[$this->tweaks[$id]['group']])) {
                                unset($activeTweaks[$this->tweaks[$id]['group']]);
                            }
                        }
                    }

                    $this->set('tweaks', $activeTweaks);
                }

                if (isset($tweak['addActiveTweaks'])) {
                    $addActiveTweaks = (array) $tweak['addActiveTweaks'];
                    $activeTweaks = $this->getActiveTweaks();
                    foreach ($addActiveTweaks as $id) {
                        if (array_key_exists($id, $this->tweaks)) {
                            $activeTweaks[$this->tweaks[$id]['group']] = $id;
                        }
                    }

                    $this->set('tweaks', $activeTweaks);
                }

                $this->saveSession();

                return true;
            } else {
                throw new \OutOfBoundsException(sprintf(self::TWEAK_NOT_DEFINED_EX_MSG, $tweakId));
            }
        }

        return false;
    }

    protected function processRequestFilters()
    {
        $filtering = false;
        foreach ($this->columns as $column) {
            if ($column->isFilterable()) {
                $ColumnId = $column->getId();

                // Get data from request
                $data = $this->getFromRequest($ColumnId);

                //if no item is selectd in multi select filter : simulate empty first choice
                if ($column->getFilterType() == 'select'
                    && $column->getSelectMulti() === true
                    && $data === null
                    && $this->getFromRequest(self::REQUEST_QUERY_PAGE) === null
                    && $this->getFromRequest(self::REQUEST_QUERY_ORDER) === null
                    && $this->getFromRequest(self::REQUEST_QUERY_LIMIT) === null
                    && ($this->getFromRequest(self::REQUEST_QUERY_MASS_ACTION) === null || $this->getFromRequest(self::REQUEST_QUERY_MASS_ACTION) == '-1')) {
                    $data = ['from' => ''];
                }

                // Store in the session
                $this->set($ColumnId, $data);

                // Filtering ?
                if (!$filtering && $data !== null) {
                    $filtering = true;
                }
            }
        }

        return $filtering;
    }

    protected function processPage($page, $filtering = false)
    {
        // Set to the first page if this is a request of order, limit, mass action or filtering
        if ($this->getFromRequest(self::REQUEST_QUERY_ORDER) !== null
            || $this->getFromRequest(self::REQUEST_QUERY_LIMIT) !== null
            || $this->getFromRequest(self::REQUEST_QUERY_MASS_ACTION) !== null
            || $filtering) {
            $this->set(self::REQUEST_QUERY_PAGE, 0);
        } else {
            $this->set(self::REQUEST_QUERY_PAGE, $page);
        }
    }

    protected function processOrder($order)
    {
        if ($order !== null) {
            list($columnId, $columnOrder) = explode('|', $order);

            $column = $this->columns->getColumnById($columnId);
            if ($column->isSortable() && in_array(strtolower($columnOrder), ['asc', 'desc'])) {
                $this->set(self::REQUEST_QUERY_ORDER, $order);
            }
        }
    }

    protected function processLimit($limit)
    {
        if (isset($this->limits[$limit])) {
            $this->set(self::REQUEST_QUERY_LIMIT, $limit);
        }
    }

    protected function setDefaultSessionData()
    {
        // Default filters
        $this->processDefaultFilters();

        // Default page
        if ($this->defaultPage !== null) {
            if ((int) $this->defaultPage >= 0) {
                $this->set(self::REQUEST_QUERY_PAGE, $this->defaultPage);
            } else {
                throw new \InvalidArgumentException(self::NOT_VALID_PAGE_NUMBER_EX_MSG);
            }
        }

        // Default order
        if ($this->defaultOrder !== null) {
            list($columnId, $columnOrder) = explode('|', $this->defaultOrder);

            $this->columns->getColumnById($columnId);
            if (in_array(strtolower($columnOrder), ['asc', 'desc'])) {
                $this->set(self::REQUEST_QUERY_ORDER, $this->defaultOrder);
            } else {
                throw new \InvalidArgumentException(sprintf(self::COLUMN_ORDER_NOT_VALID_EX_MSG, $columnOrder));
            }
        }

        if ($this->defaultLimit !== null) {
            if ((int) $this->defaultLimit >= 0) {
                if (isset($this->limits[$this->defaultLimit])) {
                    $this->set(self::REQUEST_QUERY_LIMIT, $this->defaultLimit);
                } else {
                    throw new \InvalidArgumentException(sprintf(self::LIMIT_NOT_DEFINED_EX_MSG, $this->defaultLimit));
                }
            } else {
                throw new \InvalidArgumentException(self::DEFAULT_LIMIT_NOT_VALID_EX_MSG);
            }
        }

        // Default tweak
        if ($this->defaultTweak !== null) {
            $this->processTweaks($this->defaultTweak);
        }
        $this->saveSession();
    }

    /**
     * Store permanent filters to the session and disable the filter capability for the column if there are permanent filters.
     */
    protected function processFilters($permanent = true)
    {
        foreach (($permanent ? $this->permanentFilters : $this->defaultFilters) as $columnId => $value) {
            /* @var $column Column */
            $column = $this->columns->getColumnById($columnId);

            if ($permanent) {
                // Disable the filter capability for the column
                $column->setFilterable(false);
            }

            // Convert simple value
            if (!is_array($value) || !is_string(key($value))) {
                $value = ['from' => $value];
            }

            // Convert boolean value
            if (isset($value['from']) && is_bool($value['from'])) {
                $value['from'] = $value['from'] ? '1' : '0';
            }

            // Convert simple value with select filter
            if ($column->getFilterType() === 'select') {
                if (isset($value['from']) && !is_array($value['from'])) {
                    $value['from'] = [$value['from']];
                }

                if (isset($value['to']) && !is_array($value['to'])) {
                    $value['to'] = [$value['to']];
                }
            }

            // Store in the session
            $this->set($columnId, $value);
        }
    }

    protected function processPermanentFilters()
    {
        $this->processFilters();
        $this->saveSession();
    }

    protected function processDefaultFilters()
    {
        $this->processFilters(false);
    }

    /**
     * Configures the grid with the data read from the session.
     */
    protected function processSessionData()
    {
        // Filters
        foreach ($this->columns as $column) {
            if (($data = $this->get($column->getId())) !== null) {
                $column->setData($data);
            }
        }

        // Page
        if (($page = $this->get(self::REQUEST_QUERY_PAGE)) !== null) {
            $this->setPage($page);
        } else {
            $this->setPage(0);
        }

        // Order
        if (($order = $this->get(self::REQUEST_QUERY_ORDER)) !== null) {
            list($columnId, $columnOrder) = explode('|', $order);

            $this->columns->getColumnById($columnId)->setOrder($columnOrder);
        }

        // Limit
        if (($limit = $this->get(self::REQUEST_QUERY_LIMIT)) !== null) {
            $this->limit = $limit;
        } else {
            $this->limit = key($this->limits);
        }
    }

    /**
     * Prepare Grid for Drawing.
     *
     * @throws \Exception
     *
     * @return self
     */
    protected function prepare()
    {
        if ($this->prepared) {
            return $this;
        }

        if ($this->source->isDataLoaded()) {
            $this->rows = $this->source->executeFromData($this->columns->getIterator(true), $this->page, $this->limit, $this->maxResults);
        } else {
            $this->rows = $this->source->execute($this->columns->getIterator(true), $this->page, $this->limit, $this->maxResults, $this->dataJunction);
        }

        if (!$this->rows instanceof Rows) {
            throw new \Exception(self::NO_ROWS_RETURNED_EX_MSG);
        }

        if (count($this->rows) == 0 && $this->page > 0) {
            $this->page = 0;
            $this->prepare();

            return $this;
        }

        //add row actions column
        if (count($this->rowActions) > 0) {
            foreach ($this->rowActions as $column => $rowActions) {
                if (($actionColumn = $this->columns->hasColumnById($column, true))) {
                    $actionColumn->setRowActions($rowActions);
                } else {
                    $actionColumn = new ActionsColumn($column, $this->actionsColumnTitle, $rowActions);
                    if ($this->actionsColumnSize > -1) {
                        $actionColumn->setSize($this->actionsColumnSize);
                    }

                    $this->columns->addColumn($actionColumn);
                }
            }
        }

        //add mass actions column
        if (count($this->massActions) > 0) {
            $this->columns->addColumn(new MassActionColumn(), 1);
        }

        $primaryColumnId = $this->columns->getPrimaryColumn()->getId();

        foreach ($this->rows as $row) {
            $row->setPrimaryField($primaryColumnId);
        }

        //get size
        if ($this->source->isDataLoaded()) {
            $this->source->populateSelectFiltersFromData($this->columns);
            $this->totalCount = $this->source->getTotalCountFromData($this->maxResults);
        } else {
            $this->source->populateSelectFilters($this->columns);
            $this->totalCount = $this->source->getTotalCount($this->maxResults);
        }

        if (!is_int($this->totalCount)) {
            throw new \Exception(sprintf(self::INVALID_TOTAL_COUNT_EX_MSG, gettype($this->totalCount)));
        }

        $this->prepared = true;

        return $this;
    }

    /**
     * Reads data from the request.
     *
     * @param string $key A unique key identifying the data
     *
     * @return mixed Data associated with the key or null if the key is not found
     */
    protected function getFromRequest($key)
    {
        if (isset($this->requestData[$key])) {
            return $this->requestData[$key];
        }
    }

    /**
     * Reads data from the session.
     *
     * @param string $key A unique key identifying your data
     *
     * @return mixed Data associated with the key or null if the key is not found
     */
    protected function get($key)
    {
        if (isset($this->sessionData[$key])) {
            return $this->sessionData[$key];
        }
    }

    /**
     * Writes data to the session.
     *
     * @param string $key  A unique key identifying the data
     * @param mixed  $data Data associated with the key
     */
    protected function set($key, $data)
    {
        // Only the filters values are removed from the session
        $fromIsEmpty = isset($data['from']) && ((is_string($data['from']) && $data['from'] === '') || (is_array($data['from']) && $data['from'][0] === ''));
        $toIsSet = isset($data['to']) && (is_string($data['to']) && $data['to'] !== '');
        if ($fromIsEmpty && !$toIsSet) {
            if (array_key_exists($key, $this->sessionData)) {
                unset($this->sessionData[$key]);
            }
        } elseif ($data !== null) {
            $this->sessionData[$key] = $data;
        }
    }

    protected function saveSession()
    {
        if (!empty($this->sessionData)) {
            $this->session->set($this->hash, $this->sessionData);
        }
    }

    protected function createHash()
    {
        $this->hash = 'grid_' . (empty($this->id) ? md5($this->request->get('_controller') . $this->columns->getHash() . $this->source->getHash()) : $this->getId());
    }

    public function getHash()
    {
        return $this->hash;
    }

    /**
     * Adds custom column to the grid.
     *
     * @param $column
     * @param int $position
     *
     * @return self
     */
    public function addColumn($column, $position = 0)
    {
        $this->lazyAddColumn[] = ['column' => $column, 'position' => $position];

        return $this;
    }

    /**
     * Get a column by its identifier.
     *
     * @param $columnId
     *
     * @return Column
     */
    public function getColumn($columnId)
    {
        foreach ($this->lazyAddColumn as $column) {
            if ($column['column']->getId() == $columnId) {
                return $column['column'];
            }
        }

        return $this->columns->getColumnById($columnId);
    }

    /**
     * Returns Grid Columns.
     *
     * @return Column[]|Columns
     */
    public function getColumns()
    {
        return $this->columns;
    }

    /**
     * Returns true if column exists in columns and lazyAddColumn properties.
     *
     * @param $columnId
     *
     * @return bool
     */
    public function hasColumn($columnId)
    {
        foreach ($this->lazyAddColumn as $column) {
            if ($column['column']->getId() == $columnId) {
                return true;
            }
        }

        return $this->columns->hasColumnById($columnId);
    }

    /**
     * Sets Array of Columns to the grid.
     *
     * @param $columns
     *
     * @return self
     */
    public function setColumns(Columns $columns)
    {
        $this->columns = $columns;

        return $this;
    }

    /**
     * Sets order of Columns passing an array of column ids
     * If the list of ids is uncomplete, the remaining columns will be
     * placed after.
     *
     * @param array $columnIds
     * @param bool  $keepOtherColumns
     *
     * @return self
     */
    public function setColumnsOrder(array $columnIds, $keepOtherColumns = true)
    {
        $this->columns->setColumnsOrder($columnIds, $keepOtherColumns);

        return $this;
    }

    /**
     * Adds Mass Action.
     *
     * @param Action\MassActionInterface $action
     *
     * @return self
     */
    public function addMassAction(MassActionInterface $action)
    {
        if ($action->getRole() === null || $this->securityContext->isGranted($action->getRole())) {
            $this->massActions[] = $action;
        }

        return $this;
    }

    /**
     * Returns Mass Actions.
     *
     * @return Action\MassAction[]
     */
    public function getMassActions()
    {
        return $this->massActions;
    }

    /**
     * Add a tweak.
     *
     * @param string $title title of the tweak
     * @param array  $tweak array('filters' => array, 'order' => 'colomunId|order', 'page' => integer, 'limit' => integer, 'export' => integer, 'massAction' => integer)
     * @param string $id    id of the tweak matching the regex ^[0-9a-zA-Z_\+-]+
     * @param string $group group of the tweak
     *
     * @return self
     */
    public function addTweak($title, array $tweak, $id = null, $group = null)
    {
        if ($id !== null && !preg_match('/^[0-9a-zA-Z_\+-]+$/', $id)) {
            throw new \InvalidArgumentException(sprintf(self::TWEAK_MALFORMED_ID_EX_MSG, $id));
        }

        $tweak = array_merge(['id' => $id, 'title' => $title, 'group' => $group], $tweak);
        if (isset($id)) {
            $this->tweaks[$id] = $tweak;
        } else {
            $this->tweaks[] = $tweak;
        }

        return $this;
    }

    /**
     * Returns tweaks
     * Add the url of the tweak.
     *
     * @return array
     */
    public function getTweaks()
    {
        $separator = strpos($this->getRouteUrl(), '?') ? '&' : '?';
        $url = $this->getRouteUrl() . $separator . $this->getHash() . '[' . self::REQUEST_QUERY_TWEAK . ']=';

        foreach ($this->tweaks as $id => $tweak) {
            $this->tweaks[$id] = array_merge($tweak, ['url' => $url . $id]);
        }

        return $this->tweaks;
    }

    public function getActiveTweaks()
    {
        return (array) $this->get('tweaks');
    }

    /**
     * Returns a tweak.
     *
     * @return array
     */
    public function getTweak($id)
    {
        $tweaks = $this->getTweaks();
        if (isset($tweaks[$id])) {
            return $tweaks[$id];
        }

        throw new \InvalidArgumentException(sprintf(self::NOT_VALID_TWEAK_ID_EX_MSG, $id));
    }

    /**
     * Returns tweaks with a specific group.
     *
     * @return array
     */
    public function getTweaksGroup($group)
    {
        $tweaksGroup = $this->getTweaks();

        foreach ($tweaksGroup as $id => $tweak) {
            if ($tweak['group'] != $group) {
                unset($tweaksGroup[$id]);
            }
        }

        return $tweaksGroup;
    }

    public function getActiveTweakGroup($group)
    {
        $tweaks = $this->getActiveTweaks();

        return isset($tweaks[$group]) ? $tweaks[$group] : -1;
    }

    /**
     * Adds Row Action.
     *
     * @param Action\RowActionInterface $action
     *
     * @return self
     */
    public function addRowAction(RowActionInterface $action)
    {
        if ($action->getRole() === null || $this->securityContext->isGranted($action->getRole())) {
            $this->rowActions[$action->getColumn()][] = $action;
        }

        return $this;
    }

    /**
     * Returns Row Actions.
     *
     * @return Action\RowAction[]
     */
    public function getRowActions()
    {
        return $this->rowActions;
    }

    /**
     * Sets template for export.
     *
     * @param \Twig_Template|string $template
     *
     * @throws \Exception
     *
     * @return self
     */
    public function setTemplate($template)
    {
        if ($template !== null) {
            if ($template instanceof \Twig_Template) {
                $template = '__SELF__' . $template->getTemplateName();
            } elseif (!is_string($template)) {
                throw new \Exception(self::TWIG_TEMPLATE_LOAD_EX_MSG);
            }

            $this->set(self::REQUEST_QUERY_TEMPLATE, $template);
            $this->saveSession();
        }

        return $this;
    }

    /**
     * Returns template.
     *
     * @return \Twig_Template|string
     */
    public function getTemplate()
    {
        return $this->get(self::REQUEST_QUERY_TEMPLATE);
    }

    /**
     * Adds Export.
     *
     * @param ExportInterface $export
     *
     * @return self
     */
    public function addExport(ExportInterface $export)
    {
        if ($export->getRole() === null || $this->securityContext->isGranted($export->getRole())) {
            $this->exports[] = $export;
        }

        return $this;
    }

    /**
     * Returns exports.
     *
     * @return ExportInterface[]
     */
    public function getExports()
    {
        return $this->exports;
    }

    /**
     * Returns the export response.
     *
     * @return Export[]
     */
    public function getExportResponse()
    {
        return $this->exportResponse;
    }

    /**
     * Returns the mass action response.
     *
     * @return Export[]
     */
    public function getMassActionResponse()
    {
        return $this->massActionResponse;
    }

    /**
     * Sets Route Parameters.
     *
     * @param string $parameter
     * @param mixed  $value
     *
     * @return self
     */
    public function setRouteParameter($parameter, $value)
    {
        $this->routeParameters[$parameter] = $value;

        return $this;
    }

    /**
     * Returns Route Parameters.
     *
     * @return array
     */
    public function getRouteParameters()
    {
        return $this->routeParameters;
    }

    /**
     * Sets Route URL.
     *
     * @param string $routeUrl
     *
     * @return self
     */
    public function setRouteUrl($routeUrl)
    {
        $this->routeUrl = $routeUrl;

        return $this;
    }

    /**
     * Returns Route URL.
     *
     * @return string
     */
    public function getRouteUrl()
    {
        if ($this->routeUrl === null) {
            $this->routeUrl = $this->router->generate($this->request->get('_route'), $this->getRouteParameters());
        }

        return $this->routeUrl;
    }

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

    public function isMassActionRedirect()
    {
        return $this->massActionResponse instanceof Response;
    }

    /**
     * Set value for filters.
     *
     * @param array $filters   Hash of columnName => initValue
     * @param bool  $permanent filters ?
     *
     * @return self
     */
    protected function setFilters(array $filters, $permanent = true)
    {
        foreach ($filters as $columnId => $value) {
            if ($permanent) {
                $this->permanentFilters[$columnId] = $value;
            } else {
                $this->defaultFilters[$columnId] = $value;
            }
        }

        return $this;
    }

    /**
     * Set permanent value for filters.
     *
     * @param array $filters Hash of columnName => initValue
     *
     * @return self
     */
    public function setPermanentFilters(array $filters)
    {
        return $this->setFilters($filters);
    }

    /**
     * Set default value for filters.
     *
     * @param array $filters Hash of columnName => initValue
     *
     * @return self
     */
    public function setDefaultFilters(array $filters)
    {
        return $this->setFilters($filters, false);
    }

    /**
     * Set the default grid order.
     *
     * @param $columnId
     *
     * @return self
     */
    public function setDefaultOrder($columnId, $order)
    {
        $order = strtolower($order);
        $this->defaultOrder = "$columnId|$order";

        return $this;
    }

    /**
     * Sets unique filter identification.
     *
     * @param $id
     *
     * @return self
     */
    public function setId($id)
    {
        $this->id = $id;

        return $this;
    }

    /**
     * Returns unique filter identifier.
     *
     * @return string
     */
    public function getId()
    {
        return $this->id;
    }

    /**
     * Sets persistence.
     *
     * @param $persistence
     *
     * @return self
     */
    public function setPersistence($persistence)
    {
        $this->persistence = $persistence;

        return $this;
    }

    /**
     * Returns persistence.
     *
     * @return bool
     */
    public function getPersistence()
    {
        return $this->persistence;
    }

    public function getDataJunction()
    {
        return $this->dataJunction;
    }

    public function setDataJunction($dataJunction)
    {
        $this->dataJunction = $dataJunction;

        return $this;
    }

    /**
     * Sets Limits.
     *
     * @param mixed $limits e.g. 10, array(10, 1000) or array(10 => '10', 1000 => '1000')
     *
     * @throws \InvalidArgumentException
     *
     * @return self
     */
    public function setLimits($limits)
    {
        if (is_array($limits)) {
            if ((int) key($limits) === 0) {
                $this->limits = array_combine($limits, $limits);
            } else {
                $this->limits = $limits;
            }
        } elseif (is_int($limits)) {
            $this->limits = [$limits => (string) $limits];
        } else {
            throw new \InvalidArgumentException(self::NOT_VALID_LIMIT_EX_MSG);
        }

        return $this;
    }

    /**
     * Returns limits.
     *
     * @return array
     */
    public function getLimits()
    {
        return $this->limits;
    }

    /**
     * Returns selected Limit (Rows Per Page).
     *
     * @return mixed
     */
    public function getLimit()
    {
        return $this->limit;
    }

    /**
     * Sets default Limit.
     *
     * @param $limit
     *
     * @return self
     */
    public function setDefaultLimit($limit)
    {
        $this->defaultLimit = (int) $limit;

        return $this;
    }

    /**
     * Sets default Page.
     *
     * @param $page
     *
     * @return self
     */
    public function setDefaultPage($page)
    {
        $this->defaultPage = (int) $page - 1;

        return $this;
    }

    /**
     * Sets default Tweak.
     *
     * @param $tweakId
     *
     * @return self
     */
    public function setDefaultTweak($tweakId)
    {
        $this->defaultTweak = $tweakId;

        return $this;
    }

    /**
     * Sets current Page (internal).
     *
     * @param $page
     *
     * @throws \InvalidArgumentException
     *
     * @return self
     */
    public function setPage($page)
    {
        if ((int) $page >= 0) {
            $this->page = (int) $page;
        } else {
            throw new \InvalidArgumentException(self::PAGE_NOT_VALID_EX_MSG);
        }

        return $this;
    }

    /**
     * Returns current page.
     *
     * @return int
     */
    public function getPage()
    {
        return $this->page;
    }

    /**
     * Returnd grid display data as rows - internal helper for templates.
     *
     * @return mixed
     */
    public function getRows()
    {
        return $this->rows;
    }

    /**
     * Return count of available pages.
     *
     * @return float
     */
    public function getPageCount()
    {
        $pageCount = 1;
        if ($this->getLimit() > 0) {
            $pageCount = ceil($this->getTotalCount() / $this->getLimit());
        }

        // @todo why this should be a float?
        return $pageCount;
    }

    /**
     * Returns count of filtred rows(items) from source.
     *
     * @return mixed
     */
    public function getTotalCount()
    {
        return $this->totalCount;
    }

    /**
     * Sets the max results of the grid.
     *
     * @param int $maxResults
     *
     * @throws \InvalidArgumentException
     *
     * @return self
     */
    public function setMaxResults($maxResults = null)
    {
        if ((is_int($maxResults) && $maxResults < 0) && $maxResults !== null) {
            throw new \InvalidArgumentException(self::NOT_VALID_MAX_RESULT_EX_MSG);
        }

        $this->maxResults = $maxResults;

        return $this;
    }

    /**
     * Return true if the grid is filtered.
     *
     * @return bool
     */
    public function isFiltered()
    {
        foreach ($this->columns as $column) {
            if ($column->isFiltered()) {
                return true;
            }
        }

        return false;
    }

    /**
     * Return true if if title panel is visible in template - internal helper.
     *
     * @return bool
     */
    public function isTitleSectionVisible()
    {
        if ($this->showTitles === true) {
            foreach ($this->columns as $column) {
                if ($column->getTitle() != '') {
                    return true;
                }
            }
        }

        return false;
    }

    /**
     * Return true if filter panel is visible in template - internal helper.
     *
     * @return bool
     */
    public function isFilterSectionVisible()
    {
        if ($this->showFilters === true) {
            foreach ($this->columns as $column) {
                if ($column->isFilterable() && $column->getType() != 'massaction' && $column->getType() != 'actions') {
                    return true;
                }
            }
        }

        return false;
    }

    /**
     * Return true if pager panel is visible in template - internal helper.
     *
     * @return bool return true if pager is visible
     */
    public function isPagerSectionVisible()
    {
        $limits = $this->getLimits();

        if (empty($limits)) {
            return false;
        }

        // true when totalCount rows exceed the minimum pager limit
        return min(array_keys($limits)) < $this->totalCount;
    }

    /**
     * Hides Filters Panel.
     *
     * @return self
     */
    public function hideFilters()
    {
        $this->showFilters = false;

        return $this;
    }

    /**
     * Hides Titles panel.
     *
     * @return self
     */
    public function hideTitles()
    {
        $this->showTitles = false;

        return $this;
    }

    /**
     * Adds Column Extension - internal helper.
     *
     * @param Column $extension
     *
     * @return self
     */
    public function addColumnExtension($extension)
    {
        $this->columns->addExtension($extension);

        return $this;
    }

    /**
     * Set a prefix title.
     *
     * @param $prefixTitle string
     *
     * @return self
     */
    public function setPrefixTitle($prefixTitle)
    {
        $this->prefixTitle = $prefixTitle;

        return $this;
    }

    /**
     * Get the prefix title.
     *
     * @return string
     */
    public function getPrefixTitle()
    {
        return $this->prefixTitle;
    }

    /**
     * Set the no data message.
     *
     * @param $noDataMessage string
     *
     * @return self
     */
    public function setNoDataMessage($noDataMessage)
    {
        $this->noDataMessage = $noDataMessage;

        return $this;
    }

    /**
     * Get the no data message.
     *
     * @return string
     */
    public function getNoDataMessage()
    {
        return $this->noDataMessage;
    }

    /**
     * Set the no result message.
     *
     * @param $noResultMessage string
     *
     * @return self
     */
    public function setNoResultMessage($noResultMessage)
    {
        $this->noResultMessage = $noResultMessage;

        return $this;
    }

    /**
     * Get the no result message.
     *
     * @return string
     */
    public function getNoResultMessage()
    {
        return $this->noResultMessage;
    }

    /**
     * Sets a list of columns to hide when the grid is output.
     *
     * @param array $columnIds
     *
     * @return self
     */
    public function setHiddenColumns($columnIds)
    {
        $this->lazyHiddenColumns = (array) $columnIds;

        return $this;
    }

    /**
     * Sets a list of columns to show when the grid is output
     * It acts as a mask; Other columns will be set as hidden.
     *
     * @param array $columnIds
     *
     * @return self
     */
    public function setVisibleColumns($columnIds)
    {
        $this->lazyVisibleColumns = (array) $columnIds;

        return $this;
    }

    /**
     * Sets on the visibility of columns.
     *
     * @param string|array $columnIds
     *
     * @return self
     */
    public function showColumns($columnIds)
    {
        foreach ((array) $columnIds as $columnId) {
            $this->lazyHideShowColumns[$columnId] = true;
        }

        return $this;
    }

    /**
     * Sets off the visiblilty of columns.
     *
     * @param string|array $columnIds
     *
     * @return self
     */
    public function hideColumns($columnIds)
    {
        foreach ((array) $columnIds as $columnId) {
            $this->lazyHideShowColumns[$columnId] = false;
        }

        return $this;
    }

    /**
     * Sets the size of the default action column.
     *
     * @param int $size
     *
     * @return self
     */
    public function setActionsColumnSize($size)
    {
        $this->actionsColumnSize = $size;

        return $this;
    }

    /**
     * Sets the title of the default action column.
     *
     * @param string $title
     *
     * @return self
     */
    public function setActionsColumnTitle($title)
    {
        $this->actionsColumnTitle = (string) $title;

        return $this;
    }

    /**
     * Default delete action.
     *
     * @param array $ids
     */
    public function deleteAction(array $ids)
    {
        $this->source->delete($ids);
    }

    /**
     * Get a clone of the grid.
     */
    public function __clone()
    {
        // clone all objects
        $this->columns = clone $this->columns;
    }

    /****** HELPER ******/

    /**
     * Redirects or Renders a view - helper function.
     *
     * @param string|array $param1   The view name or an array of parameters to pass to the view
     * @param string|array $param2   The view name or an array of parameters to pass to the view
     * @param Response     $response A response instance
     *
     * @return Response A Response instance
     */
    public function getGridResponse($param1 = null, $param2 = null, Response $response = null)
    {
        $isReadyForRedirect = $this->isReadyForRedirect();

        if ($this->isReadyForExport()) {
            return $this->getExportResponse();
        }

        if ($this->isMassActionRedirect()) {
            return $this->getMassActionResponse();
        }

        if ($isReadyForRedirect) {
            return new RedirectResponse($this->getRouteUrl());
        } else {
            if (is_array($param1) || $param1 === null) {
                $parameters = (array) $param1;
                $view = $param2;
            } else {
                $parameters = (array) $param2;
                $view = $param1;
            }

            $parameters = array_merge(['grid' => $this], $parameters);

            if ($view === null) {
                return $parameters;
            } else {
                return $this->container->get('templating')->renderResponse($view, $parameters, $response);
            }
        }
    }

    /**
     * Extract raw data of columns.
     *
     * @param string|array $columnNames  The name of the extract columns. If null, all the columns are return.
     * @param bool         $namedIndexes If sets to true, named indexes will be used
     *
     * @return array Raw data of columns
     */
    public function getRawData($columnNames = null, $namedIndexes = true)
    {
        if ($columnNames === null) {
            foreach ($this->getColumns() as $column) {
                $columnNames[] = $column->getId();
            }
        }

        $columnNames = (array) $columnNames;
        $result = [];
        foreach ($this->rows as $row) {
            $resultRow = [];
            foreach ($columnNames as $columnName) {
                if ($namedIndexes) {
                    $resultRow[$columnName] = $row->getField($columnName);
                } else {
                    $resultRow[] = $row->getField($columnName);
                }
            }

            $result[] = $resultRow;
        }

        return $result;
    }

    /**
     * Returns an array of the active filters of the grid stored in session.
     *
     * @throws \Exception
     *
     * @return Filter[]
     */
    public function getFilters()
    {
        if ($this->hash === null) {
            throw new \Exception(self::GET_FILTERS_NO_REQUEST_HANDLED_EX_MSG);
        }

        if ($this->sessionFilters === null) {
            $this->sessionFilters = [];
            $session = $this->sessionData;

            $requestQueries = [
                self::REQUEST_QUERY_MASS_ACTION_ALL_KEYS_SELECTED,
                self::REQUEST_QUERY_MASS_ACTION,
                self::REQUEST_QUERY_EXPORT,
                self::REQUEST_QUERY_PAGE,
                self::REQUEST_QUERY_LIMIT,
                self::REQUEST_QUERY_ORDER,
                self::REQUEST_QUERY_TEMPLATE,
                self::REQUEST_QUERY_RESET,
                MassActionColumn::ID,
            ];

            foreach ($requestQueries as $request_query) {
                unset($session[$request_query]);
            }

            foreach ($session as $columnId => $sessionFilter) {
                if (isset($sessionFilter['operator'])) {
                    $operator = $sessionFilter['operator'];
                    unset($sessionFilter['operator']);
                } else {
                    $operator = $this->getColumn($columnId)->getDefaultOperator();
                }

                if (!isset($sessionFilter['to']) && isset($sessionFilter['from'])) {
                    $sessionFilter = $sessionFilter['from'];
                }

                $this->sessionFilters[$columnId] = new Filter($operator, $sessionFilter);
            }
        }

        return $this->sessionFilters;
    }

    /**
     * Returns the filter of a column stored in session.
     *
     * @param string $columnId
     *                         Id of the column
     *
     * @throws \Exception
     *
     * @return Filter
     */
    public function getFilter($columnId)
    {
        if ($this->hash === null) {
            throw new \Exception(self::GET_FILTERS_NO_REQUEST_HANDLED_EX_MSG);
        }

        $sessionFilters = $this->getFilters();

        return isset($sessionFilters[$columnId]) ? $sessionFilters[$columnId] : null;
    }

    /**
     * A filter of the column is stored in session ?
     *
     * @param string $columnId
     *                         Id of the column
     *
     * @throws \Exception
     *
     * @return bool
     */
    public function hasFilter($columnId)
    {
        if ($this->hash === null) {
            throw new \Exception(self::HAS_FILTER_NO_REQUEST_HANDLED_EX_MSG);
        }

        return $this->getFilter($columnId) !== null;
    }
}


<?php

/*
 * This file is part of the DataGridBundle.
 *
 * (c) Abhoryo <abhoryo@free.fr>
 * (c) Stanislav Turza
 * (c) Patryk Grudniewski <patgrudniewski@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace APY\DataGridBundle\Grid\Source;

use APY\DataGridBundle\Grid\Column\Column;
use APY\DataGridBundle\Grid\Column\JoinColumn;
use APY\DataGridBundle\Grid\Row;
use APY\DataGridBundle\Grid\Rows;
use Doctrine\ORM\NoResultException;
use Doctrine\ORM\Query;
use Doctrine\ORM\QueryBuilder;
use Doctrine\ORM\Query\Expr\Join;
use Doctrine\ORM\Query\ResultSetMapping;
use Doctrine\ORM\Tools\Pagination\CountWalker;
use Doctrine\ORM\Tools\Pagination\Paginator;
use Symfony\Component\HttpKernel\Kernel;

class Entity extends Source
{
    const DOT_DQL_ALIAS_PH = '__dot__';
    const COLON_DQL_ALIAS_PH = '__col__';

    /**
     * @var \Doctrine\ORM\EntityManager
     */
    protected $manager;

    /**
     * @var \Doctrine\ORM\QueryBuilder
     */
    protected $query;

    /**
     * @var \Doctrine\ORM\QueryBuilder
     */
    protected $querySelectfromSource;

    /**
     * @var string e.g Vendor\Bundle\Entity\Page
     */
    protected $class;

    /**
     * @var string e.g Cms:Page
     */
    protected $entityName;

    /**
     * @var string e.g mydatabase
     */
    protected $managerName;

    /**
     * @var \APY\DataGridBundle\Grid\Mapping\Metadata\Metadata
     */
    protected $metadata;

    /**
     * @var \Doctrine\ORM\Mapping\ClassMetadata
     */
    protected $ormMetadata;

    /**
     * @var array
     */
    protected $joins;

    /**
     * @var string
     */
    protected $group;

    /**
     * @var string
     */
    protected $groupBy;

    /**
     * @var array
     */
    protected $hints;

    /**
     * The QueryBuilder that will be used to start generating query for the DataGrid
     * You can override this if the querybuilder is constructed in a business-specific way
     * by an external controller/service/repository and you wish to re-use it for the datagrid.
     * Typical use-case involves an external repository creating complex default restriction (i.e. multi-tenancy etc)
     * which then will be expanded on by the datagrid.
     *
     * @var QueryBuilder
     */
    protected $queryBuilder;

    /**
     * The table alias that will be used in the query to fetch actual data.
     *
     * @var string
     */
    protected $tableAlias;

    /**
     * @var null
     */
    protected $prepareCountQueryCallback = null;

    /**
     * Legacy way of accessing the default alias (before it became possible to change it)
     * Please use $entity->getTableAlias() now instead of $entity::TABLE_ALIAS.
     *
     * @deprecated
     */
    const TABLE_ALIAS = '_a';

    /**
     * @param string $entityName  e.g Cms:Page
     * @param string $managerName e.g. mydatabase
     */
    public function __construct($entityName, $group = 'default', $managerName = null)
    {
        $this->entityName = $entityName;
        $this->managerName = $managerName;
        $this->joins = [];
        $this->group = $group;
        $this->hints = [];
        $this->setTableAlias(self::TABLE_ALIAS);
    }

    public function initialise($container)
    {
        $doctrine = $container->get('doctrine');

        $this->manager = version_compare(Kernel::VERSION, '2.1.0', '>=') ? $doctrine->getManager($this->managerName) : $doctrine->getEntityManager($this->managerName);
        $this->ormMetadata = $this->manager->getClassMetadata($this->entityName);

        $this->class = $this->ormMetadata->getReflectionClass()->name;

        $mapping = $container->get('grid.mapping.manager');

        /* todo autoregister mapping drivers with tag */
        $mapping->addDriver($this, -1);
        $this->metadata = $mapping->getMetadata($this->class, $this->group);

        $this->groupBy = $this->metadata->getGroupBy();
    }

    /**
     * @param \APY\DataGridBundle\Grid\Column\Column $column
     *
     * @return string
     */
    protected function getTranslationFieldNameWithParents($column)
    {
        $name = $column->getField();

        if ($column->getIsManualField()) {
            return $column->getField();
        }

        if (strpos($name, '.') !== false) {
            $previousParent = '';

            $elements = explode('.', $name);
            while ($element = array_shift($elements)) {
                if (count($elements) > 0) {
                    $previousParent .= '_' . $element;
                }
            }
        } elseif (strpos($name, ':') !== false) {
            $previousParent = $this->getTableAlias();
        } else {
            return $this->getTableAlias().'.'.$name;
        }

        $matches = array();
        if ($column->hasDQLFunction($matches)) {
            return $previousParent.'.'.$matches['field'];
        }

        return $column->getField();
    }

    /**
     * @param \APY\DataGridBundle\Grid\Column\Column $column
     * @return string
     */
    protected function getFieldName($column, $withAlias = false)
    {
        $name = $column->getField();

        if ($column->getIsManualField()) {
            return $column->getField();
        }

        if (strpos($name, '.') !== false) {
            $previousParent = '';

            $elements = explode('.', $name);
            while ($element = array_shift($elements)) {
                if (count($elements) > 0) {
                    $parent = ($previousParent == '') ? $this->getTableAlias() : $previousParent;
                    $previousParent .= '_' . $element;
                    $this->joins[$previousParent] = ['field' => $parent . '.' . $element, 'type' => $column->getJoinType()];
                } else {
                    $name = $previousParent . '.' . $element;
                }
            }

            $alias = $this->fromColIdToAlias($column->getId());
        } elseif (strpos($name, ':') !== false) {
            $previousParent = $this->getTableAlias();
            $alias = $name;
        } else {
            return $this->getTableAlias() . '.' . $name;
        }

        // Aggregate dql functions
        $matches = [];
        if ($column->hasDQLFunction($matches)) {
            if (strtolower($matches['parameters']) == 'distinct') {
                $functionWithParameters = $matches['function'] . '(DISTINCT ' . $previousParent . '.' . $matches['field'] . ')';
            } else {
                $parameters = '';
                if ($matches['parameters'] !== '') {
                    $parameters = ', ' . (is_numeric($matches['parameters']) ? $matches['parameters'] : "'" . $matches['parameters'] . "'");
                }

                $functionWithParameters = $matches['function'] . '(' . $previousParent . '.' . $matches['field'] . $parameters . ')';
            }

            if ($withAlias) {
                // Group by the primary field of the previous entity
                $this->query->addGroupBy($previousParent);
                $this->querySelectfromSource->addGroupBy($previousParent);

                return "$functionWithParameters as $alias";
            }

            return $alias;
        }

        if ($withAlias) {
            return "$name as $alias";
        }

        return $name;
    }

    /**
     * @param string $colId
     *
     * @return string
     */
    private function fromColIdToAlias($colId)
    {
        return str_replace(['.', ':'], [self::DOT_DQL_ALIAS_PH, self::COLON_DQL_ALIAS_PH], $colId);
    }

    /**
     * @param string $fieldName
     *
     * @return string
     */
    protected function getGroupByFieldName($fieldName)
    {
        if (strpos($fieldName, '.') !== false) {
            $previousParent = '';

            $elements = explode('.', $fieldName);
            while ($element = array_shift($elements)) {
                if (count($elements) > 0) {
                    $previousParent .= '_' . $element;
                } else {
                    $name = $previousParent . '.' . $element;
                }
            }
        } else {
            if (($pos = strpos($fieldName, ':')) !== false) {
                $fieldName = substr($fieldName, 0, $pos);
            }

            return $this->getTableAlias() . '.' . $fieldName;
        }

        return $name;
    }

    /**
     * @param \APY\DataGridBundle\Grid\Columns $columns
     */
    public function getColumns($columns)
    {
        foreach ($this->metadata->getColumnsFromMapping($columns) as $column) {
            $columns->addColumn($column);
        }
    }

    protected function normalizeOperator($operator)
    {
        switch ($operator) {
            //case Column::OPERATOR_REGEXP:
            case Column::OPERATOR_LIKE:
            case Column::OPERATOR_LLIKE:
            case Column::OPERATOR_RLIKE:
            case Column::OPERATOR_NLIKE:
            case Column::OPERATOR_SLIKE:
            case Column::OPERATOR_LSLIKE:
            case Column::OPERATOR_RSLIKE:
            case Column::OPERATOR_NSLIKE:
                return 'like';
            default:
                return $operator;
        }
    }

    protected function normalizeValue($operator, $value)
    {
        switch ($operator) {
            //case Column::OPERATOR_REGEXP:
            case Column::OPERATOR_LIKE:
            case Column::OPERATOR_NLIKE:
            case Column::OPERATOR_SLIKE:
            case Column::OPERATOR_NSLIKE:
                return "%$value%";
            case Column::OPERATOR_LLIKE:
            case Column::OPERATOR_LSLIKE:
                return "%$value";
            case Column::OPERATOR_RLIKE:
            case Column::OPERATOR_RSLIKE:
                return "$value%";
            default:
                return $value;
        }
    }

    /**
     * Sets the initial QueryBuilder for this DataGrid.
     *
     * @param QueryBuilder $queryBuilder
     */
    public function initQueryBuilder(QueryBuilder $queryBuilder)
    {
        $this->queryBuilder = clone $queryBuilder;

        //Try to guess the new root alias and apply it to our queries+
        //as the external querybuilder almost certainly is not used our default alias
        $externalTableAliases = $this->queryBuilder->getRootAliases();
        if (count($externalTableAliases)) {
            $this->setTableAlias($externalTableAliases[0]);
        }
    }

    /**
     * @return QueryBuilder
     */
    protected function getQueryBuilder()
    {
        //If a custom QB has been provided, use a copy of that one
        //Otherwise create our own basic one
        if ($this->queryBuilder instanceof QueryBuilder) {
            $qb = clone $this->queryBuilder;
        } else {
            $qb = $this->manager->createQueryBuilder($this->class);
            $qb->from($this->class, $this->getTableAlias());
        }

        return $qb;
    }

    /**
     * @param \APY\DataGridBundle\Grid\Column\Column[] $columns
     * @param int                                      $page             Page Number
     * @param int                                      $limit            Rows Per Page
     * @param int                                      $gridDataJunction Grid data junction
     *
     * @return \APY\DataGridBundle\Grid\Rows
     */
    public function execute($columns, $page = 0, $limit = 0, $maxResults = null, $gridDataJunction = Column::DATA_CONJUNCTION)
    {
        $this->query = $this->getQueryBuilder();
        $this->querySelectfromSource = clone $this->query;

        $bindIndex = 123;
        $serializeColumns = [];
        $where = $gridDataJunction === Column::DATA_CONJUNCTION ? $this->query->expr()->andx() : $this->query->expr()->orx();

        $columnsById = [];
        foreach ($columns as $column) {
            $columnsById[$column->getId()] = $column;
        }

        foreach ($columns as $column) {
            // If a column is a manual field, ie a.col*b.col as myfield, it is added to select from user.
            if ($column->getIsManualField() === false) {
                $fieldName = $this->getFieldName($column, true);
                $this->query->addSelect($fieldName);
                $this->querySelectfromSource->addSelect($fieldName);
            }

            if ($column->isSorted()) {
                if ($column instanceof JoinColumn) {
                    $this->query->resetDQLPart('orderBy');
                    foreach ($column->getJoinColumns() as $columnName) {
                        $this->query->addOrderBy($this->getFieldName($columnsById[$columnName]), $column->getOrder());
                    }
                } else {
                    $this->query->orderBy($this->getFieldName($column), $column->getOrder());
                }
            }

            if ($column->isFiltered()) {
                // Some attributes of the column can be changed in this function
                $filters = $column->getFilters('entity');

                $isDisjunction = $column->getDataJunction() === Column::DATA_DISJUNCTION;

                $dqlMatches = [];
                $hasHavingClause = $column->hasDQLFunction($dqlMatches) || $column->getIsAggregate();
                if(isset($dqlMatches['function']) && $dqlMatches['function'] == 'translation_agg'){
                    $hasHavingClause = false;
                }

                $sub = $isDisjunction ? $this->query->expr()->orx() : ($hasHavingClause ? $this->query->expr()->andx() : $where);

                foreach ($filters as $filter) {
                    $operator = $this->normalizeOperator($filter->getOperator());

                    $columnForFilter = (!$column instanceof JoinColumn) ? $column : $columnsById[$filter->getColumnName()];

                    $fieldName = $this->getFieldName($columnForFilter, false);
                    $bindIndexPlaceholder = "?$bindIndex";

                    if( in_array($filter->getOperator(), array(Column::OPERATOR_LIKE,Column::OPERATOR_RLIKE,Column::OPERATOR_LLIKE,Column::OPERATOR_NLIKE,))){
                        if(isset($dqlMatches['function']) && $dqlMatches['function'] == 'translation_agg'){
                            $translationFieldName = $this->getTranslationFieldNameWithParents($columnForFilter);
                            $fieldName = "LOWER(".$translationFieldName.")";
                        }elseif(isset($dqlMatches['function']) && $dqlMatches['function'] == 'role_agg'){
                            $translationFieldName = $this->getTranslationFieldNameWithParents($columnForFilter);
                            $fieldName = "LOWER(".$translationFieldName.")";
                        }else{
                            $fieldName = "LOWER($fieldName)";
                        }
                        $bindIndexPlaceholder = "LOWER($bindIndexPlaceholder)";
                    }

                    $q = $this->query->expr()->$operator($fieldName, $bindIndexPlaceholder);

                    if ($filter->getOperator() == Column::OPERATOR_NLIKE || $filter->getOperator() == Column::OPERATOR_NSLIKE) {
                        $q = $this->query->expr()->not($q);
                    }

                    $sub->add($q);

                    if ($filter->getValue() !== null) {
                        $this->query->setParameter($bindIndex++, $this->normalizeValue($filter->getOperator(), $filter->getValue()));
                    }
                }

                if ($hasHavingClause) {
                    $this->query->andHaving($sub);
                } elseif ($isDisjunction) {
                    $where->add($sub);
                }
            }

            if ($column->getType() === 'array') {
                $serializeColumns[] = $column->getId();
            }
        }

        if ($where->count() > 0) {
            //Using ->andWhere here to make sure we preserve any other where clauses present in the query builder
            //the other where clauses may have come from an external builder
            $this->query->andWhere($where);
        }

        foreach ($this->joins as $alias => $field) {
            if (null !== $field['type'] && strtolower($field['type']) === 'inner') {
                $join = 'join';
            } else {
                $join = 'leftJoin';
            }

            $this->query->$join($field['field'], $alias);
            $this->querySelectfromSource->$join($field['field'], $alias);
        }

        if ($page > 0) {
            $this->query->setFirstResult($page * $limit);
        }

        if ($limit > 0) {
            if ($maxResults !== null && ($maxResults - $page * $limit < $limit)) {
                $limit = $maxResults - $page * $limit;
            }

            $this->query->setMaxResults($limit);
        } elseif ($maxResults !== null) {
            $this->query->setMaxResults($maxResults);
        }

        if (!empty($this->groupBy)) {
            $this->query->resetDQLPart('groupBy');
            $this->querySelectfromSource->resetDQLPart('groupBy');

            foreach ($this->groupBy as $field) {
                $this->query->addGroupBy($this->getGroupByFieldName($field));
                $this->querySelectfromSource->addGroupBy($this->getGroupByFieldName($field));
            }
        }

        //call overridden prepareQuery or associated closure
        $this->prepareQuery($this->query);
        $hasJoin = $this->checkIfQueryHasFetchJoin($this->query);

        $query = $this->query->getQuery();
        foreach ($this->hints as $hintKey => $hintValue) {
            $query->setHint($hintKey, $hintValue);
        }
        $items = new Paginator($query, $hasJoin);
        $items->setUseOutputWalkers(false);

        $repository = $this->manager->getRepository($this->entityName);

        // Force the primary field to get the entity in the manipulatorRow
        $primaryColumnId = null;
        foreach ($columns as $column) {
            if ($column->isPrimary()) {
                $primaryColumnId = $column->getId();

                break;
            }
        }

        // hydrate result
        $result = new Rows();

        foreach ($items as $item) {
            $row = new Row();

            foreach ($item as $key => $value) {
                $key = $this->fromAliasToColId($key);

                if (in_array($key, $serializeColumns) && is_string($value)) {
                    $value = unserialize($value);
                }

                $row->setField($key, $value);
            }

            $row->setPrimaryField($primaryColumnId);

            //Setting the representative repository for entity retrieving
            $row->setRepository($repository);

            //call overridden prepareRow or associated closure
            if (($modifiedRow = $this->prepareRow($row)) !== null) {
                $result->addRow($modifiedRow);
            }
        }

        return $result;
    }

    /**
     * @param string $alias
     *
     * @return string
     */
    private function fromAliasToColId($alias)
    {
        return str_replace([self::DOT_DQL_ALIAS_PH, self::COLON_DQL_ALIAS_PH], ['.', ':'], $alias);
    }

    public function getTotalCount($maxResults = null)
    {
        // Doctrine Bug Workaround: http://www.doctrine-project.org/jira/browse/DDC-1927
        $countQueryBuilder = clone $this->query;

        $this->prepareCountQuery($countQueryBuilder);

        foreach ($countQueryBuilder->getRootAliases() as $alias) {
            $countQueryBuilder->addSelect($alias);
        }

        // From Doctrine\ORM\Tools\Pagination\Paginator::count()
        $countQuery = $countQueryBuilder->getQuery();

        // Add hints from main query, if developer wants to use additional hints (ex. gedmo translations):
        foreach ($this->hints as $hintName => $hintValue) {
            $countQuery->setHint($hintName, $hintValue);
        }

        if (!$countQuery->getHint(CountWalker::HINT_DISTINCT)) {
            $countQuery->setHint(CountWalker::HINT_DISTINCT, true);
        }

        if ($countQuery->getHint(Query::HINT_CUSTOM_OUTPUT_WALKER) === false) {
            $platform = $countQuery->getEntityManager()->getConnection()->getDatabasePlatform(); // law of demeter win

            $rsm = new ResultSetMapping();
            $rsm->addScalarResult($platform->getSQLResultCasing('dctrn_count'), 'count');

            $countQuery->setHint(Query::HINT_CUSTOM_OUTPUT_WALKER, 'Doctrine\ORM\Tools\Pagination\CountOutputWalker');
            $countQuery->setResultSetMapping($rsm);
        } else {
            $hints = $countQuery->getHint(Query::HINT_CUSTOM_TREE_WALKERS);

            if ($hints === false) {
                $hints = [];
            }

            $hints[] = 'Doctrine\ORM\Tools\Pagination\CountWalker';
            //$hints[] = 'APY\DataGridBundle\Grid\Helper\ORMCountWalker';
            $countQuery->setHint(Query::HINT_CUSTOM_TREE_WALKERS, $hints);
        }
        $countQuery->setFirstResult(null)->setMaxResults($maxResults);

        try {
            $data = $countQuery->getScalarResult();
            $data = array_map('current', $data);
            $count = array_sum($data);
        } catch (NoResultException $e) {
            $count = 0;
        }

        return $count;
    }

    public function getFieldsMetadata($class, $group = 'default')
    {
        $result = [];
        foreach ($this->ormMetadata->getFieldNames() as $name) {
            $mapping = $this->ormMetadata->getFieldMapping($name);
            $values = ['title' => $name, 'source' => true];

            if (isset($mapping['fieldName'])) {
                $values['field'] = $mapping['fieldName'];
                $values['id'] = $mapping['fieldName'];
            }

            if (isset($mapping['id']) && $mapping['id'] == 'id') {
                $values['primary'] = true;
            }

            switch ($mapping['type']) {
                case 'string':
                case 'text':
                    $values['type'] = 'text';
                    break;
                case 'integer':
                case 'smallint':
                case 'bigint':
                case 'float':
                case 'decimal':
                    $values['type'] = 'number';
                    break;
                case 'boolean':
                    $values['type'] = 'boolean';
                    break;
                case 'date':
                    $values['type'] = 'date';
                    break;
                case 'datetime':
                    $values['type'] = 'datetime';
                    break;
                case 'time':
                    $values['type'] = 'time';
                    break;
                case 'array':
                case 'object':
                    $values['type'] = 'array';
                    break;
            }

            $result[$name] = $values;
        }

        return $result;
    }

    public function populateSelectFilters($columns, $loop = false)
    {
        /* @var $column Column */
        foreach ($columns as $column) {
            $selectFrom = $column->getSelectFrom();

            if ($column->getFilterType() === 'select' && ($selectFrom === 'source' || $selectFrom === 'query')) {

                // For negative operators, show all values
                if ($selectFrom === 'query') {
                    foreach ($column->getFilters('entity') as $filter) {
                        if (in_array($filter->getOperator(), [Column::OPERATOR_NEQ, Column::OPERATOR_NLIKE, Column::OPERATOR_NSLIKE])) {
                            $selectFrom = 'source';
                            break;
                        }
                    }
                }

                // Dynamic from query or not ?
                $query = ($selectFrom === 'source') ? clone $this->querySelectfromSource : clone $this->query;

                $query = $query->select($this->getFieldName($column, true))
                    ->distinct()
                    ->orderBy($this->getFieldName($column), 'asc')
                    ->setFirstResult(null)
                    ->setMaxResults(null)
                    ->getQuery();
                if ($selectFrom === 'query') {
                    foreach ($this->hints as $hintKey => $hintValue) {
                        $query->setHint($hintKey, $hintValue);
                    }
                }
                $result = $query->getResult();

                $values = [];
                foreach ($result as $row) {
                    $alias = $this->fromColIdToAlias($column->getId());

                    $value = $row[$alias];

                    switch ($column->getType()) {
                        case 'array':
                            if (is_string($value)) {
                                $value = unserialize($value);
                            }
                            foreach ($value as $val) {
                                $values[$val] = $val;
                            }
                            break;
                        case 'simple_array':
                            if (is_string($value)) {
                                $value = explode(',', $value);
                            }
                            foreach ($value as $val) {
                                $values[$val] = $val;
                            }
                            break;
                        case 'number':
                            $values[$value] = $column->getDisplayedValue($value);
                            break;
                        case 'datetime':
                        case 'date':
                        case 'time':
                            $displayedValue = $column->getDisplayedValue($value);
                            $values[$displayedValue] = $displayedValue;
                            break;
                        default:
                            $values[$value] = $value;
                    }
                }

                // It avoids to have no result when the other columns are filtered
                if ($selectFrom === 'query' && empty($values) && $loop === false) {
                    $column->setSelectFrom('source');
                    $this->populateSelectFilters($columns, true);
                } else {
                    if ($column->getType() == 'array') {
                        natcasesort($values);
                    }

                    $values = $this->prepareColumnValues($column, $values);
                    $column->setValues($values);
                }
            }
        }
    }

    /**
     * @param QueryBuilder $countQueryBuilder
     */
    public function prepareCountQuery(QueryBuilder $countQueryBuilder)
    {
        if (is_callable($this->prepareCountQueryCallback)) {
            call_user_func($this->prepareCountQueryCallback, $countQueryBuilder);
        }
    }

    /**
     * @param callable $callback
     *
     * @return $this
     */
    public function manipulateCountQuery($callback = null)
    {
        $this->prepareCountQueryCallback = $callback;

        return $this;
    }

    public function delete(array $ids)
    {
        $repository = $this->getRepository();

        foreach ($ids as $id) {
            $object = $repository->find($id);

            if (!$object) {
                throw new \Exception(sprintf('No %s found for id %s', $this->entityName, $id));
            }

            $this->manager->remove($object);
        }

        $this->manager->flush();
    }

    public function getRepository()
    {
        return $this->manager->getRepository($this->entityName);
    }

    public function getHash()
    {
        return $this->entityName;
    }

    public function addHint($key, $value)
    {
        $this->hints[$key] = $value;
    }

    public function clearHints()
    {
        $this->hints = [];
    }

    /**
     *  Set groupby column.
     *
     *  @param string $groupBy GroupBy column
     */
    public function setGroupBy($groupBy)
    {
        $this->groupBy = $groupBy;
    }

    public function getEntityName()
    {
        return $this->entityName;
    }

    /**
     * @param string $tableAlias
     */
    public function setTableAlias($tableAlias)
    {
        $this->tableAlias = $tableAlias;
    }

    /**
     * @return string
     */
    public function getTableAlias()
    {
        return $this->tableAlias;
    }

    /**
     * @param QueryBuilder $qb
     * @return boolean
     */
    protected function checkIfQueryHasFetchJoin(QueryBuilder $qb)
    {
        $join = $qb->getDqlPart('join');
        if (empty($join)) {
            return false;
        }

        foreach ($join[$this->getTableAlias()] as $join) {
            if ($join->getJoinType() === Join::INNER_JOIN || $join->getJoinType() === Join::LEFT_JOIN) {
                return true;
            }
        }

        return false;
    }
}