<?php

namespace Smartling\Vendor\Smartling\File;
use Smartling\Vendor\Psr\Log\LoggerInterface;
use Smartling\Vendor\Smartling\AuthApi\AuthApiInterface;
use Smartling\Vendor\Smartling\BaseApiAbstract;
use Smartling\Vendor\Smartling\Exceptions\SmartlingApiException;
use Smartling\Vendor\Smartling\File\Params\DownloadFileParameters;
use Smartling\Vendor\Smartling\File\Params\DownloadMultipleFilesParameters;
use Smartling\Vendor\Smartling\File\Params\ListFilesParameters;
use Smartling\Vendor\Smartling\File\Params\ExtendedListFilesParameters;
use Smartling\Vendor\Smartling\Parameters\ParameterInterface;
use Smartling\Vendor\Smartling\File\Params\UploadFileParameters;

/**
 * Class FileApi
 *
 * @package Smartling\Vendor\Smartling\Api
 */
class FileApi extends BaseApiAbstract
{
    public const FILE_NAME_NOT_SET = 'filename_not_set';
    const ENDPOINT_URL = 'https://api.smartling.com/files-api/v2/projects';

    /**
     * @param AuthApiInterface $authProvider
     * @param string $projectId
     * @param LoggerInterface $logger
     *
     * @return FileApi
     */
    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}
     */
    protected function processBodyOptions($requestData = []) {
        $opts = parent::processBodyOptions($requestData);

        if (!empty($opts['multipart'])) {
            foreach ($opts['multipart'] as &$data) {
                if ($data['name'] === 'file') {
                    $data['contents'] = $this->readFile($data['contents']);
                    $data['filename'] = array_key_exists('name', $opts) ? $opts['name'] : self::FILE_NAME_NOT_SET;
                }
            }
        }

        return $opts;
    }

    /**
     * {@inheritdoc}
     *
     * FileAPI requires [] syntax for array values in query string.
     */
    protected function getDefaultRequestData($parametersType, $parameters, $auth = true, $httpErrors = false) {
        $options = parent::getDefaultRequestData($parametersType, $parameters, $auth, $httpErrors);
        $queryStringAsStringNeeded = false;

        if ($parametersType == 'query') {
            foreach ($parameters as $key => $value) {
                if (\is_array($value)) {
                    $queryStringAsStringNeeded = true;

                    break;
                }
            }

            if ($queryStringAsStringNeeded) {
                $queryStringParts = [];

                foreach ($parameters as $key => $value) {
                    if (\is_array($value)) {
                        foreach ($value as $item) {
                            $queryStringParts[] = $key . "[]=" . \urlencode($item);
                        }
                    } else {
                        $queryStringParts[] = $key . "=" . \urlencode($value);
                    }
                }

                $options[$parametersType] = \implode("&", $queryStringParts);
            }
        }

        return $options;
    }

    /**
     * Uploads original source content to Smartling.
     *
     * @param string $realPath
     *   Real path to the file to read in into stream.
     * @param string $file_name
     *   Value that uniquely identifies the uploaded file. This ID can be used to
     *   request the file back.
     * @param string $file_type
     *   Unique identifier for the file type. Permitted values: android, ios,
     *   gettext, html, javaProperties, yaml, xliff, xml, json, docx, pptx, xlsx,
     *   idml, qt, resx, plaintext, cvs, stringsdict.
     * @param UploadFileParameters $params
     *   (optional) An associative array of additional options, with the following
     *   elements:
     *   - 'approved': Determines whether content in the file is authorized
     *     (available for translation) upon submitting the file via the Smartling
     *     Dashboard.
     *   - 'localesToApprove': This value, if set, authorizes strings for
     *     translation into specific locales.
     *
     * @return array
     *   Data about uploaded file.
     *
     * @throws \Smartling\Vendor\Smartling\Exceptions\SmartlingApiException
     *
     * @see http://docs.smartling.com/pages/API/FileAPI/Upload-File/
     */
    public function uploadFile($realPath, $file_name, $file_type, UploadFileParameters $params = null)
    {
        if (\is_null($params)) {
            $params = new UploadFileParameters();
        }
        $params = $params->exportToArray();
        $params['file'] = $realPath;
        $params['fileUri'] = $file_name;
        $params['fileType'] = $file_type;

        $requestData = $this->getDefaultRequestData('multipart', $params);

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

    /**
     * Downloads the requested file from Smartling.
     *
     * It is important to check the HTTP response status code. If Smartling finds
     * and returns the file normally, you will receive a 200 SUCCESS response.
     * If you receive any other response status code than 200, the requested
     * file will not be part of the response.
     *
     * @param string $fileUri
     *   Value that uniquely identifies the downloaded file.
     * @param string $locale
     *   A locale identifier as specified in project setup. If no locale
     *   is specified, original content is returned.
     * @param DownloadFileParameters $params
     *   (optional) An associative array of additional options, with the following
     *   elements:
     *   - 'retrievalType': Determines the desired format for the download. Could
     *     be one of following values:
     *     pending|published|pseudo|contextMatchingInstrumented
     *   - 'includeOriginalStrings': Boolean that specifies whether Smartling will
     *     return the original string or an empty string where no translation
     *     is available.
     *
     * @return string
     *   File content.
     *
     * @throws \Smartling\Vendor\Smartling\Exceptions\SmartlingApiException
     *
     * @see http://docs.smartling.com/pages/API/FileAPI/Download-File/
     */
    public function downloadFile($fileUri, $locale = '', DownloadFileParameters $params = null)
    {
        if ((!\is_string($locale)) || \strlen($locale) < 2) {
            $message = \vsprintf(
                'Invalid locale value got. Expected a string of at least 2 chars length, but got: %s',
                [
                    '' === $locale ? 'Empty string' : \var_export($locale, true)
                ]
            );

            throw new SmartlingApiException($message);
        }

        $params = (\is_null($params)) ? [] : $params->exportToArray();
        $params['fileUri'] = $fileUri;

        $requestData = $this->getDefaultRequestData('query', $params);
        unset($requestData['headers']['Accept']);

        return $this->sendRequest("locales/{$locale}/file", $requestData, self::HTTP_METHOD_GET, true);
    }

    public function downloadAllTranslationsOfFile($fileUri, DownloadFileParameters $params = null)
    {
        $params = (\is_null($params)) ? [] : $params->exportToArray();
        $params['fileUri'] = $fileUri;

        $requestData = $this->getDefaultRequestData('query', $params);
        unset($requestData['headers']['Accept']);

        return $this->sendRequest("locales/all/file/zip", $requestData, self::HTTP_METHOD_GET, true);
    }

    public function downloadMultipleTranslationsOfFiles(DownloadMultipleFilesParameters $params = null)
    {
        $params = (\is_null($params)) ? [] : $params->exportToArray();

        $requestData = $this->getDefaultRequestData('query', $params);
        unset($requestData['headers']['Accept']);

        return $this->sendRequest("/files/zip", $requestData, self::HTTP_METHOD_GET, true);
    }

    /**
     * Retrieves status about file translation progress.
     *
     * @param string $fileUri
     *   Value that uniquely identifies the file.
     * @param string $locale
     *   A locale identifier as specified in project setup.
     * @param ParameterInterface $params
     *   Additional parameters that might be added later
     *
     * @return array Data about request file.
     * Data about request file.
     * @throws SmartlingApiException
     * @see http://docs.smartling.com/pages/API/FileAPI/Status/
     */
    public function getStatus($fileUri, $locale, ParameterInterface $params = null)
    {
        $params = (\is_null($params)) ? [] : $params->exportToArray();
        $params['fileUri'] = $fileUri;

        $requestData = $this->getDefaultRequestData('query', $params);

        return $this->sendRequest("locales/$locale/file/status", $requestData, self::HTTP_METHOD_GET);
    }

    /**
     * Retrieves status about file translation progress for all locales.
     *
     * @param string $fileUri
     *   Value that uniquely identifies the file.
     * @param ParameterInterface $params
     *   Additional parameters that might be added later
     *
     * @return array Data about request file.
     * Data about request file.
     * @throws SmartlingApiException
     * @see http://docs.smartling.com/pages/API/v2/FileAPI/Status/All-Locales/
     */
    public function getStatusForAllLocales($fileUri, ParameterInterface $params = null)
    {
        $params = (\is_null($params)) ? [] : $params->exportToArray();
        $params['fileUri'] = $fileUri;

        $requestData = $this->getDefaultRequestData('query', $params);

        return $this->sendRequest("/file/status", $requestData, self::HTTP_METHOD_GET);
    }

    /**
     * Lists recently uploaded files. Returns a maximum of 500 files.
     *
     * @param ListFilesParameters $params
     *   (optional) An associative array of additional options, with the following
     *   elements:
     *   - 'uriMask': Returns only files with a URI containing a given string.
     *     Case is ignored and % is a wildcard. For example, the value .js%n will
     *     match strings.json and STRINGS.JSON but not json.strings.
     *   - 'fileTypes': Identifiers: android, ios, gettext, html, javaProperties,
     *     yaml, xliff, xml, json, docx, pptx, xlsx, idml, qt, resx, plaintext,
     *     cvs. File types are combined using the logical "OR".
     *   - 'lastUploadedAfter': Returns all files uploaded after the specified
     *     date.
     *   - 'lastUploadedBefore': Returns all files uploaded before the specified
     *     date.
     *   - 'offset': For result set returns, the offset is a number indicating the
     *     distance from the beginning of the list; for example, for a result set
     *     of "50" files, you can set the offset at 10 to return files 10 - 50.
     *   - 'limit': For result set returns, limits the number of files returned;
     *     for example, for a result set of 50 files, a limit of "10" would
     *     return files 0 - 10.
     *
     * @return array
     *   List of files objects.
     *
     * @throws \Smartling\Vendor\Smartling\Exceptions\SmartlingApiException
     *
     * @see http://docs.smartling.com/pages/API/FileAPI/List/
     */
    public function getList(ListFilesParameters $params = null)
    {
        $params = (\is_null($params)) ? [] : $params->exportToArray();

        $requestData = $this->getDefaultRequestData('query', $params);

        return $this->sendRequest('files/list', $requestData, self::HTTP_METHOD_GET);
    }

    /**
     * @param string $locale
     * @param ExtendedListFilesParameters|null $params
     *   same as ListFilesParameters, but with a new property 'status' which for now
     *   can only be of a value 'COMPLETED'
     * @return bool
     */
    public function getExtendedList($locale, ExtendedListFilesParameters $params = null)
    {
        $params = (\is_null($params)) ? [] : $params->exportToArray();

        $requestData = $this->getDefaultRequestData('query', $params);

        return $this->sendRequest("locales/$locale/files/list", $requestData, self::HTTP_METHOD_GET);
    }

    /**
     * Renames an uploaded file by changing the fileUri.
     *
     * After renaming the file, the file will only be identified by the new
     * fileUri you provide.
     *
     * @param string $fileUri
     *   Current value that uniquely identifies the file.
     * @param string $newFileUri
     *   The new value for fileUri. We recommend that you use file path + file
     *   name, similar to how version control systems identify the file.
     * @param ParameterInterface $params
     *
     * @return string Just empty string if everything was successfully.
     * Just empty string if everything was successfully.
     * @throws SmartlingApiException
     * @see http://docs.smartling.com/pages/API/FileAPI/Rename/
     */
    public function renameFile($fileUri, $newFileUri, ParameterInterface $params = null)
    {
        $params = (\is_null($params)) ? [] : $params->exportToArray();
        $params['fileUri'] = $fileUri;
        $params['newFileUri'] = $newFileUri;

        $requestData = $this->getDefaultRequestData('form_params', $params);

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

    /**
     * Removes the file from Smartling.
     *
     * The file will no longer be available for download. Any complete
     * translations for the file remain available for use within the system.
     * Smartling deletes files asynchronously and it typically takes a few minutes
     * to complete. While deleting a file, you can not upload a file with the
     * same fileUri.
     *
     * @param string $fileUri
     * @param ParameterInterface $params
     *
     * @return array
     * @throws SmartlingApiException
     */
    public function deleteFile($fileUri, ParameterInterface $params = null)
    {
        $params = (\is_null($params)) ? [] : $params->exportToArray();
        $params['fileUri'] = $fileUri;

        $requestData = $this->getDefaultRequestData('form_params', $params);

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

    /**
     * Import files form Service.
     *
     * @param string $locale
     *   The Smartling locale identifier for the language Smartling is importing.
     * @param string $fileUri
     *   The Smartling URI for file that contains the original language strings
     *   already uploaded to Smartling.
     * @param string $fileType
     *   The type of file used for imports. Valid values are: ios, android,
     *   gettext, javaProperties, xml, json, yaml, and csv.
     * @param string $fileRealPath
     *   Absolute path to the file on your local machine that contains the
     *   translated content,
     * @param string $translationState
     *   Value indicating the workflow state to import the translations into.
     *   Content will be imported into the language's default workflow.
     *   Could be 'PUBLISHED' or 'POST_TRANSLATION'.
     * @param boolean $overwrite
     *   (optional) An associative array of additional options, with the following
     *   elements:
     *   - 'overwrite': Boolean indicating whether or not to overwrite existing
     *     translations.
     *
     * @return string
     *
     * @throws \Smartling\Vendor\Smartling\Exceptions\SmartlingApiException
     *
     * @see http://docs.smartling.com/pages/API/Translation-Imports/
     */
    public function import($locale, $fileUri, $fileType, $fileRealPath, $translationState, $overwrite = false)
    {
        $params['fileUri'] = $fileUri;
        $params['fileType'] = $fileType;
        $params['file'] = $fileRealPath;
        $params['translationState'] = $translationState;
        $params['overwrite'] = $overwrite;

        $requestData = $this->getDefaultRequestData('multipart', $params);

        return $this->sendRequest("/locales/$locale/file/import", $requestData, self::HTTP_METHOD_POST);
    }

    /**
     * retrieve all statuses about file translations progress
     *
     * @param                    $fileUri
     * @param ParameterInterface $params
     *
     * @return array
     * @throws SmartlingApiException
     */
    public function getStatusAllLocales($fileUri, ParameterInterface $params = null)
    {
        $params = (\is_null($params)) ? [] : $params->exportToArray();
        $params['fileUri'] = $fileUri;

        $requestData = $this->getDefaultRequestData('query', $params);

        return $this->sendRequest('file/status', $requestData, self::HTTP_METHOD_GET);
    }


    /**
     * retrieve all statuses about file translations progress
     *
     * @param string $fileUri
     * @param ParameterInterface $params
     *
     * @return array
     * @throws SmartlingApiException
     */
    public function getLastModified($fileUri, ParameterInterface $params = null)
    {
        $params = (\is_null($params)) ? [] : $params->exportToArray();
        $params['fileUri'] = $fileUri;

        $requestData = $this->getDefaultRequestData('query', $params);

        return $this->sendRequest('file/last-modified', $requestData, self::HTTP_METHOD_GET);
    }

    /**
     * Requests last-modified value for all locales for file
     * @param string $fileUri
     *   Value that uniquely identifies the uploaded file. This ID can be used to
     *   request the file back.
     * @param ParameterInterface $params
     * @return array
     *   Data about uploaded file.
     *
     * @throws \Smartling\Vendor\Smartling\Exceptions\SmartlingApiException
     *
     * @see http://docs.smartling.com/pages/API/v2/FileAPI/Last-Modified/All-Locales/
     */
    public function lastModified($fileUri, ParameterInterface $params = null)
    {
        $result = $this->getLastModified($fileUri, $params);

        if (!\is_array($result) || !\array_key_exists('items', $result) || !\is_array($result['items'])) {
            throw new SmartlingApiException(\vsprintf(
                'No data found for file %s.', [
                    $fileUri,
                ]
            ));
        }

        /** @noinspection OffsetOperationsInspection */
        foreach ($result['items'] as &$item)
        {
            if (!isset($item['lastModified'])) {
                throw new SmartlingApiException('Property "lastModified" is not found.');
            }

            // Smartling returns UTC dates. Let's define this explicitly and
            // avoid issues when default php timezone is not UTC.
            $date = \DateTime::createFromFormat(self::PATTERN_DATE_TIME_ISO_8601, $item['lastModified'], new \DateTimeZone('UTC'));

            if (!($date instanceof \DateTime))
            {
                throw new SmartlingApiException(\vsprintf(
                    'Can\'t parse formatted time string: %s.', [
                        $item['lastModified'],
                    ]
                ));
            }

            $item['lastModified'] = $date;
        }

        return $result;
    }
}
