<?php
//使用时请在项目中引用该php文件
//设置默认时区
date_default_timezone_set('Asia/Shanghai');

//检测API路径
if (!defined("KS3_API_PATH")) {
    define("KS3_API_PATH", __DIR__);
}
//是否使用VHOST
if (!defined("KS3_API_VHOST")) {
    define("KS3_API_VHOST", FALSE);
}
//是否开启日志(写入日志文件)
if (!defined("KS3_API_LOG")) {
    define("KS3_API_LOG", FALSE);
}
//是否显示日志(直接输出日志)
if (!defined("KS3_API_DISPLAY_LOG")) {
    define("KS3_API_DISPLAY_LOG", FALSE);
}
//定义日志目录(默认是该项目log下)
if (!defined("KS3_API_LOG_PATH")) {
    define("KS3_API_LOG_PATH", "");
}
//是否使用HTTPS
if (!defined("KS3_API_USE_HTTPS")) {
    define("KS3_API_USE_HTTPS", FALSE);
}
//是否开启curl debug模式
if (!defined("KS3_API_DEBUG_MODE")) {
    define("KS3_API_DEBUG_MODE", FALSE);
}
define("KS3_API_Author", "lijunwei@kingsoft.com");
define("KS3_API_Version", "1.2");

require_once KS3_API_PATH . DIRECTORY_SEPARATOR . "config" . DIRECTORY_SEPARATOR . "Consts.php";
require_once KS3_API_PATH . DIRECTORY_SEPARATOR . "core" . DIRECTORY_SEPARATOR . "API.php";
require_once KS3_API_PATH . DIRECTORY_SEPARATOR . "core" . DIRECTORY_SEPARATOR . "Signers.php";
require_once KS3_API_PATH . DIRECTORY_SEPARATOR . "core" . DIRECTORY_SEPARATOR . "Ks3Request.class.php";
require_once KS3_API_PATH . DIRECTORY_SEPARATOR . "core" . DIRECTORY_SEPARATOR . "Handlers.php";
require_once KS3_API_PATH . DIRECTORY_SEPARATOR . "core" . DIRECTORY_SEPARATOR . "Builders.php";
require_once KS3_API_PATH . DIRECTORY_SEPARATOR . "core" . DIRECTORY_SEPARATOR . "Logger.php";
require_once KS3_API_PATH . DIRECTORY_SEPARATOR . "core" . DIRECTORY_SEPARATOR . "MessageHolder.php";
require_once KS3_API_PATH . DIRECTORY_SEPARATOR . "lib" . DIRECTORY_SEPARATOR . "RequestCore.class.php";
require_once KS3_API_PATH . DIRECTORY_SEPARATOR . "exceptions" . DIRECTORY_SEPARATOR . "Exceptions.php";

if (function_exists('get_loaded_extensions')) {
    //检测curl扩展
    $extensions = get_loaded_extensions();
    if ($extensions) {
        if (!in_array('curl', $extensions, true)) {
            throw new Ks3ClientException("please install curl extension");
        }
        if (!in_array('mbstring', $extensions, true)) {
            throw new Ks3ClientException("please install mbstring extension");

        }
    } else {
        throw new Ks3ClientException("please install extensions");
    }
} else {
    throw new Ks3ClientException();
}

class Ks3Client
{
    private $accessKey;
    private $secretKey;
    private $endpoint;

    public function __construct($accessKey = NULL, $secretKey = NULL, $endpoint = NULL)
    {
        $this->accessKey = $accessKey;
        $this->secretKey = $secretKey;

        if (empty($endpoint)) {
            throw new Ks3ClientException("must set endpoint, please see http://ks3.ksyun.com/doc/api/index.html Region part");
        }
        $this->endpoint = $endpoint;

        $this->signers = array();
    }

    /**
     * 方法列表:(具体使用请参考readme.md)
     * listBuckets,罗列bucket
     * deleteBucket，删除bucket
     * deleteBucketCORS，删除bucket跨域配置
     * createBucket,新建bucket
     * setBucketAcl，设置bucket访问权限
     * setBucketCORS,设置bucket跨域配置
     * setBucketLogging,设置bucket日志配置
     * listObjects,罗列object
     * getBucketAcl,获取bucket访问权限
     * getBucketCORS,获取bucket跨域配置
     * getBucketLocation,获取bucket地点配置
     * getBucketLogging,获取bucket日志配置
     * bucketExists,判断bucket是否存在
     * listMutipartUploads,罗列当前bucket下尚未结束的分块上传
     * putObjectByContent,上传文件
     * putObjectByFile,上传文件
     * setObjectAcl，设置object访问权限
     * copyObject,复制object
     * getObjectMeta，获取object元数据
     * objectExists，判断object是否存在
     * deleteObject，删除object
     * deleteObjects，删除多个object
     * getObject，下载object
     * getObjectAcl，获取object访问权限
     * initMultipartUpload，初始化分块上传
     * uploadPart，上传块
     * abortMultipartUpload，终止分块上传
     * listParts，罗列已经上传的块
     * completeMultipartUpload，完成分块上传
     * generatePresignedUrl，生成文件外链
     * putAdp,添加异步数据处理任务
     * getAdp,查询异步数据处理任务
     * @param $method
     * @param array $args
     * @return ResponseCore|string|string[]|null
     * @throws Exception
     */
    public function __call($method, $args = array())
    {
        $holder = new MessageHolder();

        $holder->msg = "------------------Logging Start-------------------------\r\n";
        $holder->msg .= "method->" . $method . " args->" . serialize($args) . "\r\n";
        $ex = NULL;
        try {
            $result = $this->invoke($method, $args, $holder);
        } catch (Exception $e) {
            $holder->msg .= $e . "\r\n";
            $ex = $e;
        }
        $holder->msg .= "------------------Logging End-------------------------\r\n";
        if ($ex != NULL) {
            throw $ex;
        }
        return $result;
    }

