<?php
/**
 * Base.class.php
 * 基类
 *
 * @license    http://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt LGPL
 * @copyright  Copyright (c) 2014 - ? VcySDK (http://www.vchangyi.com/)
 * @author     zhuxun37
 * @version    1.0.0
 */

namespace VcySDK;

abstract class Base
{

    /**
     * 接口调用成功时的 code 值
     *
     * @var string
     */
    const SUCCESS_CODE = 'SUCCESS';

    /**
     * 全局 UC RequestId
     * @param string $requestId
     * @return string
     */
    public function allRequestId($requestId = '')
    {
        static $ucRequestId;
        if (empty($ucRequestId) && !empty($requestId)) {
            $ucRequestId = $requestId;
        }

        return empty($ucRequestId) ? '' : $ucRequestId;
    }

    /**
     * 修改配置
     *
     * @param array $config 配置数组
     *
     * @return boolean
     */
    public function setConfig($config = array())
    {

        return Config::instance()->setConfig($config);
    }

    /**
     * 返回业务所需数据
     *
     * @param array $data 接口返回数据
     *
     * @throws Exception
     * @return mixed
     */
    public function returnData($data)
    {

        return isset($data['data']) ? $data['data'] : '';
    }

    /**
     * 创建一个支付 Api Url
     *
     * @param string $url Url地址
     *
     * @return bool
     * @throws \VcySDK\Exception
     */
    public function generateApiUrlPay($url)
    {

        // 获取参数数组
        $params = func_get_args();
        // 从配置中获取 Url 相关数据
        $paths = array();
        // 获取域名
        if (!$this->getApiUrl($paths[])) {
            throw new Exception(Error::API_URL_EMPTY);
            return false;
        }

        $paths[] = 'pay';
        // 获取企业账号
        if (!$this->getEnumber($paths[])) {
            throw new Exception(Error::ENUMBER_EMPTY);
            return false;
        }

        // 获取应用标识
        if (!$this->getPluginIdentifier($paths[])) {
            throw new Exception(Error::PLUGIN_IDENTIFIER_EMPTY);
            return false;
        }

        // 获取第三方标识
        if (!$this->getThirdIdentifier($paths[])) {
            throw new Exception(Error::THIRD_IDENTIFIER_EMPTY);
            return false;
        }
        $params[] = implode('/', $paths);

        return $this->generateApiUrl($params);
    }

    /**
     * 创建一个 Api Url
     *
     * @param array $params Url 相关参数, 第一个值为 Url 格式字串; 最后一个值为主 Url 部分
     *
     * @return boolean
     */
    public function generateApiUrl($params)
    {

        // 获取 format 字串
        $url = $params[0];
        $urlPath = array_pop($params);
        // 切出所有 sprintf 参数
        $params = array_slice($params, 1);
        array_unshift($params, $url, $urlPath);

        // 调用 sprintf 方法
        return call_user_func_array('sprintf', $params);
    }

    /**
     * 创建一个系统 Api Url
     *
     * @param string $url Url地址
     *
     * @return boolean
     * @throws \VcySDK\Exception
     */
    public function generateApiUrlS($url)
    {

        // 获取参数数组
        $params = func_get_args();
        // 从配置中获取 Url 相关数据
        $paths = array();
        // 获取域名
        if (!$this->getApiUrl($paths[])) {
            throw new Exception(Error::API_URL_EMPTY);
            return false;
        }
        $paths[] = 's';
        $params[] = implode('/', $paths);

        return $this->generateApiUrl($params);
    }

    /**
     * 创建一个系统 Api Url
     *
     * @param string $url Url地址
     *
     * @return boolean
     * @throws \VcySDK\Exception
     */
    public function generateApiUrlSP($url)
    {

        // 获取参数数组
        $params = func_get_args();
        // 从配置中获取 Url 相关数据
        $paths = array();
        // 获取域名
        if (!$this->getApiUrl($paths[])) {
            throw new Exception(Error::API_URL_EMPTY);
            return false;
        }
        $paths[] = 's';

        // 获取第三方标识
        if (!$this->getThirdIdentifier($paths[])) {
            throw new Exception(Error::THIRD_IDENTIFIER_EMPTY);
            return false;
        }

        $params[] = implode('/', $paths);

        return $this->generateApiUrl($params);
    }

