<?php
namespace Qii\Base;

use Qii\Exceptions\InvalidParams;
use Qii\Exceptions\MethodNotFound;
use Qii\Exceptions\TableException;
use Qii\Execptions;

/**
 * @method bool addRequiredFields(array $data = [string, string, bool|string|int], array $data=[...])  [string $field string $msg string $extparam],...
 * @method bool addEmailFields(array $data = [string, string, bool|string|int], array $data=[...])  [string $field string $msg string $extparam],...
 * @method bool addIdCodeFields(array $data = [string, string, bool|string|int], array $data=[...])  [string $field string $msg string $extparam],...
 * @method bool addHttpFields(array $data = [string, string, bool|string|int], array $data=[...])  [string $field string $msg string $extparam],...
 * @method bool addQQFields(array $data = [string, string, bool|string|int], array $data=[...])  [string $field string $msg string $extparam],...
 * @method bool addPostCodeFields(array $data = [string, string, bool|string|int], array $data=[...])  [string $field string $msg string $extparam],...
 * @method bool addIpFields(array $data = [string, string, bool|string|int], array $data=[...])  [string $field string $msg string $extparam],...
 * @method bool addPhoneFields(array $data = [string, string, bool|string|int], array $data=[...])  [string $field string $msg string $extparam],...
 * @method bool addTelephoneFields(array $data = [string, string, bool|string|int], array $data=[...])  [string $field string $msg string $extparam],...
 * @method bool addMobileFields(array $data = [string, string, bool|string|int], array $data=[...])  [string $field string $msg string $extparam],...
 * @method bool addEnFields(array $data = [string, string, bool|string|int], array $data=[...])  [string $field string $msg string $extparam],...
 * @method bool addCnFields(array $data = [string, string, bool|string|int], array $data=[...])  [string $field string $msg string $extparam],...
 * @method bool addAccoundFields(array $data = [string, string, bool|string|int], array $data=[...])  [string $field string $msg string $extparam],...
 * @method bool addNumberFields(array $data = [string, string, bool|string|int], array $data=[...])  [string $field string $msg string $extparam],...
 * @method bool addDateFields(array $data = [string, string, bool|string|int], array $data=[...])  [string $field string $msg string $extparam],...
 * @method bool addSafeFields(array $data = [string, string, bool|string|int], array $data=[...])  [string $field string $msg string $extparam],...
 * @method bool addPasswordFields(array $data = [string, string, bool|string|int], array $data=[...])  [string $field string $msg string $extparam],...
 * @method bool addMaxLengthFields(array $data = [string, string, bool|string|int], array $data=[...])  [string $field string $msg string $extparam],...
 * @method bool addMinLengthFields(array $data = [string, string, bool|string|int], array $data=[...])  [string $field string $msg string $extparam],...
 * @method bool addMaxNumberFields(array $data = [string, string, bool|string|int], array $data=[...])  [string $field string $msg string $extparam],...
 * @method bool addMinNumberFields(array $data = [string, string, bool|string|int], array $data=[...])  [string $field string $msg string $extparam],...
 * @method bool addLengthFields(array $data = [string, string, bool|string|int], array $data=[...])  [string $field string $msg string $extparam],...
 * @method bool addRangeofFields(array $data = [string, string, bool|string|int], array $data=[...])  [string $field string $msg string $extparam],...
 * @method bool addStringFields(array $data = [string, string, bool|string|int], array $data=[...])  [string $field string $msg string $extparam],...
 * @method bool addSetsFields(array $data = [string, string, bool|string|int], array $data=[...])  [string $field string $msg string $extparam],...
 * @method bool addSetsArrayFields(array $data = [string, string, bool|string|int], array $data=[...])  [string $field string $msg string $extparam],...
 * @method bool addDecimalFields(array $data = [string, string, string], array $data=[...]) [string $field string $msg string $extparam],...
 * @method bool addFloatFields(array $data = [string, string], array $data=[...]) [string $field string $msg string $extparam],...
 */
