<?php
namespace Qii\Driver;
use Qii\Autoloader\Psr4;
use Qii\Driver\Traits\Cache;
use Qii\Driver\Traits\Database;
use Qii\Exceptions\InvalidFormat;
use Qii\Exceptions\InvalidParams;
use Qii\Exceptions\TableException;
use Qii\Exceptions\Variable;
use Qii\Driver\Traits\SQL;

/**
 * Class Base
 * @package Qii\Driver
 * 使用方法
 * class base extends \Qii\Driver\Model {
 *      public function __construct()
 *      {
 *          parent::__construct();
 *      }
 *      public function demo()
 *      {
 *           $this->db->fields("*")->join(array("leftJoin", array('table' => 'ad_site', 'alias' => 'b', 'on' => 'a.uid = b.uid')))->where(array('a.create_at:greater' => 1))->limit(10)->groupBy(array('a.uid', 'a.create_at'))->orderBy(array('a.uid' => 'desc'))->selectAll('user a');
 *           $this->db->where(array('uid' => 1))->set(array('create_at:plus' => time(), 'status' => 1))->update('user');
 *           $this->db->updateObject('user', array('create_at:plus' => time(), 'status' => 1), array('uid' => 1));
 *           $this->db->where(array('uid:greater' => 1))->delete('user');
 *           $this->db->deleteObject('user', array('uid' => 1));
 *           $this->db->where(array('uid:greater' => 1))->like("email like '%test@test.com%'")->selectAll('user');
 *           $this->db->where(array('uid:greater' => 1))->where("or")->like(array('email' => 'test@testcom'))->selectAll('user');
 *           $this->db->where(array('uid:greater' => 1, 'or', 'email:like' => 'test@test.com'))->selectAll('user');
 *           $this->db->where(array('uid:greater' => 1))->where("OR")->like(array('email' => 'test@test.com'))->selectAll('user');
 *           $this->db->where(array('name' => 'antsnet'))->exclude(array('email' => 'test@test.com', 'status' => 1))->selectAll('user');
 *           $this->db->where(array('name' => 'antsnet'))->orTerms(array('email' => 'test@test.com', 'status' => 1))->selectAll('user');
 *           $this->db->orTerms(array('email' => 'test@test.com', 'status' => 1))->selectAll('user');
 *           $this->db->join(array("leftJoin", array("table" => 'table', 'alias' => 'a', 'on' => 'a.id=b.id')));
 *           $this->db->join(" LEFT JOIN table c on c.id=a.id")->selectAll('use a');
 *           $this->db->where(array('email:unequal' => 'test@test.com'))->in(array('uid:in' => array('1,2,3'), 'status' => array(1)))->selectAll('user');
 *           $this->>db->where(array('uid:in' => '1,2,3', 'email' => 'test@tset.com', 'status' => 1))->selectAll('user');
 *           $this->db->where(array(array('email:unequal' => 'test@test.com'), array('uid:in' => array('1,2,3'), 'status:in' => array(1))))->selectAll('user');
 *      }
 * }
 */
class Base
{
    const VERSION = '1.3';
    use Database;
    use Cache;
    use SQL;
    //load类
    public $load;
    //cache类
    public $cache;
    //语言包
    public $language;
    //响应内容
    public $response;
    /**
     * @var string sql 引用
     */
    public $executeSQL = '';

    public $operateVal = array('or', 'and', 'like');//连接条件操作;
    public $shortExpression = array(
        'equal' => "%s`%s` = '%s'",
        'in' => "%s`%s` in (%s)",
        'unequal' => "%s`%s` != '%s'",
        'greater' => "%s`%s` > '%s'",
        'gt' => "%s`%s` > '%s'",
        'greaterEqual' => "%s`%s` >= '%s'",
        'gte' => "%s`%s` >= '%s'",
        'less' => "%s`%s` < '%s'",
        'lessEqual' => "%s`%s` <= '%s'",
        'lte' => "%s`%s` <= '%s'",
        'like' => "%s`%s` like '%%%s%%'",
        'llike' => "%s`%s` like '%%%s'",
        'rlike' => "%s`%s` like '%s%%'",
        'between' => "%s`%s` between %s",
    );

    public $operateTable = array('leftJoin', 'rightJoin', 'innerJoin');//链接表的操作
    public $faultTolerant = true;

    public $whereCondition = array();
    public $joinCondition = array();

    protected $groupBy;
    protected $orderBy;