    /**
     * 创建一个企业 Api Url
     *
     * @param string $url Url地址
     *
     * @return bool
     * @throws \VcySDK\Exception
     */
    public function generateApiUrlE($url)
    {

        // 获取参数数组
        $params = func_get_args();
        // 从配置中获取 Url 相关数据
        $paths = array();
        // 获取域名
        if (!$this->getApiUrl($paths[])) {
            throw new Exception(Error::API_URL_EMPTY);
            return false;
        }

        $paths[] = 'b';
        // 获取企业账号
        if (!$this->getEnumber($paths[])) {
            throw new Exception(Error::ENUMBER_EMPTY);
            return false;
        }
        $params[] = implode('/', $paths);

        return $this->generateApiUrl($params);
    }

    /**
     * 创建一个企业 Api Url
     *
     * @param string $url Url地址
     *
     * @return bool
     * @throws \VcySDK\Exception
     */
    public function generateApiUrlB($url)
    {

        // 获取参数数组
        $params = func_get_args();
        // 从配置中获取 Url 相关数据
        $paths = array();
        // 获取域名
        if (!$this->getApiUrl($paths[])) {
            throw new Exception(Error::API_URL_EMPTY);
            return false;
        }

        $paths[] = 'b';
        // 获取企业账号
        if (!$this->getEnumber($paths[])) {
            throw new Exception(Error::ENUMBER_EMPTY);
            return false;
        }

        // 获取第三方标识
        if (!$this->getThirdIdentifier($paths[])) {
            throw new Exception(Error::THIRD_IDENTIFIER_EMPTY);
            return false;
        }
        $params[] = implode('/', $paths);

        return $this->generateApiUrl($params);
    }

    /**
     * 创建一个应用渠道 Api Url
     *
     * @param string $url 获取接口Url
     *
     * @return boolean
     * @throws \VcySDK\Exception
     */
    public function generateApiUrlA($url)
    {

        // 获取参数数组
        $params = func_get_args();
        // 从配置中获取 Url 相关数据
        $paths = array();
        // 获取域名
        if (!$this->getApiUrl($paths[])) {
            throw new Exception(Error::API_URL_EMPTY);
            return false;
        }

        $paths[] = 'a';
        // 获取企业账号
        if (!$this->getEnumber($paths[])) {
            throw new Exception(Error::ENUMBER_EMPTY);
            return false;
        }

        // 获取应用标识
        if (!$this->getPluginIdentifier($paths[])) {
            throw new Exception(Error::PLUGIN_IDENTIFIER_EMPTY);
            return false;
        }

        // 获取第三方标识
        if (!$this->getThirdIdentifier($paths[])) {
            throw new Exception(Error::THIRD_IDENTIFIER_EMPTY);
            return false;
        }
        $params[] = implode('/', $paths);

        return $this->generateApiUrl($params);
    }

    /**
     * 创建一个视频 Api Url
     *
     * @param $url
     *
     * @return bool
     * @throws \VcySDK\Exception
     */
    public function generateApiUrlALive($url)
    {

        // 获取参数数组
        $params = func_get_args();
        // 从配置中获取 Url 相关数据
        $paths = array();
        // 获取域名
        if (!$this->getApiUrl($paths[])) {
            throw new Exception(Error::API_URL_EMPTY);
            return false;
        }

        $paths[] = 'live';
        $paths[] = 'a';
        // 获取企业账号
        if (!$this->getEnumber($paths[])) {
            throw new Exception(Error::ENUMBER_EMPTY);
            return false;
        }

        // 获取应用标识
        if (!$this->getPluginIdentifier($paths[])) {
            throw new Exception(Error::PLUGIN_IDENTIFIER_EMPTY);
            return false;
        }

        $params[] = implode('/', $paths);

        return $this->generateApiUrl($params);
    }

    /**
     * 创建一个资源类 Api Url
     *
     * @param $url
     *
     * @return bool
     * @throws \VcySDK\Exception
     */
    public function generateApiUrlAtt($url)
    {

        // 获取参数数组
        $params = func_get_args();
        // 从配置中获取 Url 相关数据
        $paths = array();
        // 获取域名
        if (!$this->getApiUrl($paths[])) {
            throw new Exception(Error::API_URL_EMPTY);
            return false;
        }

        $paths[] = 'b';
        // 获取企业账号
        if (!$this->getEnumber($paths[])) {
            throw new Exception(Error::ENUMBER_EMPTY);
            return false;
        }
        $params[] = implode('/', $paths);

        return $this->generateApiUrl($params);
    }