class Rules 
{
    /**
     * @var array $rules 验证规则
     */
    private $rules = array();
    /**
     * @var array $message 验证消息
     */
    private $message = array();
    /**
     * @var array $data 待验证的数据
     */
    private $data = array();
    /**
     * 需要验证的字段
     */
    private $forceValidKey = array();
    /**
     * 选择其中一个去验证
     */
    private $optionValidKey = array();

    public $table = '';
    /**
     * 字段名称
     */
    private $fields = array();
    /**
     * @var object $validate 验证工具
     */
    public $validate;

    public function __construct()
    {
        $this->validate = \_loadClassOnce('\Qii\Library\Validate');
        $this->constants();
        $this->clean();
    }

    public function getDefaultValues()
    {
        $data = array();
        foreach($this->fields() AS $key => $val)
        {
            $data[$val] = '';
        }
        return $data;
    }

    /**
     * 返回数据表中的字段名
     *
     * @return array
     */
    public function fields()
    {
        return $this->fields;
    }
    /**
     * 添加字段
     * @param string | array $fields 字段名
     */
    public function addFields($fields)
    {
        if(empty($fields)) return;
        if(is_array($fields))
        {
            return array_map(function ($n) {
                return $this->addFields($n);
            }, $fields);
        }
        $this->fields[] = $fields;
    }
    /**
     * 定义规则
     */
    public function constants()
    {
        return array();
    }

    /**
     * 获取所有字段中的数据
     *
     * @return array
     */
    public function getValues()
    {
        return $this->data;
    }

    /**
     * 获取制定字段数据
     *
     * @param string $field 字段名
     * @return mixed|null
     */
    public function getValue($field)
    {
        if(isset($this->data[$field])) return $this->data[$field];
        return null;
    }
    /**
     * 清空数据
     */
    public function clean()
    {
        $this->data = array();
        $this->fields = array();
        $this->forceValidKey = array();
    }
    /**
     * 获取指定字段的规则配置
     * @param string $field
     * @return array
     */
    public function get($field)
    {
        $data = array();
        if(isset($this->rules[$field]))
        {
            $data['rules'] = $this->rules[$field];
            $data['message'] = isset($this->message[$field]) ? $this->message[$field] : '未设置';
        }
        return $data;
    }
    /**
     * 自动获取rules中定义的规则
     * @param string $method 方法名
     */
    public function autoForceValidKeyForMethod($method)
    {
        if(!$method) throw new Exception(__METHOD__ . ' parameter error.', 1);
        if(method_exists($this, $method))
        {
            $this->$method();
        }
        if(!$method) throw new Exception(__METHOD__ . ' undefined.', 1);
    }

    /**
     * 批量添加验证规则
     *
     * @param array $rules [$field, $key, $valid, $message]
     * @return void
     */
    public function batchRules(array $rules)
    {
        foreach ($rules as $rule)
        {
            list($field, $key, $valid, $message) = $rule;
            $this->addRules($field, $key, $valid, $message);
        }
    }

    /**
     * 添加规则
     * @param string $field 验证字段
     * @param string $key 验证规则
     * @param mixed $valid 验证参数，如 key=length valid=10
     * @param string $message 验证失败返回的消息
     * @return void
     */
    public function addRules($field, $key, $valid, $message)
    {
        if(!$field || !$key || $valid === null || $valid === '') return;
        if(!$this->isAllow($key)) return;
        $this->rules[$field][$key] = $valid;
        $this->message[$field][$key] = $message;
    }
    /**
     * 移除规则
     * @param string $fields 字段名
     */
    public function removeRules($fields)
    {
        if(!$fields) return;
        if(is_array($fields))
        {
            return array_map(function ($n) {
                return $this->removeRules($n);
            }, $fields);
        }
        if(isset($this->rules[$fields])) 
        {
            unset($this->rules[$fields]);
            unset($this->message[$fields]);
        }
    }
    /**
     * 添加必须验证用的字段
     * @param string $key 字段名
     */
    public function addForceValidKey($key)
    {
        if(!$key) return;
        if(is_array($key))
        {
            foreach ($key as $k => $value) 
            {
                $this->addForceValidKey($value);
            }
        }
        else
        {
            if(!in_array($key, $this->forceValidKey) && in_array($key, $this->fields()))
            {
                $this->forceValidKey[] = $key;
            }
        }
    }

