<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK IT ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006-2014 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st <liu21st@gmail.com>
// +----------------------------------------------------------------------
namespace Think\Model;

use Think\Model;

/**
 * ThinkPHP 聚合模型扩展
 */
class MergeModel extends Model
{

    protected $modelList   = array(); //  包含的模型列表 第一个必须是主表模型
    protected $masterModel = ''; //  主模型
    protected $joinType    = 'INNER'; //  聚合模型的查询JOIN类型
    protected $fk          = ''; //  外键名 默认为主表名_id
    protected $mapFields   = array(); //  需要处理的模型映射字段，避免混淆 array( id => 'user.id'  )

    /**
     * 架构函数
     * 取得DB类的实例对象 字段检查
     * @access public
     * @param string $name 模型名称
     * @param string $tablePrefix 表前缀
     * @param mixed $connection 数据库连接信息
     */
    public function __construct($name = '', $tablePrefix = '', $connection = '')
    {
        parent::__construct($name, $tablePrefix, $connection);
        // 聚合模型的字段信息
        if (empty($this->fields) && !empty($this->modelList)) {
            $fields = array();
            foreach ($this->modelList as $model) {
                // 获取模型的字段信息
                $result  = $this->db->getFields(M($model)->getTableName());
                $_fields = array_keys($result);
                // $this->mapFields  =   array_intersect($fields,$_fields);
                $fields = array_merge($fields, $_fields);
            }
            $this->fields = $fields;
        }

        // 设置第一个模型为主表模型
        if (empty($this->masterModel) && !empty($this->modelList)) {
            $this->masterModel = $this->modelList[0];
        }
        // 主表的主键名
        $this->pk = M($this->masterModel)->getPk();

        // 设置默认外键名 仅支持单一外键
        if (empty($this->fk)) {
            $this->fk = strtolower($this->masterModel) . '_id';
        }

    }

    /**
     * 得到完整的数据表名
     * @access public
     * @return string
     */
    public function getTableName()
    {
        if (empty($this->trueTableName)) {
            $tableName = array();
            $models    = $this->modelList;
            foreach ($models as $model) {
                $tableName[] = M($model)->getTableName() . ' ' . $model;
            }
            $this->trueTableName = implode(',', $tableName);
        }
        return $this->trueTableName;
    }

    /**
     * 自动检测数据表信息
     * @access protected
     * @return void
     */
    protected function _checkTableInfo()
    {}

    /**
     * 新增聚合数据
     * @access public
     * @param mixed $data 数据
     * @param array $options 表达式
     * @param boolean $replace 是否replace
     * @return mixed
     */
    public function add($data = '', $options = array(), $replace = false)
    {
        if (empty($data)) {
            // 没有传递数据，获取当前数据对象的值
            if (!empty($this->data)) {
                $data = $this->data;
                // 重置数据
                $this->data = array();
            } else {
                $this->error = L('_DATA_TYPE_INVALID_');
                return false;
            }
        }
        // 启动事务
        $this->startTrans();
        // 写入主表数据
        $result = M($this->masterModel)->strict(false)->add($data);
        if ($result) {
            // 写入外键数据
            $data[$this->fk] = $result;
            $models          = $this->modelList;
            array_shift($models);
            // 写入附表数据
            foreach ($models as $model) {
                $res = M($model)->strict(false)->add($data);
                if (!$res) {
                    $this->rollback();
                    return false;
                }
            }
            // 提交事务
            $this->commit();
        } else {
            $this->rollback();
            return false;
        }
        return $result;
    }

    /**
     * 对保存到数据库的数据进行处理
     * @access protected
     * @param mixed $data 要操作的数据
     * @return boolean
     */
    protected function _facade($data)
    {

        // 检查数据字段合法性
        if (!empty($this->fields)) {
            if (!empty($this->options['field'])) {
                $fields = $this->options['field'];
                unset($this->options['field']);
                if (is_string($fields)) {
                    $fields = explode(',', $fields);
                }
            } else {
                $fields = $this->fields;
            }
            foreach ($data as $key => $val) {
                if (!in_array($key, $fields, true)) {
                    unset($data[$key]);
                } elseif (array_key_exists($key, $this->mapFields)) {
                    // 需要处理映射字段
                    $data[$this->mapFields[$key]] = $val;
                    unset($data[$key]);
                }
            }
        }

        // 安全过滤
        if (!empty($this->options['filter'])) {
            $data = array_map($this->options['filter'], $data);
            unset($this->options['filter']);
        }
        $this->_before_write($data);
        return $data;
    }

    /**
     * 保存聚合模型数据
     * @access public
     * @param mixed $data 数据
     * @param array $options 表达式
     * @return boolean
     */
    public function save($data = '', $options = array())
    {
        // 根据主表的主键更新
        if (empty($data)) {
            // 没有传递数据，获取当前数据对象的值
            if (!empty($this->data)) {
                $data = $this->data;
                // 重置数据
                $this->data = array();
            } else {
                $this->error = L('_DATA_TYPE_INVALID_');
                return false;
            }
        }
        if (empty($data)) {
            // 没有数据则不执行
            $this->error = L('_DATA_TYPE_INVALID_');
            return false;
        }
        // 如果存在主键数据 则自动作为更新条件
        $pk = $this->pk;
        if (isset($data[$pk])) {
            $where[$pk]       = $data[$pk];
            $options['where'] = $where;
            unset($data[$pk]);
        }
        $options['join'] = '';
        $options         = $this->_parseOptions($options);
        // 更新操作不使用JOIN
        $options['table'] = $this->getTableName();

        if (is_array($options['where']) && isset($options['where'][$pk])) {
            $pkValue = $options['where'][$pk];
        }
        if (false === $this->_before_update($data, $options)) {
            return false;
        }
        $result = $this->db->update($data, $options);
        if (false !== $result) {
            if (isset($pkValue)) {
                $data[$pk] = $pkValue;
            }

            $this->_after_update($data, $options);
        }
        return $result;
    }