    /**
     * 创建一个文件换换类 Api Url
     *
     * @param $url
     *
     * @return bool
     * @throws \VcySDK\Exception
     */
    public function generateConvertUrl($url)
    {

        // 获取参数数组
        $params = func_get_args();
        // 从配置中获取 Url 相关数据
        $paths = array();
        // 获取域名
        if (!$this->getConvertApiUrl($paths[])) {
            throw new Exception(Error::API_URL_EMPTY);
            return false;
        }

        $paths[] = 'b';
        // 获取企业账号
        if (!$this->getEnumber($paths[])) {
            throw new Exception(Error::ENUMBER_EMPTY);
            return false;
        }
        $params[] = implode('/', $paths);

        return $this->generateApiUrl($params);
    }

    /**
     * 获取第三方标识
     *
     * @param string &$thirdIdentifier 第三方标识
     *
     * @return boolean
     * @throws \VcySDK\Exception
     */
    public function getThirdIdentifier(&$thirdIdentifier)
    {

        // 获取第三方标识
        $thirdIdentifier = Config::instance()->thirdIdentifier;
        if (empty($thirdIdentifier)) {
            Logger::write('thirdIdentifier is empty');
            throw new Exception(Error::THIRD_IDENTIFIER_EMPTY);
            return false;
        }

        return true;
    }

    /**
     * 获取应用标识
     *
     * @param string &$pluginIdentifier 应用标识
     *
     * @return boolean
     * @throws \VcySDK\Exception
     */
    public function getPluginIdentifier(&$pluginIdentifier)
    {

        // 获取应用标识
        $pluginIdentifier = Config::instance()->pluginIdentifier;
        if (empty($pluginIdentifier)) {
            Logger::write('pluginIdentifier is empty');
            throw new Exception(Error::PLUGIN_IDENTIFIER_EMPTY);
            return false;
        }

        return true;
    }

    /**
     * 获取企业账号
     *
     * @param string &$enumber 企业账号配置
     *
     * @return boolean
     * @throws \VcySDK\Exception
     */
    public function getEnumber(&$enumber)
    {

        // 获取企业账号
        $enumber = Config::instance()->enumber;
        if (empty($enumber)) {
            Logger::write('enumber is empty');
            throw new Exception(Error::ENUMBER_EMPTY);
            return false;
        }

        return true;
    }

    /**
     * 获取域名
     *
     * @param string &$apiUrl 域名配置
     *
     * @return boolean
     * @throws \VcySDK\Exception
     */
    public function getApiUrl(&$apiUrl)
    {

        // 获取域名
        $apiUrl = Config::instance()->apiUrl;
        if (empty($apiUrl)) {
            Logger::write('apiUrl is empty.');
            throw new Exception(Error::API_URL_EMPTY);
            return false;
        }

        return true;
    }

    /**
     * 获取文件转换接口域名
     *
     * @param string &$convertApiUrl 域名配置
     *
     * @return boolean
     * @throws \VcySDK\Exception
     */
    public function getConvertApiUrl(&$convertApiUrl)
    {
        // 获取域名
        $convertApiUrl = Config::instance()->fileConvertApiUrl;

        if (empty($convertApiUrl)) {
            Logger::write('convertApiUrl is empty.');
            throw new Exception(Error::FILE_CONVERT_API_URL_EMPTY);
            return false;
        }

        return true;
    }

    /**
     * 构造列表请求参数
     *
     * @param array $condition 查询参数
     * @param array $orders 排序参数
     * @param int $page 页码
     * @param int $perpage 每页记录数
     *
     * @return array
     */
    public function mergeListApiParams($condition = array(), $orders = array(), $page = 1, $perpage = 30)
    {

        // 强制转换成数组
        $condition = (array)$condition;

        // 排序参数
        if (!empty($orders) && is_array($orders)) {
            $condition['orderList'] = array();
            // 遍历所有排序字段
            foreach ($orders as $field => $type) {
                $condition['orderList'][] = array('column' => $field, 'orderType' => $type);
            }
        }

        // 分页参数
        $condition['pageNum'] = $page;
        $condition['pageSize'] = $perpage;

        return $condition;
    }

    /**
     * 使用GET方式请求接口数据
     *
     * @param mixed &$data 返回值
     * @param string $url 请求URL
     * @param string $reqData 请求数据
     * @param mixed $headers 请求头部
     * @param bool $retry 是否重试
     *
     * @return boolean
     */
    public function get(&$data, $url, $reqData = null, $headers = array(), $retry = false)
    {

        return $this->request($data, $url, $reqData, $headers, 'GET', null, $retry);
    }