    protected $_modelAlias = array('updateRows' => 'update', 'selectRows' => 'selectAll', 'select' => 'selectRow', 'getOne' => 'selectOne', 'getRow' => 'selectRow', 'getAll' => 'selectAll', 'remove' => 'delete', 'deleteRows' => 'delete');


    public function __construct()
    {
        $this->language = Psr4::getInstance()->loadClass('Qii\Language\Loader');
        $this->load = Psr4::getInstance()->loadClass('\Qii\Autoloader\Loader');
        $this->response = new Response();
    }
    /**
     * 获取所有数据
     *
     * @param $table
     * @param null $where
     * @return mixed
     * @throws \Exception
     */
    public function selectAll($table, $where = null)
    {
        if(is_array($where) && count($where) > 0)
        {
            $this->where($where);
        }
        $sql = $this->selectSQL($table);
        return $this->getAll($sql);
    }
    /**
     * 返回resource资源
     * @param string $table 数据表名称
     */
    final function rs($table)
    {
        $sql = $this->selectSQL($table);
        return $this->setQuery($sql);
    }
    /**
     * 查询一行
     * @param $table
     * @param null $where
     * @return array | bool
     * @throws \Exception
     */
    public function selectRow($table, $where = null)
    {
        if(is_array($where) && count($where) > 0)
        {
            $this->where($where);
        }
        $sql = $this->selectSQL($table);
        return $this->getRow($sql);
    }

    /**
     * get row 子类去覆写
     *
     * @param $sql
     * @return false
     */
    public function getRow($sql) {
        return false;
    }
    /**
     *
     * 查询一列
     * @param string $table 表名
     * @return false | array
     * @throws TableException
     */
    final function selectOne($table)
    {
        //验证table是否合法
        if (!$this->verifyTable($table)) {
            if (gettype($table) == 'string') {
                throw new TableException("表名不能包含怪字符且不能以数字开头,获取到的是". $table);
            }
            throw new TableException("表名必须是字符串加下划线,目标字符为". gettype($table));
        }
        $sql = $this->selectSQL($table);
        return $this->getOne($sql);
    }

    /**
     * get one 方法， 子类去覆写
     * @param string $sql
     * @return false | array
     */
    public function getOne($sql) {
        return false;
    }
    /**
     * 插入数据
     * @param $table
     * @param $dataArray
     * @return int
     * @throws InvalidFormat
     */
    final function insertObject($table, $dataArray)
    {
        $sql = $this->insertSQL($table, $dataArray);
        $this->startTime($sql);
        $this->setQuery($sql);
        $this->endTime($sql);
        $this->setError();
        return $this->lastInsertId();
    }
    /**
     *
     * Replace Object
     * @param String $table
     * @param Array|Object $dataArray
     */
    final function replaceObject($table, $dataArray)
    {
        $sql = $this->replaceSQL($table, $dataArray);
        $rs = $this->setQuery($sql);
        $this->setError();
        return $this->AffectedRows($rs);
    }
    /**
     * 更新表中指定数据 结合set、where等方法是用
     *
     * @param string $table
     * @return string
     * @throws \Exception
     */
    public function update($table)
    {
        $sql = $this->updateSQL($table);
        $this->startTime($sql);
        $res = $this->exec($sql);
        $this->endTime($sql);
        return $res;
    }
    /**
     * 更新表的数据，同时可以使用update方法达到同样的效果
     *
     * @param $table
     * @param $dataArray
     * @param array $where [key => $val]
     * @return string
     * @throws \Exception
     */
    final function updateObject($table, $dataArray, $where = array()){
        return $this->set($dataArray)->where($where)->update($table);
    }

    /**
     * 删除表中的数据，结合where方法使用
     *
     * @param string $table 表名
     * @return string
     * @throws \Exception
     */
    final function delete($table)
    {
        $sql = $this->deleteSQL($table);
        $this->startTime($sql);
        $res = $this->exec($sql);
        $this->endTime($sql);
        return $res;
    }

    /**
     * 删除表中数据
     *
     * @param string $table 表名
     * @param null $where
     * @return string
     * @throws \Exception
     */
    final function deleteObject($table, $where = null)
    {
        if(!$table) {
            throw new InvalidParams(_i('%s is invalid', '表名'), __LINE__);
        }
        return $this->where($where)->delete($table);
    }

    /**
     * set 字段加
     * @param $set
     * @return $this
     */
    public function upsetCounter($set)
    {
        if(!is_array($set))
        {
            if(!empty($set))
            {
                $this->sets[] = $set;
            }
            return $this;
        }
        if(count($set) == 0) {
            return $this;
        }
        foreach ($set as $key => $val) {
            if(substr($key, -5) == ':plus') {
                continue;
            }
            $set[$key . ":incr"] = $val;
            unset($set[$key]);
        }
        return $this->set($set);
    }