    private function invoke($method, $args = array(), $holder, $location = NULL)
    {
        $api = API::$API[$method];
        if (!$api) {
            throw new Ks3ClientException($method . " Not Found API");
        }
        if (count($args) !== 0) {
            if (count($args) > 1 || !is_array($args[0])) {
                throw new Ks3ClientException("this method only needs one array argument");
            }
            $args = $args[0];
        }
        if (isset($api["redirect"])) {
            $api = API::$API[$api["redirect"]];
        }
        $request = new Ks3Request();
        if (empty($args["Bucket"])) {
            if ($api["needBucket"]) {
                throw new Ks3ClientException($method . " this api need bucket");
            }
        } else {
            $request->bucket = $args["Bucket"];
        }
        $position = $api["objectPostion"] ?? "Key";
        if (empty($args[$position])) {
            if ($api["needObject"]) {
                throw new Ks3ClientException($method . " this api need " . $position);
            }
        } else {
            $key = $args[$position];
            $preEncoding = mb_detect_encoding($key, array("ASCII", "UTF-8", "GB2312", "GBK", "BIG5"));
            $holder->msg .= "key encoding " . $preEncoding . "\r\n";
            if (strtolower($preEncoding) !== "utf-8") {
                $key = iconv($preEncoding, "UTF-8", $key);
            }
            $request->key = $key;
        }
        $method = $api["method"];
        if ($method === "Method") {
            if (empty($args["Method"])) {
                $request->method = "GET";
            } else {
                $request->method = $args["Method"];
            }
        } else {
            $request->method = $api["method"];
        }
        if (KS3_API_USE_HTTPS) {
            $request->scheme = "https://";
        } else {
            $request->scheme = "http://";
        }
        $request->endpoint = $this->endpoint;
        //add subresource
        if (!empty($api["subResource"])) {
            $request->subResource = $api["subResource"];
        }
        //add query params
        if (isset($api["queryParams"])) {
            foreach ($api["queryParams"] as $key => $value) {
                $required = FALSE;
                if (strpos($value, "!") === 0) {
                    $required = TRUE;
                    $value = substr($value, 1);
                }
                $index = explode("->", $value);
                $curIndexArg = $args;
                $add = TRUE;
                $curkey = "";
                foreach ($index as $key1 => $value1) {
                    if (!isset($curIndexArg[$value1]) && $value1 !== "*") {
                        $add = FALSE;
                    } else {
                        $curkey = $value1;
                        //星号表示所有,按照暂时的业务，默认星号后面就没了
                        if ($curkey === "*") {
                            foreach ($curIndexArg as $queryK => $queryV) {
                                if (!is_array($queryV)) {
                                    $request->addQueryParams($queryK, $queryV);
                                }
                            }
                            $add = FALSE;
                            $required = FALSE;
                            break;
                        }

                        $curIndexArg = $curIndexArg[$value1];
                    }
                }
                if (!empty($curIndexArg) && $add) {
                    $request->addQueryParams($curkey, $curIndexArg);
                    continue;
                }
                if ($required) {
                    throw new Ks3ClientException($method . " param " . $value . " is required");
                }
            }
        }
        if (isset($api["body"])) {
            if (isset($api["body"]["builder"])) {
                $builderName = $api["body"]["builder"];
                $builder = new $builderName();
                $request->body = $builder->build($args);
            } else if (isset($api["body"]["position"])) {
                $position = $api["body"]["position"];
                $index = explode("->", $position);
                $curIndexArg = $args;
                $add = TRUE;
                $curkey = "";
                foreach ($index as $key1 => $value1) {
                    if (!isset($curIndexArg[$value1])) {
                        $add = FALSE;
                    } else {
                        $curIndexArg = $curIndexArg[$value1];
                        $curkey = $value1;
                    }
                }
                if (!empty($curIndexArg) && $add) {
                    $request->body = $curIndexArg;
                }
            }
        }

        //add ext headers
        //TODO
        //sign request
        $signer = NULL;
        if (isset($api["signer"])) {
            $signers = explode("->", $api["signer"]);
            foreach ($signers as $key => $value) {
                $signer = new $value();
                $log = $signer->sign($request, array("accessKey" => $this->accessKey, "secretKey" => $this->secretKey, "args" => $args));
                if (!empty($log)) {
                    $holder->msg .= $log . "\r\n";
                }
            }
        }

        if ($signer === NULL || !($signer instanceof QueryAuthSigner)) {
            $url = $request->toUrl($this->endpoint);
            if ($location != NULL) {
                $url = $location;
            }
            $httpRequest = new RequestCore($url);
            if (KS3_API_DEBUG_MODE === TRUE) {
                $httpRequest->debug_mode = TRUE;
            }
            $httpRequest->set_method($request->method);
            foreach ($request->headers as $key => $value) {
                $httpRequest->add_header($key, $value);
            }
            $httpRequest->request_body = $request->body;

            if (isset($args["writeCallBack"])) {
                $httpRequest->register_streaming_write_callback($args["writeCallBack"]);
            }
            if (isset($args["readCallBack"])) {
                $httpRequest->register_streaming_read_callback($args["readCallBack"]);
            }

            $read_stream = $request->read_stream;
            $read_length = $request->getHeader(Headers::$ContentLength);
            $seek_position = $request->seek_position;
            if (isset($read_stream)) {
                $httpRequest->set_read_stream($read_stream, $read_length);
                $httpRequest->set_seek_position($seek_position);
                $httpRequest->remove_header(Headers::$ContentLength);
            }
            $write_stream = $request->write_stream;
            if (isset($write_stream)) {
                $httpRequest->set_write_stream($write_stream);
            }

            $holder->msg .= "request url->" . serialize($httpRequest->request_url) . "\r\n";
            $holder->msg .= "request headers->" . serialize($httpRequest->request_headers) . "\r\n";
            $holder->msg .= "request body->" . $httpRequest->request_body . "\r\n";
            $holder->msg .= "request read stream length->" . $read_length . "\r\n";
            $holder->msg .= "request read stream seek position->" . $seek_position . "\r\n";
            $httpRequest->send_request();
            //print_r($httpRequest);
            $body = $httpRequest->get_response_body();
            $data = new ResponseCore ($httpRequest->get_response_header(), Utils::replaceNS2($body), $httpRequest->get_response_code());

            if ($data->status == 307) {
                $respHeaders = $httpRequest->get_response_header();
                $location = $respHeaders["location"];
                if (strpos($location, "http") === 0) {
                    $holder->msg .= "response code->" . $httpRequest->get_response_code() . "\r\n";
                    $holder->msg .= "response headers->" . serialize($httpRequest->get_response_header()) . "\r\n";
                    $holder->msg .= "response body->" . $body . "\r\n";
                    $holder->msg .= "retry request to " . $location . "\r\n";
                    //array($args)详见invoke开头
                    return $this->invoke($method, array($args), $holder, $location);
                }
            }
            $holder->msg .= "response code->" . $httpRequest->get_response_code() . "\r\n";
            $holder->msg .= "response headers->" . serialize($httpRequest->get_response_header()) . "\r\n";
            $holder->msg .= "response body->" . $body . "\r\n";
            $handlers = explode("->", $api["handler"]);
            foreach ($handlers as $key => $value) {
                $handler = new $value();
                $data = $handler->handle($data);
            }
            return $data;
        }

        $url = $request->toUrl($this->endpoint);
        $holder->msg .= $url . "\r\n";
        return $url;
    }

    //用于生产表单上传时的签名信息
    public function postObject($bucket, $postFormData = array(), $unknowValueFormFiled = array(), $filename = NULL, $expire = 18000): array
    {
        $policy = array();

        $expireTime = Utils::iso8601(time() + $expire);
        $policy["expiration"] = $expireTime;
        $postFormData["bucket"] = $bucket;
        $conditions = array();
        foreach ($postFormData as $key => $value) {
            $condition = array();
            $condition[$key] = str_replace("\${filename}", $filename, $value);
            $conditions[] = $condition;
        }
        foreach ($unknowValueFormFiled as $key => $value) {
            $condition = array();
            $condition[] = "starts-with";
            $condition[] = "\$" . $value;
            $condition[] = "";
            $conditions[] = $condition;
        }
        $policy["conditions"] = $conditions;
        $json = json_encode($policy);
        $signature = base64_encode(hash_hmac('sha1', base64_encode($json), $this->secretKey, true));
        $result = array();
        $result["Policy"] = base64_encode($json);
        $result["Signature"] = $signature;
        $result["KSSAccessKeyId"] = $this->accessKey;
        return $result;
    }
}