    /**
     * 使用POST方式请求接口数据
     *
     * @param mixed &$data 返回值
     * @param string $url 请求URL
     * @param string $reqData 请求数据
     * @param mixed $headers 请求头部
     * @param mixed $files 文件
     * @param bool $retry 是否重试
     *
     * @return boolean
     */
    public function post(&$data, $url, $reqData = null, $headers = array(), $files = null, $retry = false)
    {

        return $this->request($data, $url, $reqData, $headers, 'POST', $files, $retry);
    }

    /**
     * 请求SDK接口
     *
     * @param string $url 接口URL
     * @param array $reqData 请求参数
     * @param string $urlFunc URL拼凑方法
     * @param array $urlParams URL参数
     * @param mixed $files 文件
     * @param string $type 请求方式
     *
     * @return array|bool
     * @throws \VcySDK\Exception
     */
    public function postSDK($url, $reqData, $urlFunc, $urlParams = array(), $files = null, $type = 'post')
    {
        // 拼凑URL
        $func = array($this, $urlFunc);
        array_unshift($urlParams, $url);
        $apiUrl = call_user_func_array($func, $urlParams);
        if (!$apiUrl) {
            throw new Exception(Error::API_URL_ERROR);
            return false;
        }

        // 头信息
        if (null == $files) {
            $headers = array('Content-Type' => 'application/json');
        } else {
            $headers = array();
        }

//        // 计算签名
//        $sigSdk = &ApiSig::instance();
//        $sig = $sigSdk->getSig($reqData);
//        $reqData['sign'] = $sig;
//        $reqData['timestamp'] = NOW_TIME;

        // 发送请求
        $data = array();
        try {
            if (!$this->$type($data, $apiUrl, $reqData, $headers, $files)) {
                Logger::write('apiUrl: ' . $apiUrl . '|reqData: ' . var_export($reqData, true));
                throw new Exception(Error::API_REQUEST_ERROR);
                return false;
            }
        } catch (Exception $e) {
            Logger::write('$snoopy[' . $type . '] error, url: ' . $url . '|data: ' . var_export($reqData, true) . '|errcode: ' . $e->getCode() . '|errmsg: ' . $e->getMessage());
            // 获取报错信息
            $msgs = array('msg' => $e->getMessage(), 'code' => $e->getSdkCode(), 'requestId' => $e->getRequestID());
            throw new Exception($msgs, $e->getCode());
        }

        // 记录 UC 请求 ID
        $this->allRequestId($data['requestId']);

        // TODO zhoutao 2016-08-31 01:07:45 等UC接口全开启签名后取消注释
        // 验证返回数据签名
        Logger::write('UC请求方法:' . $url);
        if (isset($data['data'])) {
            //            $sigSdk = &ApiSig::instance();
            //            $sig = $sigSdk->getSig($data['data'], ['timestamp' => $data['timestamp']]);
            //            if ($sig !== $data['sign']) {
            //                throw new Exception('接口返回数据签名错误');
            //            }

            return $data['data'];
        }

        // 如果没有data返回则返回code
        return ['code' => $data['code']];
    }

    /**
     * 整理错误信息
     * @param Exception $err
     * @return string
     */
    protected function collectExceptionMsg($err)
    {

        $message = $err->getMessage();
        $trace = $err->getTrace();
        Logger::write('trace: ' . var_export($trace, true));
        if (!is_array($trace) || 0 >= count($trace)) {
            return $message;
        }

        $args = isset($trace[0]) ? (array)$trace[0]['args'] : array();
        $data = isset($args[0]) ? (array)$args[0]['data'] : array();
        $otherMsgs = [];
        foreach ($data as $_msg) {
            if (empty($_msg['field']) || empty($_msg['message'])) {
                continue;
            }

            $otherMsgs[] = $_msg['field'] . ':' . $_msg['message'];
        }

        $message .= implode($otherMsgs);
        return $message;
    }

    /**
     * 拼凑请求URL
     *
     * @param string $url URL地址
     * @param mixed $params 请求参数
     *
     * @return boolean
     */
    protected function buildGetQuery(&$url, $params)
    {

        // 如果请求数据为空
        if (empty($params)) {
            return true;
        }

        // 拼凑 GET 字串
        if (is_array($params)) {
            $get_data = http_build_query($params);
        } else {
            $get_data = $params;
        }

        // 判断 URL 是否有参数
        if (false === strpos($url, '?')) {
            $url .= '?';
        } else {
            $url .= '&';
        }

        $url .= $get_data;

        return true;
    }

