<?php
/**
 * Model.class.php
 * $author$
 */
namespace Com;

abstract class Model
{

    /** 正常状态 */
    const ST_CREATE = 1;

    /** 已更新 */
    const ST_UPDATE = 2;

    /** 已删除 */
    const ST_DELETE = 3;

    /** Model 实例 */
    protected $_m = null;

    /** 当前 Model 名称 */
    protected $_model_name = '';

    /** 字段前缀 */
    public $prefield = '';

    /**
     * 表格前缀
     * @var string
     */
    protected $_table_prefix = '';

    /**
     * 构造方法
     *
     * @param string $name
     * @param string $table_prefix
     * @param string $connection
     */
    public function __construct($name = '', $table_prefix = '', $connection = '')
    {

        // 如果当前模块名称为空
        if (!empty($name)) {
            $this->_model_name = $name;
        }
        // 初始化数据库连接
        $this->_m = new \Think\Model($this->get_model_name(), empty($table_prefix) ? $this->_table_prefix : $table_prefix, $connection);

    }

    /**
     * 得到当前的数据对象名称
     *
     * @access public
     * @return string
     */
    public function get_model_name()
    {

        // 如果当前 Model 名称为空
        if (empty($this->_model_name)) {
            $name = substr(get_class($this), 0, -strlen(C('DEFAULT_M_LAYER')));
            $pos = strrpos($name, '\\');
            if ($pos) { // 有命名空间
                $this->_model_name = substr($name, $pos + 1);
            } else {
                $this->_model_name = $name;
            }
        }

        return $this->_model_name;
    }

    // 获取表名
    public function get_tname()
    {

        return $this->_m->getTableName();
    }

    /**
     * 根据主键获取数据
     * @param      $val
     * @param bool $withOutDomain 跳过企业标识查询
     * @return array
     */
    public function get($val, $withOutDomain = false)
    {
        $sql = 'SELECT * FROM __TABLE__ WHERE ';

        $where = [
            '`' . $this->_m->getPk() . '`=?',
            '`' . $this->prefield . 'domain`=?',
            '`' . $this->prefield . 'status`<?',
        ];

        $params = [
            (string)$val,
            QY_DOMAIN,
            $this->get_st_delete()
        ];

        // 排除企业标识
        if ($withOutDomain) {
            unset($where[1], $params[1]);
            // 要重组下数组 不然 ORM 查询会出问题
            $where = array_values($where);
            $params = array_values($params);
        }

        // 设置条件
        return $this->_m->fetch_row($sql . implode(' AND ', $where), $params);
    }

    /**
     * 获取数据列表
     *
     * @param int|array $page_option 分页参数
     * @param array $order_option 排序参数
     *
     * @return array|bool
     */
    public function list_all($page_option = null, $order_option = array())
    {

        // limit
        $limit = '';
        if (!$this->_limit($limit, $page_option)) {
            return false;
        }

        // 排序
        $orderby = '';
        if (!$this->_order_by($orderby, $order_option)) {
            return false;
        }

        // 执行 SQL
        return $this->_m->fetch_array("SELECT * FROM __TABLE__ WHERE `{$this->prefield}domain`=? AND `{$this->prefield}status`<?{$orderby}{$limit}",
            array(
                QY_DOMAIN,
                $this->get_st_delete()
            ));
    }

    /**
     * 获取数据列表
     *
     * @param int|array $page_option 分页参数
     * @param array $order_option 排序参数
     *
     * @return array|bool
     */
    public function list_all_without_domain($page_option = null, $order_option = array())
    {

        // limit
        $limit = '';
        if (!$this->_limit($limit, $page_option)) {
            return false;
        }

        // 排序
        $orderby = '';
        if (!$this->_order_by($orderby, $order_option)) {
            return false;
        }

        // 执行 SQL
        return $this->_m->fetch_array("SELECT * FROM __TABLE__ WHERE `{$this->prefield}status`<?{$orderby}{$limit}",
            array(
                $this->get_st_delete()
            ));
    }

