<?php

/**
 * Copyright 1999-2021 iFLYTEK Corporation

 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

namespace app\extend\IFlytek\Xfyun\Core;

/**
 * Http客户端
 *
 * @author guizheng@iflytek.com
 */

use Exception;
use GuzzleHttp\Psr7\Response;
use app\extend\IFlytek\Xfyun\Core\Handler\Guzzle7HttpHandler;
use app\extend\IFlytek\Xfyun\Core\Traits\SignTrait;
use app\extend\IFlytek\Xfyun\Core\Traits\DecideRetryTrait;
use app\extend\IFlytek\Xfyun\Core\Handler\HttpHandlerFactory;
use Psr\Http\Message\RequestInterface;
use GuzzleHttp\Psr7\Utils;

class HttpClient
{
    use SignTrait;
    use DecideRetryTrait;

    const MAX_DELAY_MICROSECONDS = 60000000;

    /**
     * @var Guzzle7HttpHandler
     */
    private $httpHandler;

    /**
     * @var array 要添加的头部信息
     */
    private $httpHeaders;

    /**
     * @var int 超时时间
     */
    private $requestTimeout;

    /**
     * @var int 重试次数
     */
    private $retries;

    /**
     * @var int 重试次数
     */
    private $decideRetryFunction;

    /**
     * @var int 重试次数
     */
    private $delayFunction;

    /**
     * @var int 重试次数
     */
    private $calcDelayFunction;

    public function __construct($config)
    {
        $config += [
            'httpHandler' => null,
            'httpHeaders' => [],
            'requestTimeout' => 3000,
            'retries' => 3,
            'decideRetryFunction' => null,
            'delayFunction' => null,
            'calcDelayFunction' => null
        ];
        $this->httpHandler = $config['httpHandler'] ?: HttpHandlerFactory::build();
        $this->httpHeaders = $config['httpHeaders'];
        $this->retries = $config['retries'];
        $this->decideRetryFunction = $config['decideRetryFunction'] ?: $this->getDecideRetryFunction();
        $this->calcDelayFunction = $config['calcDelayFunction'] ?: [$this, 'calculateDelay'];
        $this->delayFunction = $config['delayFunction'] ?: static function ($delay) {
            usleep($delay);
        };
    }

    /**
     * 发送请求，并接受返回
     *
     * @param   RequestInterface $request
     * @param   array $options 请求的配置参数
     * @return  Response
     * @throws  Exception
     */
    public function sendAndReceive(RequestInterface $request, array $options = [])
    {
        try {
            $delayFunction = $this->delayFunction;
            $calcDelayFunction = $this->calcDelayFunction;
            $retryAttempt = 0;

            while (true) {
                try {
                    return call_user_func_array($this->httpHandler, [$this->applyHeaders($request), $options]);
                } catch (Exception $exception) {
                    if ($this->decideRetryFunction) {
                        if (!call_user_func($this->decideRetryFunction, $exception)) {
                            throw $exception;
                        }
                    }

                    if ($retryAttempt >= $this->retries) {
                        break;
                    }

                    $delayFunction($calcDelayFunction($retryAttempt));
                    $retryAttempt++;
                }
            }

            throw $exception;
        } catch (Exception $ex) {
            throw $ex;
        }
    }

    /**
     * 将头部信息添加到请求对象中
     *
     * @param   $request 请求对象
     * @return  RequestInterface
     */
    private function applyHeaders($request)
    {
      return Utils::modifyRequest($request, ['set_headers' => $this->httpHeaders]);
    }

    /**
     * 根据重试次数计算下次重试延迟时间
     *
     * @param   int $attempt 重试次数
     * @return  int
     */
    public static function calculateDelay($attempt)
    {
        return min(
            mt_rand(0, 1000000) + (pow(2, $attempt) * 1000000),
            self::MAX_DELAY_MICROSECONDS
        );
    }
}