    /**
     * set 字段减
     * @param $set
     * @return $this
     */
    public function downsetCounter($set)
    {
        if(!is_array($set)) {
            if(!empty($set)) {
                $this->sets[] = $set;
            }
            return $this;
        }
        if(count($set) == 0) {
            return $this;
        }
        foreach ($set as $key => $val) {
            if(substr($key, -6) == ':minus') {
                continue;
            }
            $set[$key . ":decr"] = $val;
            unset($set[$key]);
        }
        return $this->set($set);
    }

    /**
     * OR 条件
     * 用法:
     *  1:
     *  $this->orTerms(['field1:like|equal....' => val1])->orTerms(....)
     *  $this->orTerms(['field1:field2...:like|equal....' => val1])
     *  == (field1 like|equal.... val1) or (field2 like|equal.... val1) ....
     *  2:
     * $this->orTerm(['field1:like|equal...' => val1, 'field2:like|equal...' => val1])
     * == (field1 like|equal.... val1 AND field2 like|equal.... val1 ....)
     * @param $where
     * @param string $defaultOperater
     * @param null $res
     * @return $this
     * @throws \Exception
     */
    public function orTerms($where)
    {
        return $this->or($where);
    }

    /**
     * in 条件
     * @param $where
     * @param $defaultOperater
     * @param $res
     * @return $this
     * @throws \Exception
     */
    public function in($where, $defaultOperater = 'or', &$res = null)
    {
        if(!is_array($where))
        {
            if($where != '') {
                if(!empty($this->whereCondition) && !$this->isOperator($where)) {
                    $this->whereCondition[] = $defaultOperater;
                }
                $this->whereCondition[] = "(". $where . ")";
            }
            return $this;
        }
        if(count($where) == 0) {
            return $this;
        }

        foreach ($where as $key => $val) {
            if(substr($key, - 3) == ':in') {
                continue;
            }
            $where[$key . ":in"] = $val;
            unset($where[$key]);
        }
        return $this->where($where);
    }

    /**
     * 不包含
     *
     * @param mix $where 条件
     * @return $this
     */
    public function exclude($where)
    {
        if(!is_array($where))
        {
            return $this->where($where);
        }
        if(count($where) == 0) {
            return $this;
        }
        foreach($where as $key => $value) {
            if($this->getFieldOperator($key)['operator'] == '') {
                $where[$key . ":neq"] = $value;
                unset($where[$key]);
            }
        }
        return $this->where($where);
    }

    /**
     * left join
     * @param $table
     * @param $on
     * @return $this
     * @throws \Exception
     */
    final function leftJoinObject($table, $on)
    {
        return $this->join($table, $on);
    }

    /**
     * GROUP BY
     * @param mixed $group
     * @return $this
     */
    public function groupBy($group)
    {
        $this->group($group);
        return $this;
    }

    /**
     * Order by
     *
     * @param array|string $order
     * @return $this
     * @throws \Exception
     */
    public function orderBy($order)
    {
        $this->order($order);
        return $this;
    }

    /**
     * Order by sql
     *
     * @param string $orderBy
     * @return $this
     * @throws \Exception
     */
    public function orderByStr($orderBy)
    {
        return $this->order($orderBy);
    }
    /**
     * 清空Data数据
     */
    final public function cleanCondition()
    {
        $this->clean();
    }

    /**
     * 获取错误码
     */
    final public function getCode()
    {
        return $this->response->getCode();
    }

    /**
     * 获取错误信息
     */
    final public function getMessage()
    {
        if ($this->response->isError()) {
            return $this->response->getMessage();
        }
    }

    /**
     * 返回response对象
     *
     * @return Response
     */
    public function getResponse()
    {
        return $this->response;
    }

    /**
     * 转换字符创
     *
     * @param $str
     * @return array|string
     */
    final public function iconv($str)
    {
        if (is_array($str)) {
            return array_map(function ($n) {
                return toUtf8($n);
            }, $str);
        }
        return toUtf8($str);
    }

    /**
     * 执行Model过程中保存的相关信息
     *
     * @param string $option
     * @return mixed
     */
    final function querySQL($option = '')
    {
        $allow = array('_queryTimes', '_querySeconds', '_errorInfo', '_exeSQL');
        if (in_array($option, $allow)) {
            return $this->{$option};
        }
        return 0;
    }