    /**
     * 请求接口数据
     *
     * @param mixed &$data 返回值
     * @param string $url 请求URL
     * @param string|array $reqParams 请求数据
     * @param mixed $headers 请求头部
     * @param string $method 请求方式, 如: GET/POST/DELETE/PUT
     * @param mixed $files 文件路径
     * @param bool $retry 是否重试
     *
     * @return boolean
     * @throws \VcySDK\Exception
     */
    public function request(&$data, $url, $reqParams, $headers, $method, $files = null, $retry = false)
    {

        // 载入 Snoopy 类
        $snoopy = new \VcySDK\Net\Snoopy();
        // 使用自定义的头字段,格式为 array(字段名 => 值, ... ...)
        if (!is_array($headers)) {
            $headers = [];
        }

        if (!isset($headers['x-request-id'])) {
            static $x_request_id;

            if (is_null($x_request_id)) {
                $x_request_id = I('request.x-request-id', strtolower(md5(microtime(true))));
            }

            $headers['x-request-id'] = $x_request_id;
        }

        Logger::write("URL: {$url} Method: {$method} Post: " . var_export($reqParams, true) . " Header: " . var_export($headers, true) . " Files: " . var_export($files, true));
        $snoopy->rawheaders = $headers;
        $method = rstrtoupper($method);
        // 非 GET 协议, 需要设置
        $methods = array('POST', 'PUT', 'DELETE');
        if (!in_array($method, $methods)) {
            $method = 'GET';
        }

        // 设置协议
        if (!empty($files)) {
            // 如果需要传文件
            $method = 'POST';
            $snoopy->set_submit_multipart();
        } else {
            $snoopy->set_submit_normal('application/json');
        }

        // 判断协议
        $snoopy->set_submit_method($method);
        switch (rstrtoupper($method)) {
            case 'POST' :
            case 'PUT' :
            case 'DELETE' :
                $result = $snoopy->submit($url, $reqParams, $files);
                break;
            default :
                // 如果有请求数据
                $this->buildGetQuery($url, $reqParams);
                $result = $snoopy->fetch($url);
                break;
        }

        // 如果读取错误
        if (!$result || 200 != $snoopy->status) {
            Logger::write('$snoopy[' . $method . '] error, url: ' . $url . '|post:' . var_export($reqParams, true) . '|result: ' . var_export($result, true) . '|status: ' . $snoopy->status . '|error: ' . $snoopy->error);
            // 出错时, 返回 $snoopy 对象
            $data = $snoopy;
            throw new Exception(Error::API_REQUEST_ERROR);
        }

        // 获取返回数据
        $data = $snoopy->results;
        // 如果返回的是 JSON, 则解析 JSON
        if ($this->isJson($snoopy->headers)) {
            $data = json_decode($data, true);
            // 如果接口返回错误, 则直接抛异常
            if (self::SUCCESS_CODE != $data['code']) {
                throw new Exception($data);
            }
        }

        Logger::write("result: " . var_export($data, true) . "|post: " . var_export($reqParams, true));
        // 如果返回的数据为空, 则
        if (empty($data)) {
            Logger::write('$snoopy[' . $method . '] error, url: ' . $url . '|result: ' . var_export($result, true) . '|status: ' . $snoopy->status);
            // 出错时, 返回 $snoopy 对象
            $data = $snoopy;
            throw new Exception(Error::API_RESPONSE_DATA_EMPTY);
        }

        return true;
    }

    /**
     * 判断返回值是否为Json数据
     *
     * @param array $headers 头部数据
     *
     * @return boolean
     */
    private function isJson($headers)
    {

        // 如果头部信息已经解析出来, 则
        if (isset($headers['Content-Type'])) {
            return 0 === strpos($headers['Content-Type'], 'application/json');
        }

        // 遍历返回的头部信息
        foreach ($headers as $_header) {
            // 如果匹配到 json 头
            if (preg_match('/^Content-Type:\s*application\/json/i', $_header)) {
                return true;
            }
        }

        return false;
    }

    /**
     * 验证请求签名
     *
     * @param string $method 请求方式
     *
     * @return mixed
     */
    public function checkSig($method = 'POST')
    {

        $sig = ApiSig::instance();
        return $sig->getParamAndCheck([], $method);
    }
}