<?php namespace Dever\Data;

use Dever;
use Dever\Loader\Config;
use Dever\Loader\Project;
use Dever\Loader\Import;
use Dever\Output\Export;
use Dever\Routing\Input;
use Dever\String\Helper;

class Model
{
    /**
     * config
     *
     * @var array
     */
    public $config;

    /**
     * data
     *
     * @var array
     */
    protected $data;

    /**
     * index
     *
     * @var string
     */
    protected $index;

    /**
     * table
     *
     * @var string
     */
    protected $table;

    /**
     * API
     *
     * @var string
     */
    const DATABASE = 'database/';

    /**
     * instance
     *
     * @var string
     */
    protected static $instance;

    /**
     * key
     *
     * @var string
     */
    protected static $key;

    /**
     * upinto
     *
     * @return mixed
     */
    public static function upinto($table = '', $where = array(), $param = array(), $update = true)
    {
        $db = self::load($table);
        $where['clear'] = true;
        $info = $db->one($where);
        if ($info && $update) {
            if ($param) {
                $param['where_id'] = $info['id'];
                $db->update($param);
            }
            
            $id = $info['id'];
        } else {
            $param = array_merge($where, $param);
            $id = $db->insert($param);
        }

        return $id;
    }

    /**
     * load
     *
     * @return mixed
     */
    public static function load($table = '', $param = '', $key = '')
    {
        if (is_array($table)) {
            print_r($table);die;
        }
        list($table, $index, $method) = self::table($table);
        $key = $table . '_' . $index . '_' . $key;
        if (empty(static::$instance[$key])) {
            static::$instance[$key] = new static($table, $index);
        }

        if ($method) {
            return static::$instance[$key]->$method($param);
        }

        return static::$instance[$key];
    }

    /**
     * closeAll
     *
     * @return mixed
     */
    public static function closeAll()
    {
        if (static::$instance) {
            foreach (static::$instance as $k => $v) {
                static::$instance[$k] = null;
                unset(static::$instance[$k]);
            }
        }
    }


    /**
     * table
     * @param array $param
     *
     * @return array
     */
    public static function table($table)
    {
        $index = $method = '';
        if (is_array($table)) {
            print_r($table);die;
        }
        if (!$table) {
            $table = self::$key;
        } elseif (strpos($table, '/') === false) {
            $index = $table;
            $table = self::$key;
        }
        self::$key = $table;
        if (strpos($table, '-')) {
            list($table, $method) = explode('-', $table);
        }
        if (strpos($table, ':')) {
            list($table, $index) = explode(':', $table);
        }

        return array($table, $index, $method);
    }

    /**
     * __construct
     *
     * @return mixed
     */
    public function __construct($table, $index = '')
    {
        $this->init($table, $index);
    }

    /**
     * init
     *
     * @return mixd
     */
    protected function init($table, $index = '')
    {
        $this->table = $table;
        $this->index = $index;
        if (!$this->table) {
            $this->config['name'] = 'table';
            $this->config['project'] = '';
            $this->config['db'] = '';
        } else {
            $this->loadConfig();
        }
    }

