<?php

/**
 * Класс модели данных
 *
 * @copyright NetMonsters <team@netmonsters.ru>
 * @link http://netmonsters.ru
 * @package Majestic
 * @subpackage Model
 * @since 2011-11-15
 */

/**
 * @property MongoDriver $db
 */
abstract class MongoModel extends Model
{

    /**
     * @var string Mongo id field to use as key
     */
    protected $key = '_id';

    /**
     * @var bool Is Mongo key field uses original MongoId
     */
    protected $useMongoId = true;

    public function __construct($connection = 'default')
    {
        parent::__construct($connection);
    }

    protected function count($query = array(), $limit = 0, $skip = 0)
    {
        return $this->db->count($this->table(), $query, $limit, $skip);
    }

    public function get($id)
    {
        if ($this->useMongoId) {
            if (!$id instanceof MongoId) {
                $id = new MongoId($id);
            }
        }
        return $this->fetch(array($this->key => $id));
    }

    public function batchInsert($data)
    {
        return $this->db->insert($this->table(), $data, true);
    }

    public function update($data, $where)
    {
        if ($this->useMongoId) {
            if (!$where instanceof MongoId) {
                $where = new MongoId($where);
            }
        }
        return $this->db->update($this->table(), $data, array($this->key => $where));
    }

    public function delete($id)
    {
        if ($this->useMongoId) {
            if (!$id instanceof MongoId) {
                $id = new MongoId($id);
            }
        }
        return $this->db->delete($this->table(), array($this->key => $id));
    }

    /**
     * @param array $data Request
     * @param array $params Request parameters
     * @param string $field Requested field name
     * @param CacheKey $cache_key Key for caching in
     * @return mixed
     */
    protected function fetchField($data, $params = array(), $field, $cache_key = null)
    {
        if (!$cache_key || !$result = $cache_key->get()) {
            $result = $this->db
                    ->find($this->table(), $data, array($field => 1))
                    ->limit($this->getLimit($params))
                    ->skip($this->getSkip($params))
                    ->order($this->getOrder($params))
                    ->fetchField($field);
            if ($cache_key) {
                $cache_key->set($result);
            }
        }
        return $result;
    }

    /**
     * @param array $data Request
     * @param array $params Request parameters
     * @param CacheKey $cache_key Key for caching in
     * @return mixed
     */
    protected function fetch($data, $params = array(), $cache_key = null)
    {
        if (!$cache_key || !$result = $cache_key->get()) {
            $result = $this->db
                    ->find($this->table(), $data, $this->getFields($params))
                    ->limit(1)
                    ->skip($this->getSkip($params))
                    ->order($this->getOrder($params))
                    ->fetch();
            if ($cache_key) {
                $cache_key->set($result);
            }
        }
        return $result;
    }

    /**
     * @param array $data
     * @param array $params
     * @param CacheKey $cache_key
     * @return array
     */
    protected function fetchAll($data, $params = array(), $cache_key = null)
    {
        if (!$cache_key || !$result = $cache_key->get()) {
            $result = $this->db
                    ->find($this->table(), $data, $this->getFields($params))
                    ->limit($this->getLimit($params))
                    ->skip($this->getSkip($params))
                    ->order($this->getOrder($params))
                    ->fetchAll();
            if ($cache_key) {
                $cache_key->set($result);
            }
        }
        return $result;
    }

    /**
     * @param array $params Parameters for find query
     * @return int Query result limits rule
     * */
    private function getLimit($params = array())
    {
        return isset($params['limit']) ? (int) $params['limit'] : 0;
    }

    /**
     * @param array $params Parameters for find query
     * @return int Query result skip rule
     * */
    private function getSkip($params = array())
    {
        return isset($params['skip']) ? (int) $params['skip'] : 0;
    }

    /**
     * @param array $params Parameters for find query
     * @return array Query result sort rules
     * @throws GeneralException
     * */
    private function getOrder($params = array())
    {
        $order = array();
        if (isset($params['order'])) {
            if (is_string($params['order'])) {
                $order = array($params['order'] => 1);
            } elseif (is_array($params['order'])) {
                $order = $params['order'];
            } else {
                throw new GeneralException('Wrong order parameter given to query.');
            }
        }
        return $order;
    }

    /**
     * @param array $params Parameters for find query
     * @return array Query result sort rules
     * @throws GeneralException
     * */
    private function getFields($params)
    {
        $fields = array();
        if (isset($params['fields'])) {
            if (is_string($params['fields'])) {
                $fields = array($params['fields'] => 1);
            } elseif (is_array($params['fields'])) {
                if (array_keys($params['fields']) === range(0, count($params['fields']) - 1)) {
                    foreach ($params['fields'] as $filed) {
                        $fields[$filed] = 1;
                    }
                } else {
                    $fields = $params['fields'];
                }
            } else {
                throw new GeneralException('Wrong fields parameter given to query.');
            }
        }
        return $fields;
    }

    /**
     * Sets the additional condition if it is set
     *
     * @param array $query Initial query
     * @param string $filed Field name to set
     * @param mixed $value Field value to set
     * @return MongoModel Current model object
     */
    protected function addCondition(&$query, $filed, $value)
    {
        if(!is_null($value) && $value !== false) {
            $query[$filed] = $value;
        }
        return $this;
    }
}