    /**
     * 获取执行的sql
     *
     * @param string $sql
     * @return $this
     */
    public function fetchSql(&$sql) {
        $this->executeSQL = &$sql;
        return $this;
    }
    /**
     * 获取操作符
     *
     * @param string $field 字段
     * @return array
     */
    protected function getFieldOperator($field)
    {
        $operator = explode(":", $field);
        if(count($operator) == 1) {
            return array('operator' => '', 'field' => $field);
        }
        $operate = array_pop($operator);
        $field = join(":", $operator);

        return array('operator' => $operate, 'field' => $field);
    }
    /**
     * 获取字段别名
     *
     * @param string $name
     * @param string $connector
     * @return array
     */
    protected function getFieldAlias($name, $connector = ".")
    {
        $aliases = explode('.', $name, 2);
        if(count($aliases) == 1) {
            return array('alias' => '', 'name' => $name);
        }

        return array('alias' => $aliases[0] . $connector, 'name' => join(".", array_slice($aliases, 1)));
    }

    /**
     * 把条件根据『链接字符串,『字段:值』』等内容分组
     *
     *
     * $whereGroup = array(
     *   ['id' => 1, 'name' => '名字'],
     *   'or',
     *   ['status' => 1, 'create_at:greater' => '22222'],//
     *   'or',//链接字段与字段之间的操作符
     *   'like',//使用在字段与值之间的操作符，可以省略
     *   'and',//字段1与字段2之间的操作符
     *   'or',//字段2与字段3之间的操作符
     *   '...',//字段n与字段n+1之间的操作符
     *   //字段n与字段n+1之间的操作符可以省略
     *   //子数组之间的操作符优先于上边的操作符
     *   ['email' => 'antsnet@163.com', "and", 'nickname' => 'antsnet'],
     *   ['uid' => 1],
     *   'and',
     *   ['name' => 'ss'],
     *   'and',
     *   'and',
     *   'update_at=1',
     *   );
     * @param $group
     * @return array [['or/and', 'like/equal/...', ['字段1' => '值1', '字段2' => '值2']],[...]]
     * @throws \Exception
     */
    protected function groupContents($group)
    {
        $slices = array();
        //如果是一维数组就直接操作值，不检查操作符
        if(count($group) == count($group, 1)) {
            $slices[] = array($group);
            return $slices;
        }
        $count = count($group);
        $tmpWhere = array();
        //最后一次是否是赋值
        $lastIsValue = null;
        foreach($group as $index => $val)
        {
            //in比较特殊，它可以是字符串，可以是数组，结果是数组就单独处理不走操作符的处理逻辑
            if(substr($index, -3)  == ':in' && is_array($val)) {
                $tmpWhere[$index] = $val;
                continue;
            }
            if(stristr($index, ':between') !== false) {
                if(is_array($val)) {
                    $val = $this->setQuote($val);
                    $val = [$index => "'". join("' and '", $val) ."'"];
                }
            }
            if(is_array($val)) {
                // group array 的情况，每组条件之间加 and
                $slices[] = $this->groupContents($val)[0];
                if($index < $count - 1) {
                    $slices[] = 'and';
                }
                continue;
            }
            $isOperator = $this->isOperator($val);//如果是操作符，上一个不是操作符就清空tmpWhere，并放入slices
            $isValue = !$isOperator;
            if($lastIsValue && $isOperator)
            {
                $slices[] = array_values($tmpWhere);
                $tmpWhere = array();
                $tmpWhere[$index] = $val;
            }
            else if($count - 1 == $index)
            {
                $tmpWhere[$index] = $val;
                print_r($tmpWhere);
                $slices[] = array_values($tmpWhere);
                $tmpWhere = array();
            }
            else if($isOperator){
                $slices[][] = $val;
            }
            else {
                $tmpWhere[$index] = $val;
            }
            $lastIsValue = $isValue;
        }
        // @todo 待测试
        if(count($tmpWhere) > 0) {
            //作为独立 slices 的条件
            if(count($slices) > 0) {
                $slices[] = "and";
            }
            $slices[][] = $tmpWhere;
        }
        return $slices;
    }
    /**
     * 判断是否是操作符号
     *
     * @param mixed $val 值
     * @return bool
     */
    final public function isOperator($val)
    {
        if(is_array($val)) {
            return false;
        }
        if(!is_array($val)) {
            if(in_array(gettype($val), array("array", "object", "resource","resource (closed)"))) {
                throw new \Exception("期待参数为字符串，获取到的为: " . gettype($val) ."(". json_encode($val) . ")");
            }
            $val = strtolower($val);
        }
        return in_array($val, $this->operateVal) || in_array($val, $this->operateTable);
    }

