<?php

namespace Smartling\Vendor\Smartling\Context;
use Smartling\Vendor\Psr\Log\LoggerInterface;
use Smartling\Vendor\Smartling\AuthApi\AuthApiInterface;
use Smartling\Vendor\Smartling\BaseApiAbstract;
use Smartling\Vendor\Smartling\Context\Params\MatchContextParameters;
use Smartling\Vendor\Smartling\Context\Params\MissingResourcesParameters;
use Smartling\Vendor\Smartling\Context\Params\UpdateResourceStateParameters;
use Smartling\Vendor\Smartling\Context\Params\UploadContextParameters;
use Smartling\Vendor\Smartling\Context\Params\UploadResourceParameters;
use Smartling\Vendor\Smartling\Exceptions\SmartlingApiException;
use Smartling\Vendor\Smartling\Wait;
use Smartling\Vendor\Smartling\Waitable;

/**
 * Class ContextApi
 *
 * @package Smartling\Vendor\Smartling\Project
 */
class ContextApi extends BaseApiAbstract implements Waitable
{

    use Wait;

    const ENDPOINT_URL = 'https://api.smartling.com/context-api/v2/projects';

    /**
     * Makes async operation sync.
     *
     * @param array $data
     * @throws SmartlingApiException
     */
    public function wait(array $data) {
        if (!empty($data['processUid'])) {
            $start_time = \time();

            do {
                $delta = \time() - $start_time;

                if ($delta > $this->getTimeOut()) {
                    throw new SmartlingApiException(\vsprintf('Async operation is not completed after %s seconds.', [$delta]));
                }

                \sleep(1);

                $result = $this->getMatchStatus($data['processUid']);
            }
            while ($result['processState'] != 'COMPLETED');
        }
    }

    /**
     * Instantiates Context API object.
     *
     * @param AuthApiInterface $authProvider
     * @param string $projectId
     * @param LoggerInterface $logger
     *
     * @return ContextApi
     */
    public static function create(AuthApiInterface $authProvider, $projectId, $logger = null)
    {
        $client = self::initializeHttpClient(self::ENDPOINT_URL);

        $instance = new self($projectId, $client, $logger, self::ENDPOINT_URL);
        $instance->setAuth($authProvider);

        return $instance;
    }

    /**
     * {@inheritdoc}
     *
     * @throws SmartlingApiException
     */
    protected function processBodyOptions($requestData = []) {
        $opts = parent::processBodyOptions($requestData);
        $keys = ['content', 'resource'];

        if (!empty($opts['multipart'])) {
            foreach ($opts['multipart'] as &$data) {
                if (\in_array($data['name'], $keys)) {
                    $data['contents'] = $this->readFile($data['contents']);

                    if ($data['name'] === 'content' && substr($requestData['multipart']['content'], 0, 7) === 'data://') {
                      if (!isset($requestData['multipart']['name'])) {
                        throw new SmartlingApiException('When content is specified using the data:// protocol, name is a required request body field');
                      }

                      $data['filename'] = $requestData['multipart']['name'];
                    }
                }

                if ($data['name'] == 'matchParams') {
                    $data['headers'] = [
                        "Content-Type" => "application/json",
                    ];
                }
            }
        }

        return $opts;
    }

    /**
     * {@inheritdoc}
     */
    protected function getDefaultRequestData($parametersType, $parameters, $auth = true, $httpErrors = false) {
        $requestData = parent::getDefaultRequestData($parametersType, $parameters, $auth = true, $httpErrors = false);
        $requestData['headers']['X-SL-Context-Source'] = $this->getXSLContextSourceHeader();

        return $requestData;
    }

    /**
     * Returns X-SL-Context-Source header.
     *
     * @return string
     */
    private function getXSLContextSourceHeader() {
        return \vsprintf('group=connectors;name=%s;version=%s', [
            static::getCurrentClientId(),
            static::getCurrentClientVersion(),
        ]);
    }

    /**
     * Upload a new context.
     *
     * @param \Smartling\Vendor\Smartling\Context\Params\UploadContextParameters $params
     * @return array
     * @throws \Smartling\Vendor\Smartling\Exceptions\SmartlingApiException
     */
    public function uploadContext(UploadContextParameters $params)
    {
        $requestData = $this->getDefaultRequestData('multipart', $params->exportToArray());

        return $this->sendRequest('contexts', $requestData, self::HTTP_METHOD_POST);
    }

    /**
     * Match context async.
     *
     * @param $contextUid
     * @param \Smartling\Vendor\Smartling\Context\Params\MatchContextParameters $params
     *
     * @return array
     * @throws \Smartling\Vendor\Smartling\Exceptions\SmartlingApiException
     */
    public function matchContext($contextUid, MatchContextParameters $params = null)
    {
        $endpoint = \vsprintf('contexts/%s/match/async', [$contextUid]);
        $requestData = $this->getDefaultRequestData('json', \is_null($params) ? [] : $params->exportToArray());

        return $this->sendRequest($endpoint, $requestData, self::HTTP_METHOD_POST);
    }