    /**
     * loadConfig
     *
     * @return mixd
     */
    protected function loadConfig()
    {
        list($projectName, $tableName) = explode('/', $this->table);
        $projectInfo = Project::load($projectName);
        if (!$projectInfo) {
            $projectInfo = array (
                'name' => $projectName,
            );
            $path = '';
            $this->config['link'] = true;
        } else {
            $path = $projectInfo['path'] . self::DATABASE;
            $this->config['link'] = false;
        }

        if ($tableName == 'col') {
            //print_r($this->table);die;
        }

        if ($this->index) {
            $path .= $this->index . '/';
            $this->config['db_prefix'] = $this->config['db'] = $this->index;
        } else {
            $this->config['db'] = $projectInfo['name'];
        }

        /*
        if (strstr($tableName, '__')) {
            $temp = explode('__', $tableName);
            $file = $path . $temp[0] . '.php';
        } else {
            $file = $path . $tableName . '.php';
        }
        */

        $file = $path . $tableName . '.php';
        
        if (is_file($file)) {
            $config = include $file;
            # 这里用到了复制表，但不好用，暂时关闭
            /*
            if (strstr($tableName, '__') && isset($config['manage']['lang'])) {
                if (isset($config['struct'])) {
                    $config['manage']['lang'] = explode(',', $config['manage']['lang']);
                    foreach ($config['struct'] as $k => $v) {
                        if ($k != 'id' && !in_array($k, $config['manage']['lang'])) {
                            unset($config['struct'][$k]);
                        }
                    }
                }
            }*/
            if (isset($config['struct']) && isset($config['manage']['lang']) && $config['manage']['lang'] && Dever::config('base')->lang_set) {
                $config['manage']['lang'] = explode(',', $config['manage']['lang']);
                foreach ($config['manage']['lang'] as $v) {
                    if (isset($config['struct'][$v])) {
                        foreach (Dever::config('base')->lang_set as $k1 => $v1) {
                            if (Dever::config('base')->lang != $k1) {
                                $config['struct'][$k1 . '_' . $v] = $config['struct'][$v];
                            }
                        }
                    }
                }
            }
            $this->config = array_merge($this->config, $config);
            /*
            if (strstr($tableName, '__')) {
                $this->config['name'] = $tableName;
            }*/
        } else {
            $this->config['name'] = $tableName;
        }
        $this->config['project'] = $projectInfo;
    }

    /**
     * db
     *
     * @return mixd
     */
    private function db()
    {
        return Model\Database::get($this->config['db']);
    }

    /**
     * query
     *
     * @return mixd
     */
    public function query($sql, $data = array(), $method = '')
    {
        if (strstr($sql, '{project}')) {
            if (isset($this->config['struct'])) {
                $sql = Helper::replace('{project}', $this->config['project']['name'], $sql);
            } else {
                $sql = Helper::replace('{project}_', '', $sql);
            }
        }
        $this->sql = Helper::replace('{table}', $this->getTableName(), $sql);
        return $this->db()->exe($this->sql, $data, $method);
    }

    /**
     * getTableName
     *
     * @return mixd
     */
    private function getTableName()
    {
        $prefix = '';
        if (defined('DEVER_PROJECT')) {
            $prefix = DEVER_PROJECT . '_';
        }
        if (isset($this->config['struct'])) {
            return $prefix . $this->config['project']['name'] . '_' . $this->config['name'];
        } else {
            return $this->config['name'];
        }
    }

    /**
     * fetch
     *
     * @return mixd
     */
    public function fetch($sql, $data = array(), $cache = false)
    {
        return $this->fetchAll($sql, $data, false, $cache, 'fetch');
    }

    /**
     * setTable
     *
     * @return mixd
     */
    private function setTable($sql)
    {
        $table = $sql;
        //$this->db()->setTable($table);
    }

    /**
     * fetchAll
     *
     * @return mixd
     */
    public function fetchAll($sql, $data = array(), $page = array(), $cache = false, $method = 'fetchAll')
    {
        $this->setTable($sql);

        if (!$cache && !empty(Config::get('cache')->mysql)) {
            $cache = 'getSql_' . md5($sql . serialize($data));
            if ($page && $p = Input::get('page')) {
                $cache .= '_p' . $p;
            }
        }

        if ($cache) {
            $result = $this->db()->cache($cache);
            if ($result) {
                return $result;
            }
        }

        if ($page) {
            $result = $this->page($page, $sql, $data);
        } else {
            $result = $this->query($sql, $data);
            if ($method) {
                $result = $result->$method();
            }
        }
        
        if ($cache) {
            $this->db()->cache($cache, $result);
        } else {
            $this->db()->log($this->sql, $data, $result);
        }

        return $result;
    }