    public function removeForceValidKey($k)
    {
        foreach ($this->forceValidKey as $key => $value) 
        {
            if($value == $k)
            {
                unset($this->forceValidKey[$key]);
            }
        }
    }
    /**
     * 添加必须其中某一个字段，选择不为空的字段去验证
     * 
     * @param string $key 字段名
     */
    public function addOptionValidKey($key)
    {
        if(!$key) return;
        if(is_array($key))
        {
            foreach ($key as $k => $value) 
            {
                $this->addOptionValidKey($value);
            }
        }
        else
        {
            if(!in_array($key, $this->optionValidKey) && in_array($key, $this->fields()))
            {
                $this->optionValidKey[] = $key;
            }
        }
    }
    /**
     * 给数据添加属性
     * @param string $field 字段
     * @param mix $val 值
     */
    public function addValue($field, $val)
    {
        if(in_array($field, $this->fields())){
            $this->data[$field] = $val;
        }
    }
    /**
     * 添加数据
     * @param array $data 数据
     */
    public function addValues($data)
    {
        foreach ($data AS $field => $value){
            $this->addValue($field, $value);
        }
    }

    public function verifyField($value, $rules){
        $data = array();
        $data['data'] = $this->data;
        $data['code'] = 0;
        $data['valid'] = true;
        $data['msg'] = '';
        foreach($rules as $rule)
        {
            $result = $this->validate->verify(
                array($value),
                array($rule['rules']),
                array($rule['message'])
            );
            if($result !== true){
                $data['valid'] = false;
                $data['code'] = 20000;
                $data['errorInfo'] = $result;
            }
        }
        return $data;
    }