    /**
     * Match context sync.
     *
     * @param $contextUid
     * @param \Smartling\Vendor\Smartling\Context\Params\MatchContextParameters $params
     *
     * @throws \Smartling\Vendor\Smartling\Exceptions\SmartlingApiException
     */
    public function matchContextSync($contextUid, MatchContextParameters $params = null)
    {
        $this->wait($this->matchContext($contextUid, $params));
    }

    /**
     * Upload and match async.
     *
     * @param \Smartling\Vendor\Smartling\Context\Params\UploadContextParameters $params
     * @return array
     * @throws \Smartling\Vendor\Smartling\Exceptions\SmartlingApiException
     */
    public function uploadAndMatchContext(UploadContextParameters $params)
    {
        $requestData = $this->getDefaultRequestData('multipart', $params->exportToArray());

        return $this->sendRequest('contexts/upload-and-match-async', $requestData, self::HTTP_METHOD_POST);
    }

    /**
     * Upload and match sync.
     *
     * @param \Smartling\Vendor\Smartling\Context\Params\UploadContextParameters $params
     * @throws \Smartling\Vendor\Smartling\Exceptions\SmartlingApiException
     */
    public function uploadAndMatchContextSync(UploadContextParameters $params)
    {
        $this->wait($this->uploadAndMatchContext($params));
    }

    /**
     * Get context match status.
     *
     * @param $processUid
     * @return array
     * @throws SmartlingApiException
     */
    public function getMatchStatus($processUid) {
        $endpoint = \vsprintf('/processes/%s', [$processUid]);
        $requestData = $this->getDefaultRequestData('query', []);

        return $this->sendRequest($endpoint, $requestData, self::HTTP_METHOD_GET);
    }

    /**
     * Get missing resources.
     *
     * @param MissingResourcesParameters|null $params
     * @return array
     * @throws \Smartling\Vendor\Smartling\Exceptions\SmartlingApiException
     */
    public function getMissingResources(MissingResourcesParameters $params = null) {
        $requestData = $this->getDefaultRequestData('query', \is_null($params) ? [] : $params->exportToArray());

        return $this->sendRequest('missing-resources', $requestData, self::HTTP_METHOD_GET);
    }

    /**
     * Get all missing resources.
     *
     * @return array
     * Contains next keys:
     *  - "items": array of missing resources
     *  - "all": boolean which indicates whether function has read all the
     *           available items or not.
     *
     * @throws SmartlingApiException
     */
    public function getAllMissingResources() {
        $missingResources = [];
        $offset = FALSE;
        $all = TRUE;
        $start_time = \time();

        while (!\is_null($offset)) {
            $delta = \time() - $start_time;

            if ($delta > $this->getTimeOut()) {
                $all = FALSE;
                break;
            }

            if (!$offset) {
                $params = NULL;
            }
            else {
                $params = new MissingResourcesParameters();
                $params->setOffset($offset);
            }

            $response = $this->getMissingResources($params);
            $offset = !empty($response['offset']) ? $response['offset'] : NULL;
            $missingResources = \array_merge($missingResources, $response['items']);
        }

        return [
            'items' => $missingResources,
            'all' => $all,
        ];
    }

    /**
     * Upload resource.
     *
     * @param $resourceId
     * @param \Smartling\Vendor\Smartling\Context\Params\UploadResourceParameters $params
     * @return bool
     * @throws SmartlingApiException
     */
    public function uploadResource($resourceId, UploadResourceParameters $params)
    {
        $endpoint = \vsprintf('resources/%s', [$resourceId]);
        $requestData = $this->getDefaultRequestData('multipart', $params->exportToArray());

        return $this->sendRequest($endpoint, $requestData, self::HTTP_METHOD_PUT);
    }

    /**
     * Render context.
     *
     * @param $contextUid
     * @return array
     * @throws \Smartling\Vendor\Smartling\Exceptions\SmartlingApiException
     */
    public function renderContext($contextUid)
    {
        $endpoint = \vsprintf('contexts/%s/render', [$contextUid]);
        $requestData = $this->getDefaultRequestData('form_params', []);

        return $this->sendRequest($endpoint, $requestData, self::HTTP_METHOD_POST);
    }

    /**
     * Update resource state.
     *
     * @param string $resourceId
     * @param UpdateResourceStateParameters $params
     * @return bool
     * @throws SmartlingApiException
     */
    public function updateResourceState($resourceId, UpdateResourceStateParameters $params)
    {
        $endpoint = "resources/$resourceId/state";
        $requestData = $this->getDefaultRequestData('json', $params->exportToArray());

        return $this->sendRequest($endpoint, $requestData, self::HTTP_METHOD_PUT);
    }

}