    /**
     * 判断是否是表达式 equal unequal ...
     *
     * @param string $val
     * @return bool
     * @throws \Exception
     */
    protected function isExpression($val) {
        if(is_array($val)) {
            return false;
        }
        if(in_array(gettype($val), array("array", "object", "resource","resource (closed)"))) {
            throw new \Exception("期待参数为字符串，获取到的为: " . gettype($val) ."(". json_encode($val) . ")");
        }

        return isset($this->shortExpression[$val]);
    }

    /**
     * 是否是操作表
     *
     * @param string $val 值
     * @return bool
     */
    protected function isOperateTable($val)
    {
        if(is_array($val)) {
            return false;
        }
        return in_array($val, $this->operateTable);
    }

    /**
     * 处理table join
     * @param array $slices [['leftJoin', ['table' => '表名', 'alias' => 'a', 'on' => 'a.id = b.id']]]
     * @param string $defaultJoin
     * @param null $res
     * @return $this
     * @throws \Exception
     */
    protected function handleTableJoin($slices, $defaultJoin = 'leftJoin', &$res = null)
    {
        foreach ($slices as $v)
        {
            if(count($v) == 0) {
                throw new \Exception('连接表操作失败，格式:"leftJoin/rightJoin/innerJoin", ["table" => "表名", "alias" => "b", "on" => "a.id=b.id"]', __LINE__);
            }
            if(count($v) == 1 && !$this->isOperateTable($v[0]))
            {
                array_unshift($v, $defaultJoin);
            }
            $joinOperator = array(
                "leftJoin" => " LEFT JOIN %s ON %s",
                "rightJoin" => " RIGHT JOIN %s ON %s",
                "innerJoin" => " INNER JOIN %s ON %s"
            );
            if(!$this->isOperateTable($v[0]))
            {
                throw new \Exception('连接表操作失败，格式:"leftJoin/rightJoin/innerJoin", ["table" => "表名", "alias" => "b", "on" => "a.id=b.id"]', __LINE__);
            }

            $joinCondition = sprintf($joinOperator[$v[0]], $v[1]['table'] . ' '. $v[1]['alias'], $v[1]['on']);
            if(!empty($this->join) && is_array($this->join))
            {
                $this->joinCondition = array_merge($this->join, array($joinCondition));
            }
            else
            {
                $this->joinCondition[] = $joinCondition;
            }
            $res[] = $joinCondition;
        }
        unset($joinCondition);
        return $this;
    }

    /**
     * 验证数据表名是否符合规范
     * 表名不能以数字开头，
     *
     * @param string $name
     * @return bool
     */
    final public function verifyTable($name) {
        if (!is_string($name) || (
            // table
           !preg_match("/^[a-zA-Z_]+[a-zA-Z0-9_]{0,}$/", $name) &&
           // `table`
           !preg_match("/^`[a-zA-Z_]+[a-zA-Z0-9_]{0,}`$/", $name)) &&
            // table alias
            !preg_match("/^[a-zA-Z_]+[a-zA-Z0-9_]{0,}\s+[a-zA-Z]+[a-zA-Z0-9_]{0,}$/", $name) &&
            // `table` alias
            !preg_match("/^`[a-zA-Z_]+[a-zA-Z0-9_]{0,}`\s+[a-zA-Z]+[a-zA-Z0-9_]{0,}$/", $name) &&
            // database.table
            !preg_match("/^[a-zA-Z]+\.[a-zA-Z]+[a-zA-Z0-9_]{0,}$/", $name) &&
            !preg_match("/^[a-zA-Z]+\.[a-zA-Z]+[a-zA-Z0-9_]{0,}\s+[a-zA-Z]+[a-zA-Z0-9_]{0,}$/", $name)
        ) {
            return false;
        }
        return true;
    }
    /**
     * 如果不存在指定的方法则调用提示错误
     *
     * @param string $method
     * @param mixd $args
     * @return mixed
     */
    public function __call($method, $args)
    {
        if (isset($this->_modelAlias[$method])) {
            if (method_exists($this, $this->_modelAlias[$method])) {
                return call_user_func_array(array($this, $this->_modelAlias[$method]), $args);
            }
            return \Qii::setError(false, __LINE__, 1506, 'Alias ' . get_called_class() . '->' . $method . '() not found');
        }
        return \Qii::setError(false, __LINE__, 1506, get_called_class() . '->' . $method . '() not found');
    }
}