    /**
     * 验证字段的值
     *
     * @param array $fields 需要验证的[字段=>值]
     * @return array
     */
    public function verifyFields($fields) {
        $data = array();
        $data['data'] = $fields;
        $data['code'] = 0;
        $data['valid'] = true;

        if(!is_array($fields)) {
            $data['valid'] = true;
            return $data;
        }
        $msg = [];
        foreach ($fields as $key => $val) {
            $rule = $this->get($key);
            if(!$rule)
            {
                $rule['rules'] = array('required' => true);
                $rule['message'] = array('required' => $key .'不能为空');
            }
            $result = $this->validate->verify(
                array($key => $val),
                array($key => $rule['rules']),
                array($key => $rule['message'])
            );
            if($result !== true){
                $data['valid'] = false;
                $data['code'] = 20000;
                $data['errorInfo'][$key] = $result;
                $msg[] = $result;
            }
        }
        if(count($msg) > 0) $data['msg'] = join("\n", $msg);
        return $data;
    }
    /**
     * 验证数据，验证将返回数据及验证结果
     * @return bool
     */
    public function verify()
    {
        $data = array();
        $data['data'] = $this->data;
        $data['code'] = 0;
        $data['valid'] = true;
        $data['msg'] = '';
        if(empty($this->forceValidKey))
        {
            return $data;
        }
        //将optionValidKey中不为空的字段添加到必须验证的字段中去
        //如果选择验证的都没数据就提示参数错误
        $options = array();
        foreach($this->optionValidKey AS $key)
        {
            if($this->data[$key] && $this->data[$key] != '')
            {
                $options[] = $key;
                $this->addForceValidKey($key);
            }
        }
        if(count($this->optionValidKey) > 0 && count($options) == 0)
        {
                $data['valid'] = false;
                $data['code'] = 20000;
                $data['errorInfo'][] = join(',', $this->optionValidKey) . ' 字段必须填写一个';
                $data['msg'] = _i($data['code']);
                return $data;
        }
        $msg = array();
        foreach($this->forceValidKey AS $key)
        {
            $rule = $this->get($key);
            if(!$rule)
            {
                $rule['rules'] = array('required' => true);
                $rule['message'] = array('required' => $key .'不能为空');
            }
            $result = $this->validate->verify(
                array($key => isset($this->data[$key]) ? $this->data[$key] : ''),
                array($key => $rule['rules']),
                array($key => $rule['message'])
            );
            if($result !== true){
                $data['valid'] = false;
                $data['code'] = 20000;
                $data['errorInfo'][$key] = $result;
                $msg[] = $result;
            }
        }
        if(count($msg) > 0)
        {
            $data['msg'] = join("\n", $msg);
        }
        return $data;
    }
    /**
     * 验证数据，验证将返回数据及验证结果
     * @return bool
     */
     /*
    public function verify()
    {
        $data = array();
        $data['data'] = array();
        $valid = _loadClassOnce('Qii\Library\Validate');
        foreach($this->data AS $key => $val)
        {
            $rule = $this->get($key);
            $data['data'][$key] = $val;
            if(empty($rule))
            {
                continue;
            }
            $result = $valid->verify(array($key => $val), array($key => $rule['rules']), array($key => $rule['message']));
            if($result !== true){
                $data['valid'][$key] = $result;
            }
        }
        return $data;
    }*/
    /**
     * 是否在允许的规则内
     * @param string $key 规则名称
     * @return bool
     */
    public function isAllow($key)
    {
        $allow = array(
            'required', 'email', 'idcode', 'http',
            'qq', 'postcode', 'ip', 'phone', 'telephone',
            'mobile', 'en', 'cn', 'account', 'number', 'date',
            'safe', 'password', 'maxlength', 'minlength', 'maxnumber', 'minnumber', 'length',
            'rangeof', 'string', 'sets', 'setsarray', 'float', 'decimal'
        );
        return in_array($key, $allow);
    }

    /**
     * 添加验证
     *
     * @param string $validKey valid
     * @param array $args [['field', 'msg', validExtParam],...], make(length, [['field', 'msg', 10], ...])
     * @return false|void
     */
    public function make($validKey, $args) {
        if(!is_array($args)) {
            return false;
        }
        foreach ($args as $value) {
            if(count($value) == 0) {
                continue;
            }
            if(count($value) == 1) {
                $value[1] = (isset($value[0]) ? $value[0] : '') .'格式不正确';
                $value[2] = true;
            }else if(count($value) == 2) {
                $value[2] = true;
            }
            $this->addRules($value[0], $validKey, $value[2], $value[1]);
        }
    }
    /**
     * 设置表名，主要针对分表的
     */
    public function setTable($table) {
        if(!$table) throw new InvalidParams("table is null");
        $this->table = $table;
    }
    /**
     * 获取当前表名
     *
     * @return void
     */
    public function getTable(){
        if (!$this->table) throw new TableException("table is null");
        return $this->table;
    }
    /**
     * 保存验证
     *
     * @return array
     */
    public function getValidFieldsForSave(){
        return [];
    }
    /**
     * 更新数据验证
     *
     * @return array
     */
    public function getValidFieldsForUpdate(){
        return [];
    }
    /**
     * 快捷添加验证
     * @param string $method 验证方法
     * @param array $args 参数
     * @return mixed
     */
    public function __call($method, $args) {
        preg_match("/add(.*)Fields/", $method, $matches);
        if(!$matches || count($matches) < 2) {
            throw new MethodNotFound($method ." does not found");
        }
        $validKey = strtolower($matches[1]);
        if (!$this->isAllow($validKey)) {
            throw new MethodNotFound($method ." does not found");
        }
        return call_user_func_array(array($this, 'make'), array($validKey, $args));
    }
}