    /**
     * 删除字段
     *
     * @param mixed $vals
     *            主键对应的值
     *
     * @return object
     */
    public function delete($vals)
    {

        // 如果参数为空
        if (empty($vals)) {
            E('_ERR_DELETE_CONDS_INVALID_');

            return false;
        }

        // 执行 SQL
        return $this->_m->execsql("UPDATE __TABLE__ SET `{$this->prefield}status`=?, `{$this->prefield}deleted`=?
                WHERE `" . $this->_m->getPk() . "` IN (?)
                AND `{$this->prefield}domain`=? AND `{$this->prefield}status`<?", array(
            $this->get_st_delete(),
            $this->nowOrMilliTime($this->prefield . 'deleted'),
            (array)$vals,
            QY_DOMAIN,
            $this->get_st_delete()
        ));
    }

    /**
     * 更新数据
     *
     * @param int $val 主键键值
     * @param array $data 待更新数据
     * @param bool $withOutDomain 排除企业标识
     *
     * @return bool
     */
    public function update($val = null, $data = array(), $withOutDomain = false)
    {

        // 补齐时间信息
        $this->_fill_status_timestamp($data, $this->get_st_update(), $withOutDomain);
        // PDO params
        $params = array();
        // 更新时 SET 数据
        $sets = array();
        if (!$this->_parse_set($sets, $params, $data)) {
            return false;
        }

        $sql = "UPDATE __TABLE__ SET " . implode(",", $sets) . " WHERE ";

        // 条件值
        $where = [
            '`' . $this->_m->getPk() . '` IN (?)',
            '`' . $this->prefield . 'status`<?'
        ];
        // 加入查询条件
        $params[] = (array)$val;
        $params[] = $this->get_st_delete();

        if (!$withOutDomain) {
            $params[] = QY_DOMAIN;
            $where[] = '`' . $this->prefield . 'domain`=?';
        }

        // 执行
        return $this->_m->execsql($sql . implode(' AND ', $where), $params);
    }

    /**
     * 更新数据 (条件排除 企业标识)
     *
     * @param int $val 主键键值
     * @param array $data 待更新数据
     *
     * @return bool
     */
    public function updateWithOutDomain($val = null, $data = array())
    {

        // 补齐时间信息
        $this->fillStatusTimestampWithOutDomain($data, $this->get_st_update());
        // PDO params
        $params = array();
        // 更新时 SET 数据
        $sets = array();
        if (!$this->_parse_set($sets, $params, $data)) {
            return false;
        }

        // 加入查询条件
        $params[] = (array)$val;
        $params[] = $this->get_st_delete();

        // 执行
        return $this->_m->execsql("UPDATE __TABLE__ SET " . implode(",",
                $sets) . " WHERE `" . $this->_m->getPk() . "` IN (?) AND `{$this->prefield}status`<?",
            $params);
    }

    /**
     * 统计总数
     *
     * @param int $page_options 分页选项
     *
     * @return array|bool
     */
    public function count($page_options = 0)
    {

        // LIMIT
        $limit = '';
        if (!$this->_limit($limit, $page_options)) {
            return false;
        }
        // 状态查询条件
        $params = array(
            QY_DOMAIN,
            $this->get_st_delete()
        );

        // 执行 SQL
        return $this->_m->result("SELECT COUNT(*) FROM __TABLE__ WHERE `{$this->prefield}domain`=? AND `{$this->prefield}status`<?{$limit}",
            $params);
    }

    /**
     * 根据主键值读取数据
     *
     * @param int|string|array $vals 查询条件
     * @param array $orders 排序
     *
     * @return array|bool
     */
    public function list_by_pks($vals, $orders = array())
    {

        if (empty($vals)) {
            return array();
        }

        // 设置条件
        $orderby = '';
        if (!$this->_order_by($orderby, $orders)) {
            return false;
        }

        // 状态查询条件
        $params = array(
            $vals,
            QY_DOMAIN,
            $this->get_st_delete()
        );

        return $this->_m->fetch_array(
            "SELECT * FROM __TABLE__ WHERE 
                `" . $this->_m->getPk() . "` IN (?) AND
                `{$this->prefield}domain`=? AND
                `{$this->prefield}status`<?" .
            $orderby,
            $params);
    }

    /**
     * 根据条件读取数据
     *
     * @param array $conds 查询条件数组
     * @param array $order_option 排序数组
     * @param bool $withOutDomain 排除企业标识字段
     *
     * @return array|bool
     */
    public function get_by_conds($conds, $order_option = array(), $withOutDomain = false)
    {

        $params = array();
        // 条件
        $wheres = array();
        if (!$this->_parse_where($wheres, $params, $conds)) {
            return false;
        }

        // 排序
        $orderby = '';
        if (!$this->_order_by($orderby, $order_option)) {
            return false;
        }

        // 企业标记
        if (!$withOutDomain) {
            $wheres[] = "`{$this->prefield}domain`=?";
            $params[] = QY_DOMAIN;
        }
        // 状态条件
        $wheres[] = "`{$this->prefield}status`<?";
        $params[] = $this->get_st_delete();

        // 执行 SQL
        return $this->_m->fetch_row("SELECT * FROM __TABLE__ WHERE " . implode(' AND ', $wheres) . "{$orderby} LIMIT 1",
            $params);
    }

    /**
     * 根据条件读取数据数组
     *
     * @param array $conds 条件数组
     * @param int|array $page_option 分页参数
     * @param array $order_option 排序
     * @param string $fields 读取字段
     * @param array $extOption
     *                  withOutDomain true 跳过 domain 字段查询
     *                  withOutStatus true 跳过 domain 字段查询
     *
     * @return array|bool
     */
    public function list_by_conds($conds, $page_option = null, $order_option = array(), $fields = '*', $extOption = [])
    {

        $params = array();
        // 条件
        $wheres = array();
        if (!$this->_parse_where($wheres, $params, $conds)) {
            return false;
        }
        if (empty($extOption['withOutDomain'])) {
            // 企业标记
            $wheres[] = "`{$this->prefield}domain`=?";
            $params[] = QY_DOMAIN;
        }
        if (empty($extOption['withOutStatus'])) {
            // 状态条件
            $wheres[] = "`{$this->prefield}status`<?";
            $params[] = $this->get_st_delete();
        }

        // 排序
        $orderby = '';
        if (!$this->_order_by($orderby, $order_option)) {
            return false;
        }
        // 分页参数
        $limit = '';
        if (!$this->_limit($limit, $page_option)) {
            return false;
        }
        // 读取记录
        $sql = "SELECT {$fields} FROM __TABLE__ ";
        if (!empty($wheres)) {
            $sql .= 'WHERE ' . implode(' AND ', $wheres);
        }

        return $this->_m->fetch_array($sql . "{$orderby}{$limit}", $params);
    }

    /**
     * 没有企业标识的查询
     * @param        $conds
     * @param null   $page_option
     * @param array  $order_option
     * @param string $fields
     * @return array|bool
     */
    public function listWithOutDomian($conds, $page_option = null, $order_option = array(), $fields = '*')
    {
        $params = array();
        // 条件
        $wheres = array();
        if (!$this->_parse_where($wheres, $params, $conds)) {
            return false;
        }
        // 状态条件
        $wheres[] = "`{$this->prefield}status`<?";
        $params[] = $this->get_st_delete();

        // 排序
        $orderby = '';
        if (!$this->_order_by($orderby, $order_option)) {
            return false;
        }
        // 分页参数
        $limit = '';
        if (!$this->_limit($limit, $page_option)) {
            return false;
        }
        // 读取记录
        return $this->_m->fetch_array("SELECT {$fields} FROM __TABLE__ WHERE " . implode(' AND ',
                $wheres) . "{$orderby}{$limit}", $params);
    }

    /**
     * 根据条件更新数据
     *
     * @param array $conds 条件数组
     * @param array $data 数据数组
     * @param bool $withOutDomain 不带企业标识
     * @return bool|mixed
     */
    public function update_by_conds($conds, $data, $withOutDomain = false)
    {

        // 补齐时间信息
        $this->_fill_status_timestamp($data, $this->get_st_update(), $withOutDomain);
        $params = array();
        // 更新时 SET 数据
        $sets = array();
        if (!$this->_parse_set($sets, $params, $data)) {
            return false;
        }

        // 更新条件
        $wheres = array();
        if (!$this->_parse_where($wheres, $params, $conds)) {
            return false;
        }

        // 企业标记
        if (!$withOutDomain) {
            $wheres[] = "`{$this->prefield}domain`=?";
            $params[] = QY_DOMAIN;
        }
        // 状态条件
        $wheres[] = "`{$this->prefield}status`<?";
        $params[] = $this->get_st_delete();

        return $this->_m->execsql("UPDATE __TABLE__ SET " . implode(',', $sets) . " WHERE " . implode(' AND ', $wheres),
            $params);
    }

    /**
     * 根据条件删除数据
     *
     * @param array $conds 删除条件数组
     *
     * @return bool
     */
    public function delete_by_conds($conds)
    {

        $params = array();
        // SET
        $sets = array(
            "`{$this->prefield}status`=?",
            "`{$this->prefield}deleted`=?"
        );
        $params[] = $this->get_st_delete();
        $params[] = $this->nowOrMilliTime($this->prefield . 'deleted');
        // 更新条件
        $wheres = array();
        if (!$this->_parse_where($wheres, $params, $conds)) {
            return false;
        }

        // 企业标记
        $wheres[] = "`{$this->prefield}domain`=?";
        $params[] = QY_DOMAIN;
        // 状态条件
        $wheres[] = "`{$this->prefield}status`<?";
        $params[] = $this->get_st_delete();

        return $this->_m->execsql('UPDATE __TABLE__ SET ' . implode(',', $sets) . ' WHERE ' . implode(' AND ', $wheres),
            $params);
    }

    /**
     * 根据条件计算数量
     *
     * @param array $conds
     * @param string $fields
     * @param bool   $withOutDomain 排除企业标识字段
     * @param bool   $withOutStatus 排除数据状态字段
     *
     * @return number|bool
     */
    public function count_by_conds($conds, $fields = '*', $withOutDomain = false, $withOutStatus = false)
    {

        $params = array();
        // 更新条件
        $wheres = array();
        if (!$this->_parse_where($wheres, $params, $conds)) {
            return false;
        }

        // 企业标记
        if (!$withOutDomain) {
            $wheres[] = "`{$this->prefield}domain`=?";
            $params[] = QY_DOMAIN;
        }
        if (!$withOutStatus) {
            // 状态条件
            $wheres[] = "`{$this->prefield}status`<?";
            $params[] = $this->get_st_delete();
        }

        return $this->_m->result('SELECT COUNT(' . $fields . ') FROM __TABLE__ WHERE ' . implode(' AND ', $wheres), $params);
    }

    /**
     * 插入单条数据
     *
     * @param string $data 待插入数据
     * @param array $options 表达式
     * @param boolean $replace 是否使用 REPLACE INTO
     *
     * @return \Think\mixed
     */
    public function insert($data = '', $options = array(), $replace = false)
    {

        // 补齐时间信息
        $this->_fill_status_timestamp($data, $this->get_st_create());

        return $this->_m->insert($data, $options, $replace);
    }

    /**
     * 插入多条数据
     *
     * @param array $list 待插入数据数组
     * @param array $options 表达式
     * @param boolean $replace 是否使用 REPLACE INTO
     *
     * @return Ambigous <boolean, string, unknown>
     */
    public function insert_all($list, $options = array(), $replace = false)
    {

        // 补齐所有时间信息
        foreach ($list as &$_data) {
            $this->_fill_status_timestamp($_data, $this->get_st_create());
        }

        return $this->_m->insert_all($list, $options, $replace);
    }

    // 开始存储过程
    public function start_trans()
    {

        return $this->_m->startTrans();
    }

    // 回滚
    public function rollback()
    {

        return $this->_m->rollback();
    }

    // 提交
    public function commit()
    {

        return $this->_m->commit();
    }

    /**
     * 解析 SQL 的 WHERE 参数
     *
     * @param array $wheres SQL 的更新
     * @param array $params PDO 的参数数组
     * @param array $data WHERE 的数据
     *
     * @return boolean
     */
    protected function _parse_where(&$wheres, &$params, $data)
    {

        // 遍历所有 where
        foreach ($data as $field => $_v) {
            // 检查字段的合法性
            $field = trim($field);
            if ($this->__isField($field)) {
                // 去掉数据为空的情况
                if (is_array($_v) && empty($_v)) {
                    unset($data[$field]);
                    continue;
                }
                $wheres[] = is_array($_v) ? "{$field} IN (?)" : "{$field}=?";
            } elseif ($this->__isSqlWhere($field)) {
                $wheres[] = $field;
            }
            // FIXME 这里判断要兼容 field ! 类似这种
            //            else {
            //                E('_ERR_WHERE_FIELD_INVALID');
            //
            //                return false;
            //            }

            $params[] = $_v;
        }

        return true;
    }

    /**
     * 检查是否为查询条件语法
     *
     * @param string $where 查询条件
     *
     * @return bool
     */
    private function __isSqlWhere($where)
    {

        if (!preg_match("/^([a-z0-9\`\_\'\"\.]+)\s*(=|>|<|!=|>=|<=|\sLIKE|<>|\sBETWEEN|\sIN|NOT\s*IN)\s*[\w\W]+/i", $where)) {
            return false;
        }

        return true;
    }

    /**
     * 解析 SQL 的 SET 参数
     *
     * @param array $sets SQL 的更新
     * @param array $params PDO 的参数数组
     * @param array $data SET 的数据
     *
     * @return boolean
     */
    protected function _parse_set(&$sets, &$params, $data)
    {

        // 遍历所有 set
        foreach ($data as $field => $_v) {
            // 检查字段的合法性
            if ($this->__isField($field)) {
                $sets[] = is_array($_v) ? "{$field} IN (?)" : "{$field}=?";
            } elseif ($this->__isSqlWhere($field)) {
                $sets[] = $field;
            } else {
                E('_ERR_SET_FIELD_INVALID');
                return false;
            }

            $params[] = is_array($_v) ? serialize($_v) : $_v;
        }

        return true;
    }

    /**
     * 检查字段合法性
     *
     * @param string $field 字段名
     *
     * @return bool
     */
    private function __isField($field)
    {

        // 验证字段规则
        if (!preg_match("/^[a-z0-9\`\_\'\"\.]+$/i", $field)) {

            return false;
        }

        return true;
    }

    /**
     * 生成排序
     *
     * @param string $orderby 排序字串
     * @param array $options 传入参数
     *
     * @return boolean
     */
    protected function _order_by(&$orderby, $options = array())
    {

        $orders = array();
        // 遍历所有排序
        foreach ($options as $_field => $_dir) {
            // 检查字段的合法性
            if (!$this->__isField($_field)) {
                E('_ERR_ORDER_BY_FIELD_INVALID_', array(
                    'field' => $_field
                ));

                return false;
            }
            $_dir = rstrtoupper($_dir);
            $orders[] = $_field . ' ' . ('DESC' == $_dir ? $_dir : 'ASC');
        }

        // 排序字串
        if (!empty($orders)) {
            $orderby = ' ORDER BY ' . implode(',', $orders);
        }

        return true;
    }

    /**
     * 解析 SQL limit
     *
     * @param string $limit limit字串
     * @param int $options 传入参数
     *
     * @return boolean
     */
    protected function _limit(&$limit, $options = 10)
    {

        // 如果参数为数组时
        if (is_array($options)) {
            // 如果数组元素个数 <= 2
            if (count($options) <= 2) {
                foreach ($options as &$_i) {
                    $_i = (int)$_i;
                }
                unset($_i);
                $limit = implode(',', $options);
            } else { // > 2 时, 报错
                E('_ERR_SQL_LIMIT_INVALID_');

                return false;
            }
        } else {
            $limit = (int)$options;
        }

        // 如果有, 则
        if (!empty($limit)) {
            $limit = ' LIMIT ' . $limit;
        } else {
            $limit = '';
        }

        return true;
    }

    /**
     * 根据状态补齐时间字段信息
     *
     * @param array $data 更新数据
     * @param int $status 状态值
     * @param bool $withOutDomain 排除企业标识
     *
     * @return boolean
     */
    protected function _fill_status_timestamp(&$data, $status, $withOutDomain = false)
    {

        // 状态字段
        $k_status = $this->prefield . 'status';
        $k_ts = '';
        if ($status == $this->get_st_create()) {
            // 创建时间字段
            $k_ts = $this->prefield . 'created';
        } elseif ($status == $this->get_st_update()) {
            // 更新时间字段
            $k_ts = $this->prefield . 'updated';
        } elseif ($status == $this->get_st_delete()) {
            // 删除时间字段
            $k_ts = $this->prefield . 'deleted';
        }

        // 如果没有指定状态值
        if (!isset($data[$k_status])) {
            $data[$k_status] = $this->get_st_create();
        }

        // 如果没有指定创建时间
        if (!empty($k_ts) && !isset($data[$k_ts])) {
            $data[$k_ts] = $this->nowOrMilliTime($k_ts);
        }

        if (!$withOutDomain) {
            // 当前所在企业
            $k_domain = $this->prefield . 'domain';
            if (!isset($data[$k_domain])) {
                $data[$k_domain] = QY_DOMAIN;
            }
        }

        return true;
    }

    /**
     * 根据状态补齐时间字段信息 (排除 企业标识)
     *
     * @param array $data 更新数据
     * @param int $status 状态值
     *
     * @return boolean
     */
    protected function fillStatusTimestampWithOutDomain(&$data, $status)
    {

        // 状态字段
        $k_status = $this->prefield . 'status';
        $k_ts = '';
        if ($status == $this->get_st_create()) {
            // 创建时间字段
            $k_ts = $this->prefield . 'created';
        } elseif ($status == $this->get_st_update()) {
            // 更新时间字段
            $k_ts = $this->prefield . 'updated';
        } elseif ($status == $this->get_st_delete()) {
            // 删除时间字段
            $k_ts = $this->prefield . 'deleted';
        }

        // 如果没有指定状态值
        if (!isset($data[$k_status])) {
            $data[$k_status] = $this->get_st_create();
        }

        // 如果没有指定创建时间
        if (!empty($k_ts) && !isset($data[$k_ts])) {
            $data[$k_ts] = $this->nowOrMilliTime($k_ts);
        }

        return true;
    }

    /**
     * 用NOW_TIME或者MILLI_TIME
     * @param string $field 字段名称
     * @return mixed
     */
    public function nowOrMilliTime($field)
    {

        $fields = $this->_m->getDbFields(true);
        $types = $fields['_type'];
        if (stripos($types[$field], 'bigint') !== false) {
            return MILLI_TIME;
        }

        return NOW_TIME;
    }

    /**
     *  where 条件
     * @param array $where
     * @param null $parse
     */
    public function where($where, $parse = null)
    {

        $this->_m->where($where, $parse);
    }

    /**
     * 字段值增长
     *
     * @access public
     * @param string $field
     *            字段名
     * @param integer $step
     *            增长值
     * @return boolean
     */
    public function setInc($field, $step = 1)
    {

        $this->_m->setInc($field, $step);
    }

    /**
     * 字段值减少
     *
     * @access public
     * @param string $field
     *            字段名
     * @param integer $step
     *            减少值
     * @return boolean
     */
    public function setDec($field, $step = 1)
    {

        $this->_m->setDec($field, $step);
    }

    // 获取删除状态值
    public function get_st_delete()
    {

        return self::ST_DELETE;
    }

    public function get_st_update()
    {

        return self::ST_UPDATE;
    }

    public function get_st_create()
    {

        return self::ST_CREATE;
    }
}