    /**
     * rowCount
     *
     * @return mixd
     */
    public function rowCount($sql, $data = array())
    {
        return $result = $this->query($sql, $data)->rowcount();
    }

    /**
     * lastId
     *
     * @return mixd
     */
    public function lastId($sql, $data = array())
    {
        return $result = $this->query($sql, $data)->id();
    }

    /**
     * page
     *
     * @return array
     */
    public function page($config = array(), $sql = false, $data = array())
    {
        return $this->db()->getPageBySql($config, $sql, $data, $this);
    }

    /**
     * index
     *
     * @return array
     */
    public function index($index)
    {
        return $this->db()->index($index);
    }

    /**
     * transaction begin
     *
     * @return array
     */
    public function begin()
    {
        return $this->db()->begin();
    }

    /**
     * transaction commit
     *
     * @return array
     */
    public function commit()
    {
        return $this->db()->commit();
    }

    /**
     * transaction rollback
     *
     * @return array
     */
    public function rollback()
    {
        return $this->db()->rollback();
    }

    /**
     * __call
     *
     * @return mixd
     */
    public function __call($method, $param)
    {
        if (isset($param[0][0]) && is_array($param[0][0])) {
            $result = array();
            foreach ($param[0] as $k => $v) {
                $result[] = $this->$method($v);
            }
            
            return $result;
        }

        $call = false;
        if (isset($param[1])) {
            if (is_object($param[1])) {
                $call = $param[1];
            } elseif ($method == 'insert') {
                $param[0]['insert_value_num'] = $param[1];
            }
        }

        $param = $param ? $this->initParam($param[0], $method) : array();

        $key = $this->table . $method . md5(serialize($param));
        if (isset($param['clear']) && $param['clear'] && isset($this->data[$key])) {
            unset($this->data[$key]);
        }

        $this->compatible($key, $method, $param);

        if (!isset($this->data[$key])) {
            $this->request($method, $param);
            if (isset($this->config['request'][$method]['type']) && in_array($this->config['request'][$method]['type'], array('update', 'insert'))) {
                return $this->callData($method, $param, $call);
            }
            $this->data[$key] = $this->callData($method, $param, $call);
        }

        return $this->data[$key];

    }

    /**
     * getData
     *
     * @return mixd
     */
    private function callData($method, $param, $call = false)
    {
        $param = $this->search($method, $param);
        $handle = new Model\Handle($method, $this->config, $param, $call);
        return $handle->get();
    }

    /**
     * compatible
     *
     * @return mixd
     */
    protected function compatible($key, $method, $param)
    {
        if (Config::get('database')->compatible && isset($this->config['project'])) {
            $file = $this->config['project']['path'] . 'database/' . ucfirst(Config::get('database')->compatible) . '/' . ucfirst($this->config['name']) . '.php';
            if (is_file($file)) {
                $class = ucfirst($this->config['project']['name']) . '\\Database\\' . ucfirst(Config::get('database')->compatible) . '\\' . ucfirst($this->config['name']);
                if (class_exists($class) && method_exists($class, $method)) {
                    $this->data[$key] = $class::$method($param);
                }
            }
        }
    }