    /**
     * 删除聚合模型数据
     * @access public
     * @param mixed $options 表达式
     * @return mixed
     */
    public function delete($options = array())
    {
        $pk = $this->pk;
        if (empty($options) && empty($this->options['where'])) {
            // 如果删除条件为空 则删除当前数据对象所对应的记录
            if (!empty($this->data) && isset($this->data[$pk])) {
                return $this->delete($this->data[$pk]);
            } else {
                return false;
            }

        }

        if (is_numeric($options) || is_string($options)) {
            // 根据主键删除记录
            if (strpos($options, ',')) {
                $where[$pk] = array('IN', $options);
            } else {
                $where[$pk] = $options;
            }
            $options          = array();
            $options['where'] = $where;
        }
        // 分析表达式
        $options['join'] = '';
        $options         = $this->_parseOptions($options);
        if (empty($options['where'])) {
            // 如果条件为空 不进行删除操作 除非设置 1=1
            return false;
        }
        if (is_array($options['where']) && isset($options['where'][$pk])) {
            $pkValue = $options['where'][$pk];
        }

        $options['table'] = implode(',', $this->modelList);
        $options['using'] = $this->getTableName();
        if (false === $this->_before_delete($options)) {
            return false;
        }
        $result = $this->db->delete($options);
        if (false !== $result) {
            $data = array();
            if (isset($pkValue)) {
                $data[$pk] = $pkValue;
            }

            $this->_after_delete($data, $options);
        }
        // 返回删除记录个数
        return $result;
    }

    /**
     * 表达式过滤方法
     * @access protected
     * @param string $options 表达式
     * @return void
     */
    protected function _options_filter(&$options)
    {
        if (!isset($options['join'])) {
            $models = $this->modelList;
            array_shift($models);
            foreach ($models as $model) {
                $options['join'][] = $this->joinType . ' JOIN ' . M($model)->getTableName() . ' ' . $model . ' ON ' . $this->masterModel . '.' . $this->pk . ' = ' . $model . '.' . $this->fk;
            }
        }
        $options['table'] = M($this->masterModel)->getTableName() . ' ' . $this->masterModel;
        $options['field'] = $this->checkFields(isset($options['field']) ? $options['field'] : '');
        if (isset($options['group'])) {
            $options['group'] = $this->checkGroup($options['group']);
        }

        if (isset($options['where'])) {
            $options['where'] = $this->checkCondition($options['where']);
        }

        if (isset($options['order'])) {
            $options['order'] = $this->checkOrder($options['order']);
        }

    }

    /**
     * 检查条件中的聚合字段
     * @access protected
     * @param mixed $data 条件表达式
     * @return array
     */
    protected function checkCondition($where)
    {
        if (is_array($where)) {
            $view = array();
            foreach ($where as $name => $value) {
                if (array_key_exists($name, $this->mapFields)) {
                    // 需要处理映射字段
                    $view[$this->mapFields[$name]] = $value;
                    unset($where[$name]);
                }
            }
            $where = array_merge($where, $view);
        }
        return $where;
    }

    /**
     * 检查Order表达式中的聚合字段
     * @access protected
     * @param string $order 字段
     * @return string
     */
    protected function checkOrder($order = '')
    {
        if (is_string($order) && !empty($order)) {
            $orders = explode(',', $order);
            $_order = array();
            foreach ($orders as $order) {
                $array = explode(' ', trim($order));
                $field = $array[0];
                $sort  = isset($array[1]) ? $array[1] : 'ASC';
                if (array_key_exists($field, $this->mapFields)) {
                    // 需要处理映射字段
                    $field = $this->mapFields[$field];
                }
                $_order[] = $field . ' ' . $sort;
            }
            $order = implode(',', $_order);
        }
        return $order;
    }

    /**
     * 检查Group表达式中的聚合字段
     * @access protected
     * @param string $group 字段
     * @return string
     */
    protected function checkGroup($group = '')
    {
        if (!empty($group)) {
            $groups = explode(',', $group);
            $_group = array();
            foreach ($groups as $field) {
                // 解析成聚合字段
                if (array_key_exists($field, $this->mapFields)) {
                    // 需要处理映射字段
                    $field = $this->mapFields[$field];
                }
                $_group[] = $field;
            }
            $group = implode(',', $_group);
        }
        return $group;
    }

    /**
     * 检查fields表达式中的聚合字段
     * @access protected
     * @param string $fields 字段
     * @return string
     */
    protected function checkFields($fields = '')
    {
        if (empty($fields) || '*' == $fields) {
            // 获取全部聚合字段
            $fields = $this->fields;
        }
        if (!is_array($fields)) {
            $fields = explode(',', $fields);
        }

        // 解析成聚合字段
        $array = array();
        foreach ($fields as $field) {
            if (array_key_exists($field, $this->mapFields)) {
                // 需要处理映射字段
                $array[] = $this->mapFields[$field] . ' AS ' . $field;
            } else {
                $array[] = $field;
            }
        }
        $fields = implode(',', $array);
        return $fields;
    }

    /**
     * 获取数据表字段信息
     * @access public
     * @return array
     */
    public function getDbFields()
    {
        return $this->fields;
    }

}
