<?php
class Core_Doctrine_Table extends Doctrine_Table
{
    protected $_defaults = array();
    protected $_defaultsSingle = array(
        'limit'  => null,
        'offset' => null
    );
    protected $_paramsSingle = array();

    /**
     * @param array $params
     * @param int $hydrationMode
     *
     * @return array
     */
    public function get($params = null, $hydrationMode = null, $directHydration = false)
    {
        /* @var $query Doctrine_Query */
        $query  = $this->generateQuery($params);

        $offset = isset($params['offset']) ? $params['offset'] : null;
        $limit  = isset($params['limit']) ? $params['limit'] : null;
        
        $query->useQueryCache();
        $query->useResultCache();
        
        if (true === $directHydration) {
            $items = $query->execute(array(), $hydrationMode);
        } else {
            $items = $query->execute();
        }

        $count  = count($items);
        $total  = $query->count();

        if (false === $directHydration && $hydrationMode === Doctrine_Core::HYDRATE_ARRAY) {
            $items = $items->toArray();
        }

        return compact('count', 'total', 'offset', 'limit', 'items');
    }

    /**
     * Get items with array hydration, shorthand of Table::get($params, Doctrine_Core::HYDRATE_ARRAY)
     *
     * @param array $params
     *
     * @return array
     */
    public function getArray($params = null, $directHydration = false)
    {
        return $this->get($params, Doctrine_Core::HYDRATE_ARRAY, $directHydration);
    }

    /**
     * Get one - return first item from the items key of the get method result
     *
     * @param array $params
     * @param int $hidrationMode
     *
     * @return Doctrine_Record|array
     */
    public function getOne($params, $hidrationMode = null)
    {
        if (null === $params) {
            $params = array();
        } elseif (is_numeric($params)) {
            $params = array('id' => $params);
        }

        $params['limit'] = 1;

        $results = $this->get($params, $hidrationMode);

        if (empty($results['count'])) {
            return false;
        }

        return $results['items'][0];
    }

    /**
     * Get one with array hydration, shorthand of Table::getOne($params, Doctrine_Core::HYDRATE_ARRAY)
     *
     * @param array $params
     *
     * @return array
     */
    public function getOneArray($params)
    {
        return $this->getOne($params, Doctrine_Core::HYDRATE_ARRAY);
    }

    /**
     * @param array $params
     *
     * @return Doctrine_Query
     */
    public function generateQuery(array $params = null)
    {
        // Create query
        $query = $this->createQuery('tb');
        
        $no_order = false;
        if (array_key_exists('no_order', $params)) {
            $no_order = (bool) $params;
            unset($params['no_order']);
        }

        $processedParams = $this->_process($params);
       
        // Turbo hack to get order by field
        if (!$no_order && isset($processedParams['query']['id']) && (!isset($processedParams['order'])) && count($this->getIdentifierColumnNames()) == 1) {
            $ids                      = current($processedParams['query']['id']);
            $identifier               = current($this->getIdentifierColumnNames());
            $processedParams['limit'] = null;
            if (count($ids) > 1) {
                $processedParams['order'] = array('' => 'FIELD('. $identifier .', '. implode(',', $ids) .')');
            }
        }
        
        $ql     = new Core_QueryLanguage($processedParams);
        $ql->setAdapter('doctrine', new Core_QueryLanguage_Adapter_Doctrine);

        $query  = $ql->getAdapter('doctrine')
            ->setInitialQuery($query)
            ->getQuery();

        if (isset($_GET['debug_generate_query'])) {
            print_r($processedParams);
            print_r($ql->getQuery()->getSelect());
            print_r($ql->getQuery()->getQuery());
            echo $query->getDql() . PHP_EOL;
            echo $query->getSqlQuery() . PHP_EOL;
            // die;
        }

        return $query;
    }

    public function createQuery($alias = '')
    {
        $query = parent::createQuery($alias);
        return $this->_configureQuery($query, $alias);
    }

    public function getSecurityResource()
    {
        return 'model:'. strtolower($this->getComponentName());
    }

    protected function _configureQuery(Doctrine_Query $query, $alias = 'tb')
    {
        return $query;
    }

    protected function _configureQueryIds(Doctrine_Query $query, $ids)
    {
        $query->andWhereIn('tb.id', $ids);
    }

    protected function _process($params)
    {
        if (!isset($params['select'])) {
            $params['select'] = array();
        }

        if (is_string($params['select'])) {
            $params['select'] = explode(' ', trim($params['select']));
        }

        if (isset($this->_defaults['select'])) {
            $params['select'] = array_merge((array) $this->_defaults['select'], $params['select']);
            $params['select'] = array_unique($params['select'], SORT_REGULAR);
        }

        if (!isset($params['query'])) {
            $params['query'] = array();
        }

        if (is_string($params['query'])) {
            $oldQuery = $params['query'];
            preg_match_all('/(?P<field>[a-z0-9\_\.]+)(?P<operator>[!=:><]+)"?(?P<value>.*)"?/', $params['query'], $matches, PREG_SET_ORDER);
            $params['query'] = array();
            foreach ($matches as $query) {
                $value = null;
                if (strpos($query['value'], '[') === 0) {
                    $value = json_decode($query['value']);
                }

                if (null === $value) {
                    $value = $query['value'];
                }
                $params['query'][$query['field']] = array($query['operator'] => $value);
            }

            if (!isset($params['query']['title']) && empty($matches)) {
                $params['query']['title'] = array(Core_QueryLanguage_Query::OPERATOR_CONTAINS => $oldQuery);
            }
        }

        if (isset($this->_defaults['query'])) {
            $params['query'] = array_merge((array) $this->_defaults['query'], $params['query']);
            $params['query'] = array_unique($params['query'], SORT_REGULAR);
        }

        // Everything else besides select, query, order, limit and offset...
        $query = array_diff_key($params, array_flip(array('select', 'query', 'order', 'limit', 'offset')));

        // ... add to query
        foreach ($query as $field => $value) {
            $params['query'][$field] = array(Core_QueryLanguage_Query::OPERATOR_EQUAL => $value);
        }

        $params = array_filter(array_merge($this->_defaults, $params), array($this, '_filter'));
        $params = array_intersect_key($params, array_flip(array(
            'select', 'query', 'order', 'limit', 'offset'
        )));

        return $params;
    }

    protected function _normalize($params)
    {
        if (null !== $params) {
            $params = array_filter(array_merge($this->_defaults, (array) $params), array($this, '_filter'));
        } else {
            $params = $this->_defaults;
        }
        return $params;
    }

    protected function _filter($value)
    {
        return (null !== $value);
    }

    protected function _validate(Doctrine_Record $entity, $formIni)
    {
        $config = new Core_Config($formIni);
        $form   = new Core_Form($config);

        // Whatever request method is fine
        $form->setMethod(Core_Application::get('Request')->getMethod());

        // Populate values by hand, values do not come from the request per se
        $form->populate($entity->toArray());

        if (!$form->isValid()) {
            $errors = $form->getErrors();

            $exception = new Core_Validate_Exception('Error while validating values');
            $exception->setErrors($errors);

            throw $exception;
        } else {
            return true;
        }
    }
}