    /**
     * request
     *
     * @return mixd
     */
    protected function request($method, $param)
    {
        if (!isset($this->config['struct'])) {
            $this->config['struct'] = array();
        }
        if (empty($this->config['request'][$method]) && isset($this->config['struct'])) {
            $search = '';
            if (isset($param['search_type'])) {
                $search = $param['search_type'];
            }

            $this->config['request'][$method] = Model\Request::get($this->table, $method, $this->config['struct'], $search);

            if ($method == 'list' && isset($this->config['manage']['list_type']) && $this->config['manage']['list_type']) {
                $this->config['request']['list']['page'][0] = 50000;
            }


            if (isset($this->config['request'][$method]['type']) && isset($this->config['request'][$method]['option']) && $this->config['request'][$method]['type'] == 'all' && isset($this->config['request'][$method . '_option'])) {

                $this->config['request'][$method]['option'] = array_merge($this->config['request'][$method]['option'], $this->config['request'][$method . '_option']);
            }
        }

        if (isset($param['config']) && isset($this->config['request'][$method]['config']) && $this->config['request'][$method]['config']) {
            $this->config['request'][$method] = array_merge($this->config['request'][$method], $param['config']);
            unset($param['config']);
            unset($this->config['request'][$method]['config']);
        }

        if (in_array($method, array('all', 'state', 'total', 'list', 'find')) && isset($param['option'])) {
            foreach ($param['option'] as $k => $v) {
                $this->config['request'][$method]['option'][$k] = $v;
            }
        }
        if ($method == 'list') {
            $search = Dever::search_button();
            if ($search) {
                $this->config['request']['list']['group'] = $search[0];
                $this->config['request']['list']['col'] = $search[1];
            }
        }

        if (isset($param['join'])) {
            $this->config['request'][$method]['join'] = $param['join'];
            if (isset($this->config['request'][$method]['option'])) {
                foreach ($this->config['request'][$method]['option'] as $k => $v) {
                    if (is_string($v) && !strstr($v, 't_1') && !strstr($k, '-')) {
                        $this->config['request'][$method]['option'][$k] = 'yes-t_1.' . $k;
                    }
                    if (is_array($v) && !strstr($v[0], 't_1')) {
                        if (strstr($v[0], '-')) {
                            $temp = explode('-', $v[0]);
                            $this->config['request'][$method]['option'][$k][0] = 'yes-t_1.' . $temp[1];
                        } else {
                            $this->config['request'][$method]['option'][$k][0] = 'yes-t_1.' . $k;
                        }
                    }
                }
            }
        }

        if (DEVER_APP_NAME != 'manage') {
            $lang = Dever::langConfig();
            if (Dever::config('base')->lang && $lang && $lang != Dever::config('base')->lang && isset($this->config['manage']['lang']) && $this->config['manage']['lang']) {
                $col = array();
                foreach ($this->config['manage']['lang'] as $k => $v) {
                    $col[] = $lang . '_' . $v . ' as `' . $v . '`';
                }
                if (!isset($this->config['request'][$method]['col'])) {
                    $this->config['request'][$method]['col'] = '*';
                }
                $this->config['request'][$method]['col'] .= ',' . implode(',', $col);
            }
        }
    }

    /**
     * search
     *
     * @return mixd
     */
    protected function search($method, $param)
    {
        if (isset($param['search_type']) && $param['search_type'] == 2) {
            $join = array();
            $i = 2;
            foreach ($param as $k => $v) {
                if (strpos($k, '-') !== false) {
                    $k = str_replace(array('option_', 'where_'), '', $k);
                    if (isset($this->config['struct'][$k]) && isset($this->config['struct'][$k]['sync'])) {
                        $temp = explode('-', $k);
                        $join[] = array
                        (
                            'table' => $temp[0] . '/' . $temp[1],
                            'type' => 'left join',
                            'on' => $this->config['struct'][$k]['sync'],
                        );

                        $t = 't_' . $i . '.' . $temp[2];
                        $this->config['request'][$method]['option'][$t] = $this->config['request'][$method]['option'][$k];
                        $param['option_' . $t] = $param['option_' . $k];
                        unset($this->config['request'][$method]['option'][$k]);
                        unset($param['option_' . $k]);
                        $i++;
                    }
                }
            }
            if ($join) {
                $this->config['request'][$method]['join'] = $join;
            }
        }

        return $param;
    }

    protected function searchFulltext($k, $param)
    {

    }

    /**
     * initParam
     *
     * @return mixd
     */
    private function initParam($param, $method)
    {
        if ($param && !is_array($param)) {
            if ($this->config['link']) {
                $param = array('id' => $param);
            } else {
                $param = array('where_id' => $param, 'option_id' => $param);
            }
        }
        return $param;
    }
}
