<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * 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.
 *
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Queue
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */

namespace MicrosoftAzure\Storage\Queue;

use MicrosoftAzure\Storage\Common\Internal\Authentication\SharedAccessSignatureAuthScheme;
use MicrosoftAzure\Storage\Common\Internal\Authentication\SharedKeyAuthScheme;
use MicrosoftAzure\Storage\Common\Internal\Authentication\TokenAuthScheme;
use MicrosoftAzure\Storage\Common\Internal\Http\HttpFormatter;
use MicrosoftAzure\Storage\Common\Internal\Middlewares\CommonRequestMiddleware;
use MicrosoftAzure\Storage\Common\Internal\Serialization\XmlSerializer;
use MicrosoftAzure\Storage\Common\Internal\ServiceRestProxy;
use MicrosoftAzure\Storage\Common\Internal\ServiceRestTrait;
use MicrosoftAzure\Storage\Common\Internal\StorageServiceSettings;
use MicrosoftAzure\Storage\Common\Internal\Utilities;
use MicrosoftAzure\Storage\Common\Internal\Validate;
use MicrosoftAzure\Storage\Common\LocationMode;
use MicrosoftAzure\Storage\Queue\Internal\IQueue;
use MicrosoftAzure\Storage\Queue\Internal\QueueResources as Resources;
use MicrosoftAzure\Storage\Queue\Models\CreateMessageOptions;
use MicrosoftAzure\Storage\Queue\Models\CreateMessageResult;
use MicrosoftAzure\Storage\Queue\Models\CreateQueueOptions;
use MicrosoftAzure\Storage\Queue\Models\GetQueueMetadataResult;
use MicrosoftAzure\Storage\Queue\Models\ListMessagesOptions;
use MicrosoftAzure\Storage\Queue\Models\ListMessagesResult;
use MicrosoftAzure\Storage\Queue\Models\ListQueuesOptions;
use MicrosoftAzure\Storage\Queue\Models\ListQueuesResult;
use MicrosoftAzure\Storage\Queue\Models\PeekMessagesOptions;
use MicrosoftAzure\Storage\Queue\Models\PeekMessagesResult;
use MicrosoftAzure\Storage\Queue\Models\QueueACL;
use MicrosoftAzure\Storage\Queue\Models\QueueMessage;
use MicrosoftAzure\Storage\Queue\Models\QueueServiceOptions;
use MicrosoftAzure\Storage\Queue\Models\UpdateMessageResult;

/**
 * This class constructs HTTP requests and receive HTTP responses for queue
 * service layer.
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Queue
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */
class QueueRestProxy extends ServiceRestProxy implements IQueue
{
    use ServiceRestTrait;

    /**
     * Builds a queue service object, it accepts the following
     * options:
     *
     * - http: (array) the underlying guzzle options. refer to
     *   http://docs.guzzlephp.org/en/latest/request-options.html for detailed available options
     * - middlewares: (mixed) the middleware should be either an instance of a sub-class that
     *   implements {@see MicrosoftAzure\Storage\Common\Middlewares\IMiddleware}, or a
     *   `callable` that follows the Guzzle middleware implementation convention
     *
     * Please refer to
     * https://azure.microsoft.com/en-us/documentation/articles/storage-configure-connection-string
     * for how to construct a connection string with storage account name/key, or with a shared
     * access signature (SAS Token).
     *
     * @param string $connectionString The configuration connection string.
     * @param array  $options          Array of options to pass to the service
     *
     * @return QueueRestProxy
     */
    public static function createQueueService(
        $connectionString,
        array $options = []
    ) {
        $settings = StorageServiceSettings::createFromConnectionString(
            $connectionString
        );

        $primaryUri = Utilities::tryAddUrlScheme(
            $settings->getQueueEndpointUri()
        );
        $secondaryUri = Utilities::tryAddUrlScheme(
            $settings->getQueueSecondaryEndpointUri()
        );

        $queueWrapper = new QueueRestProxy(
            $primaryUri,
            $secondaryUri,
            $settings->getName(),
            $options
        );

        // Getting authentication scheme
        if ($settings->hasSasToken()) {
            $authScheme = new SharedAccessSignatureAuthScheme(
                $settings->getSasToken()
            );
        } else {
            $authScheme = new SharedKeyAuthScheme(
                $settings->getName(),
                $settings->getKey()
            );
        }

        // Adding common request middleware
        $commonRequestMiddleware = new CommonRequestMiddleware(
            $authScheme,
            Resources::STORAGE_API_LATEST_VERSION,
            Resources::QUEUE_SDK_VERSION
        );
        $queueWrapper->pushMiddleware($commonRequestMiddleware);

        return $queueWrapper;
    }

    /**
     * Builds a queue service object, it accepts the following
     * options:
     *
     * - http: (array) the underlying guzzle options. refer to
     *   http://docs.guzzlephp.org/en/latest/request-options.html for detailed available options
     * - middlewares: (mixed) the middleware should be either an instance of a sub-class that
     *   implements {@see MicrosoftAzure\Storage\Common\Middlewares\IMiddleware}, or a
     *   `callable` that follows the Guzzle middleware implementation convention
     *
     * Please refer to
     * https://docs.microsoft.com/en-us/azure/storage/common/storage-auth-aad
     * for authenticate access to Azure blobs and queues using Azure Active Directory.
     *
     * @param string $token            The bearer token passed as reference.
     * @param string $connectionString The configuration connection string.
     * @param array  $options          Array of options to pass to the service
     *
     * @return QueueRestProxy
     */
    public static function createQueueServiceWithTokenCredential(
        &$token,
        $connectionString,
        array $options = []
    ) {
        $settings = StorageServiceSettings::createFromConnectionStringForTokenCredential(
            $connectionString
        );

        $primaryUri = Utilities::tryAddUrlScheme(
            $settings->getQueueEndpointUri()
        );

        $secondaryUri = Utilities::tryAddUrlScheme(
            $settings->getQueueSecondaryEndpointUri()
        );

        $queueWrapper = new QueueRestProxy(
            $primaryUri,
            $secondaryUri,
            $settings->getName(),
            $options
        );

        // Getting authentication scheme
        $authScheme = new TokenAuthScheme(
            $token
        );

        // Adding common request middleware
        $commonRequestMiddleware = new CommonRequestMiddleware(
            $authScheme,
            Resources::STORAGE_API_LATEST_VERSION,
            Resources::QUEUE_SDK_VERSION
        );
        $queueWrapper->pushMiddleware($commonRequestMiddleware);

        return $queueWrapper;
    }

    /**
     * Lists all queues in the storage account.
     *
     * @param ListQueuesOptions $options The optional list queue options.
     *
     * @return ListQueuesResult
     */
    public function listQueues(ListQueuesOptions $options = null)
    {
        return $this->listQueuesAsync($options)->wait();
    }

    /**
     * Creates promise to list all queues in the storage account.
     *
     * @param ListQueuesOptions $options The optional list queue options.
     *
     * @return \GuzzleHttp\Promise\PromiseInterface
     */
    public function listQueuesAsync(ListQueuesOptions $options = null)
    {
        $method      = Resources::HTTP_GET;
        $headers     = array();
        $postParams  = array();
        $queryParams = array();
        $path        = Resources::EMPTY_STRING;

        if (is_null($options)) {
            $options = new ListQueuesOptions();
        }

        $maxResults = $options->getMaxResults();
        $include    = $options->getIncludeMetadata();
        $include    = $include ? 'metadata' : null;
        $prefix     = $options->getPrefix();
        $marker     = $options->getNextMarker();

        $this->addOptionalQueryParam($queryParams, Resources::QP_COMP, 'list');
        $this->addOptionalQueryParam($queryParams, Resources::QP_PREFIX_LOWERCASE, $prefix);
        $this->addOptionalQueryParam($queryParams, Resources::QP_MARKER_LOWERCASE, $marker);
        $this->addOptionalQueryParam($queryParams, Resources::QP_INCLUDE, $include);
        $this->addOptionalQueryParam(
            $queryParams,
            Resources::QP_MAX_RESULTS_LOWERCASE,
            $maxResults
        );

        $dataSerializer = $this->dataSerializer;

        return $this->sendAsync(
            $method,
            $headers,
            $queryParams,
            $postParams,
            $path,
            Resources::STATUS_OK,
            Resources::EMPTY_STRING,
            $options
        )->then(function ($response) use ($dataSerializer) {
            $parsed = $dataSerializer->unserialize($response->getBody());
            return ListQueuesResult::create(
                $parsed,
                Utilities::getLocationFromHeaders($response->getHeaders())
            );
        }, null);
    }

    /**
     * Clears all messages from the queue.
     *
     * If a queue contains a large number of messages, Clear Messages may time out
     * before all messages have been deleted. In this case the Queue service will
     * return status code 500 (Internal Server Error), with the additional error
     * code OperationTimedOut. If the operation times out, the client should
     * continue to retry Clear Messages until it succeeds, to ensure that all
     * messages have been deleted.
     *
     * @param string              $queueName The name of the queue.
     * @param QueueServiceOptions $options   The optional parameters.
     *
     * @return void
     */
    public function clearMessages($queueName, QueueServiceOptions $options = null)
    {
        $this->clearMessagesAsync($queueName, $options)->wait();
    }

    /**
     * Creates promise to clear all messages from the queue.
     *
     * If a queue contains a large number of messages, Clear Messages may time out
     * before all messages have been deleted. In this case the Queue service will
     * return status code 500 (Internal Server Error), with the additional error
     * code OperationTimedOut. If the operation times out, the client should
     * continue to retry Clear Messages until it succeeds, to ensure that all
     * messages have been deleted.
     *
     * @param string              $queueName The name of the queue.
     * @param QueueServiceOptions $options   The optional parameters.
     *
     * @return \GuzzleHttp\Promise\PromiseInterface
     */
    public function clearMessagesAsync(
        $queueName,
        QueueServiceOptions $options = null
    ) {
        Validate::canCastAsString($queueName, 'queueName');
        Validate::notNullOrEmpty($queueName, 'queueName');

        $method      = Resources::HTTP_DELETE;
        $headers     = array();
        $postParams  = array();
        $queryParams = array();
        $path        = $queueName . '/messages';
        $body        = Resources::EMPTY_STRING;

        if (is_null($options)) {
            $options = new QueueServiceOptions();
        }

        $options->setLocationMode(LocationMode::PRIMARY_ONLY);

        return $this->sendAsync(
            $method,
            $headers,
            $queryParams,
            $postParams,
            $path,
            Resources::STATUS_NO_CONTENT,
            $body,
            $options
        );
    }

    /**
     * Adds a message to the queue and optionally sets a visibility timeout
     * for the message.
     *
     * @param string               $queueName   The name of the queue.
     * @param string               $messageText The message contents.
     * @param CreateMessageOptions $options     The optional parameters.
     *
     * @return CreateMessageResult
     */
    public function createMessage(
        $queueName,
        $messageText,
        CreateMessageOptions $options = null
    ) {
        return $this->createMessageAsync($queueName, $messageText, $options)->wait();
    }

    /**
     * Creates promise to add a message to the queue and optionally sets a
     * visibility timeout for the message.
     *
     * @param string               $queueName   The name of the queue.
     * @param string               $messageText The message contents.
     * @param CreateMessageOptions $options     The optional parameters.
     *
     * @return \GuzzleHttp\Promise\PromiseInterface
     */
    public function createMessageAsync(
        $queueName,
        $messageText,
        CreateMessageOptions $options = null
    ) {
        Validate::canCastAsString($queueName, 'queueName');
        Validate::notNullOrEmpty($queueName, 'queueName');
        Validate::canCastAsString($messageText, 'messageText');

        $method      = Resources::HTTP_POST;
        $headers     = array();
        $postParams  = array();
        $queryParams = array();
        $path        = $queueName . '/messages';
        $body        = Resources::EMPTY_STRING;
        $message     = new QueueMessage();
        $message->setMessageText($messageText);
        $body = $message->toXml($this->dataSerializer);


        if (is_null($options)) {
            $options = new CreateMessageOptions();
        }

        $this->addOptionalHeader(
            $headers,
            Resources::CONTENT_TYPE,
            Resources::URL_ENCODED_CONTENT_TYPE
        );

        $visibility = $options->getVisibilityTimeoutInSeconds();
        $timeToLive = $options->getTimeToLiveInSeconds();

        $this->addOptionalQueryParam(
            $queryParams,
            Resources::QP_VISIBILITY_TIMEOUT,
            $visibility
        );
        $this->addOptionalQueryParam(
            $queryParams,
            Resources::QP_MESSAGE_TTL,
            $timeToLive
        );

        $options->setLocationMode(LocationMode::PRIMARY_ONLY);

        $dataSerializer = $this->dataSerializer;

        return $this->sendAsync(
            $method,
            $headers,
            $queryParams,
            $postParams,
            $path,
            Resources::STATUS_CREATED,
            $body,
            $options
        )->then(function ($response) use ($dataSerializer) {
            $parsed = $dataSerializer->unserialize($response->getBody());
            return CreateMessageResult::create($parsed);
        }, null);
    }

    /**
     * Creates a new queue under the storage account.
     *
     * @param string                    $queueName The queue name.
     * @param Models\CreateQueueOptions  $options   The Optional parameters.
     *
     * @return void
     */
    public function createQueue(
        $queueName,
        Models\CreateQueueOptions $options = null
    ) {
        $this->createQueueAsync($queueName, $options)->wait();
    }

    /**
     * Creates promise to create a new queue under the storage account.
     *
     * @param string                     $queueName The queue name.
     * @param Models\CreateQueueOptions  $options   The Optional parameters.
     *
     * @return \GuzzleHttp\Promise\PromiseInterface
     */
    public function createQueueAsync(
        $queueName,
        Models\CreateQueueOptions $options = null
    ) {
        Validate::canCastAsString($queueName, 'queueName');
        Validate::notNullOrEmpty($queueName, 'queueName');

        $method      = Resources::HTTP_PUT;
        $headers     = array();
        $postParams  = array();
        $queryParams = array();
        $path        = $queueName;

        if (is_null($options)) {
            $options = new CreateQueueOptions();
        }

        $metadata = $options->getMetadata();
        $headers  = $this->generateMetadataHeaders($metadata);

        $options->setLocationMode(LocationMode::PRIMARY_ONLY);

        return $this->sendAsync(
            $method,
            $headers,
            $queryParams,
            $postParams,
            $path,
            array(Resources::STATUS_CREATED, Resources::STATUS_NO_CONTENT),
            Resources::EMPTY_STRING,
            $options
        );
    }

    /**
     * Deletes a specified message from the queue.
     *
     * @param string              $queueName  The name of the queue.
     * @param string              $messageId  The id of the message.
     * @param string              $popReceipt The valid pop receipt value returned
     * from an earlier call to the Get Messages or Update Message operation.
     * @param QueueServiceOptions $options    The optional parameters.
     *
     * @return void
     */
    public function deleteMessage(
        $queueName,
        $messageId,
        $popReceipt,
        QueueServiceOptions $options = null
    ) {
        $this->deleteMessageAsync(
            $queueName,
            $messageId,
            $popReceipt,
            $options
        )->wait();
    }

    /**
     * Creates promise to delete a specified message from the queue.
     *
     * @param string              $queueName  The name of the queue.
     * @param string              $messageId  The id of the message.
     * @param string              $popReceipt The valid pop receipt value returned
     * from an earlier call to the Get Messages or Update Message operation.
     * @param QueueServiceOptions $options    The optional parameters.
     *
     * @return \GuzzleHttp\Promise\PromiseInterface
     */
    public function deleteMessageAsync(
        $queueName,
        $messageId,
        $popReceipt,
        QueueServiceOptions $options = null
    ) {
        Validate::canCastAsString($queueName, 'queueName');
        Validate::notNullOrEmpty($queueName, 'queueName');
        Validate::canCastAsString($messageId, 'messageId');
        Validate::notNullOrEmpty($messageId, 'messageId');
        Validate::canCastAsString($popReceipt, 'popReceipt');
        Validate::notNullOrEmpty($popReceipt, 'popReceipt');

        $method      = Resources::HTTP_DELETE;
        $headers     = array();
        $postParams  = array();
        $queryParams = array();
        $path        = $queueName . '/messages/' . $messageId;
        $body        = Resources::EMPTY_STRING;

        if (is_null($options)) {
            $options = new QueueServiceOptions();
        }

        $this->addOptionalQueryParam(
            $queryParams,
            Resources::QP_POPRECEIPT,
            $popReceipt
        );

        $options->setLocationMode(LocationMode::PRIMARY_ONLY);

        return $this->sendAsync(
            $method,
            $headers,
            $queryParams,
            $postParams,
            $path,
            Resources::STATUS_NO_CONTENT,
            $body,
            $options
        );
    }

    /**
     * Deletes a queue.
     *
     * @param string              $queueName The queue name.
     * @param QueueServiceOptions $options   The optional parameters.
     *
     * @return void
     */
    public function deleteQueue($queueName, QueueServiceOptions $options = null)
    {
        $this->deleteQueueAsync($queueName, $options)->wait();
    }

    /**
     * Creates promise to delete a queue.
     *
     * @param string              $queueName The queue name.
     * @param QueueServiceOptions $options   The optional parameters.
     *
     * @return \GuzzleHttp\Promise\PromiseInterface
     */
    public function deleteQueueAsync(
        $queueName,
        QueueServiceOptions $options = null
    ) {
        Validate::canCastAsString($queueName, 'queueName');
        Validate::notNullOrEmpty($queueName, 'queueName');

        $method      = Resources::HTTP_DELETE;
        $headers     = array();
        $postParams  = array();
        $queryParams = array();
        $path        = $queueName;

        if (is_null($options)) {
            $options = new QueueServiceOptions();
        }

        $options->setLocationMode(LocationMode::PRIMARY_ONLY);

        return $this->sendAsync(
            $method,
            $headers,
            $queryParams,
            $postParams,
            $path,
            Resources::STATUS_NO_CONTENT,
            Resources::EMPTY_STRING,
            $options
        );
    }

    /**
     * Returns queue properties, including user-defined metadata.
     *
     * @param string              $queueName The queue name.
     * @param QueueServiceOptions $options   The optional parameters.
     *
     * @return Models\GetQueueMetadataResult
     */
    public function getQueueMetadata($queueName, QueueServiceOptions $options = null)
    {
        return $this->getQueueMetadataAsync($queueName, $options)->wait();
    }

    /**
     * Creates promise to return queue properties, including user-defined metadata.
     *
     * @param string              $queueName The queue name.
     * @param QueueServiceOptions $options   The optional parameters.
     *
     * @return \GuzzleHttp\Promise\PromiseInterface
     */
    public function getQueueMetadataAsync(
        $queueName,
        QueueServiceOptions $options = null
    ) {
        Validate::canCastAsString($queueName, 'queueName');
        Validate::notNullOrEmpty($queueName, 'queueName');

        $method      = Resources::HTTP_GET;
        $headers     = array();
        $postParams  = array();
        $queryParams = array();
        $path        = $queueName;
        $body        = Resources::EMPTY_STRING;

        if (is_null($options)) {
            $options = new QueueServiceOptions();
        }

        $this->addOptionalQueryParam($queryParams, Resources::QP_COMP, 'metadata');

        return $this->sendAsync(
            $method,
            $headers,
            $queryParams,
            $postParams,
            $path,
            Resources::STATUS_OK,
            $body,
            $options
        )->then(function ($response) {
            $responseHeaders = HttpFormatter::formatHeaders($response->getHeaders());
            $metadata = Utilities::getMetadataArray($responseHeaders);
            $maxCount = intval(
                Utilities::tryGetValue(
                    $responseHeaders,
                    Resources::X_MS_APPROXIMATE_MESSAGES_COUNT
                )
            );

            return new GetQueueMetadataResult($maxCount, $metadata);
        }, null);
    }

    /**
     * Lists all messages in the queue.
     *
     * @param string              $queueName The queue name.
     * @param ListMessagesOptions $options   The optional parameters.
     *
     * @return Models\ListMessagesResult
     */
    public function listMessages($queueName, ListMessagesOptions $options = null)
    {
        return $this->listMessagesAsync($queueName, $options)->wait();
    }

    /**
     * Creates promise to list all messages in the queue.
     *
     * @param string              $queueName The queue name.
     * @param ListMessagesOptions $options   The optional parameters.
     *
     * @return \GuzzleHttp\Promise\PromiseInterface
     */
    public function listMessagesAsync(
        $queueName,
        ListMessagesOptions $options = null
    ) {
        Validate::canCastAsString($queueName, 'queueName');
        Validate::notNullOrEmpty($queueName, 'queueName');

        $method      = Resources::HTTP_GET;
        $headers     = array();
        $queryParams = array();
        $postParams  = array();
        $path        = $queueName . '/messages';

        if (is_null($options)) {
            $options = new ListMessagesOptions();
        }

        $messagesCount = $options->getNumberOfMessages();
        $visibility    = $options->getVisibilityTimeoutInSeconds();

        $this->addOptionalQueryParam(
            $queryParams,
            Resources::QP_NUM_OF_MESSAGES,
            $messagesCount
        );
        $this->addOptionalQueryParam(
            $queryParams,
            Resources::QP_VISIBILITY_TIMEOUT,
            $visibility
        );

        $dataSerializer = $this->dataSerializer;

        $options->setLocationMode(LocationMode::PRIMARY_ONLY);

        return $this->sendAsync(
            $method,
            $headers,
            $queryParams,
            $postParams,
            $path,
            Resources::STATUS_OK,
            Resources::EMPTY_STRING,
            $options
        )->then(function ($response) use ($dataSerializer) {
            $parsed = $dataSerializer->unserialize($response->getBody());
            return ListMessagesResult::create($parsed);
        }, null);
    }

    /**
     * Retrieves a message from the front of the queue, without changing
     * the message visibility.
     *
     * @param string              $queueName The queue name.
     * @param PeekMessagesOptions $options   The optional parameters.
     *
     * @return Models\PeekMessagesResult
     */
    public function peekMessages($queueName, PeekMessagesOptions $options = null)
    {
        return $this->peekMessagesAsync($queueName, $options)->wait();
    }

    /**
     * Creates promise to retrieve a message from the front of the queue,
     * without changing the message visibility.
     *
     * @param string              $queueName The queue name.
     * @param PeekMessagesOptions $options   The optional parameters.
     *
     * @return \GuzzleHttp\Promise\PromiseInterface
     */
    public function peekMessagesAsync(
        $queueName,
        PeekMessagesOptions $options = null
    ) {
        Validate::canCastAsString($queueName, 'queueName');
        Validate::notNullOrEmpty($queueName, 'queueName');

        $method      = Resources::HTTP_GET;
        $headers     = array();
        $queryParams = array();
        $postParams  = array();
        $path        = $queueName . '/messages';

        if (is_null($options)) {
            $options = new PeekMessagesOptions();
        }

        $messagesCount = $options->getNumberOfMessages();

        $this->addOptionalQueryParam($queryParams, Resources::QP_PEEK_ONLY, 'true');
        $this->addOptionalQueryParam(
            $queryParams,
            Resources::QP_NUM_OF_MESSAGES,
            $messagesCount
        );

        $dataSerializer = $this->dataSerializer;

        return $this->sendAsync(
            $method,
            $headers,
            $queryParams,
            $postParams,
            $path,
            Resources::STATUS_OK,
            Resources::EMPTY_STRING,
            $options
        )->then(function ($response) use ($dataSerializer) {
            $parsed = $dataSerializer->unserialize($response->getBody());
            return PeekMessagesResult::create($parsed);
        }, null);
    }

    /**
     * Sets user-defined metadata on the queue. To delete queue metadata, call
     * this API without specifying any metadata in $metadata.
     *
     * @param string              $queueName The queue name.
     * @param array               $metadata  The metadata array.
     * @param QueueServiceOptions $options   The optional parameters.
     *
     * @return void
     */
    public function setQueueMetadata(
        $queueName,
        array $metadata = null,
        QueueServiceOptions $options = null
    ) {
        $this->setQueueMetadataAsync($queueName, $metadata, $options)->wait();
    }

    /**
     * Creates promise to set user-defined metadata on the queue. To delete
     * queue metadata, call this API without specifying any metadata in $metadata.
     *
     * @param string              $queueName The queue name.
     * @param array               $metadata  The metadata array.
     * @param QueueServiceOptions $options   The optional parameters.
     *
     * @return \GuzzleHttp\Promise\PromiseInterface
     */
    public function setQueueMetadataAsync(
        $queueName,
        array $metadata = null,
        QueueServiceOptions $options = null
    ) {
        Validate::canCastAsString($queueName, 'queueName');
        Validate::notNullOrEmpty($queueName, 'queueName');
        Utilities::validateMetadata($metadata);

        $method      = Resources::HTTP_PUT;
        $headers     = array();
        $queryParams = array();
        $postParams  = array();
        $path        = $queueName;
        $body        = Resources::EMPTY_STRING;

        if (is_null($options)) {
            $options = new QueueServiceOptions();
        }

        $this->addOptionalQueryParam($queryParams, Resources::QP_COMP, 'metadata');

        $metadataHeaders = $this->generateMetadataHeaders($metadata);
        $headers         = $metadataHeaders;

        $options->setLocationMode(LocationMode::PRIMARY_ONLY);

        return $this->sendAsync(
            $method,
            $headers,
            $queryParams,
            $postParams,
            $path,
            Resources::STATUS_NO_CONTENT,
            $body,
            $options
        );
    }

    /**
     * Updates the visibility timeout of a message and/or the message contents.
     *
     * @param string              $queueName                  The queue name.
     * @param string              $messageId                  The id of the message.
     * @param string              $popReceipt                 The valid pop receipt
     * value returned from an earlier call to the Get Messages or Update Message
     * operation.
     * @param string              $messageText                The message contents.
     * @param int                 $visibilityTimeoutInSeconds Specifies the new
     * visibility timeout value, in seconds, relative to server time.
     * The new value must be larger than or equal to 0, and cannot be larger
     * than 7 days. The visibility timeout of a message cannot be set to a value
     * later than the expiry time. A message can be updated until it has been
     * deleted or has expired.
     * @param QueueServiceOptions $options                    The optional
     * parameters.
     *
     * @return Models\UpdateMessageResult
     */
    public function updateMessage(
        $queueName,
        $messageId,
        $popReceipt,
        $messageText,
        $visibilityTimeoutInSeconds,
        QueueServiceOptions $options = null
    ) {
        return $this->updateMessageAsync(
            $queueName,
            $messageId,
            $popReceipt,
            $messageText,
            $visibilityTimeoutInSeconds,
            $options
        )->wait();
    }

    /**
     * Creates promise to update the visibility timeout of a message and/or the
     * message contents.
     *
     * @param string              $queueName                  The queue name.
     * @param string              $messageId                  The id of the message.
     * @param string              $popReceipt                 The valid pop receipt
     * value returned from an earlier call to the Get Messages or Update Message
     * operation.
     * @param string              $messageText                The message contents.
     * @param int                 $visibilityTimeoutInSeconds Specifies the new
     * visibility timeout value, in seconds, relative to server time.
     * The new value must be larger than or equal to 0, and cannot be larger
     * than 7 days. The visibility timeout of a message cannot be set to a value
     * later than the expiry time. A message can be updated until it has been
     * deleted or has expired.
     * @param QueueServiceOptions $options                    The optional
     * parameters.
     *
     * @return \GuzzleHttp\Promise\PromiseInterface
     */
    public function updateMessageAsync(
        $queueName,
        $messageId,
        $popReceipt,
        $messageText,
        $visibilityTimeoutInSeconds,
        QueueServiceOptions $options = null
    ) {
        Validate::canCastAsString($queueName, 'queueName');
        Validate::notNullOrEmpty($queueName, 'queueName');
        Validate::canCastAsString($messageId, 'messageId');
        Validate::notNullOrEmpty($messageId, 'messageId');
        Validate::canCastAsString($popReceipt, 'popReceipt');
        Validate::notNullOrEmpty($popReceipt, 'popReceipt');
        Validate::canCastAsString($messageText, 'messageText');
        Validate::isInteger(
            $visibilityTimeoutInSeconds,
            'visibilityTimeoutInSeconds'
        );
        Validate::notNull(
            $visibilityTimeoutInSeconds,
            'visibilityTimeoutInSeconds'
        );

        $method      = Resources::HTTP_PUT;
        $headers     = array();
        $postParams  = array();
        $queryParams = array();
        $path        = $queueName . '/messages' . '/' . $messageId;
        $body        = Resources::EMPTY_STRING;

        if (is_null($options)) {
            $options = new QueueServiceOptions();
        }

        $this->addOptionalQueryParam(
            $queryParams,
            Resources::QP_VISIBILITY_TIMEOUT,
            $visibilityTimeoutInSeconds
        );
        $this->addOptionalQueryParam(
            $queryParams,
            Resources::QP_POPRECEIPT,
            $popReceipt
        );

        if (!empty($messageText)) {
            $this->addOptionalHeader(
                $headers,
                Resources::CONTENT_TYPE,
                Resources::URL_ENCODED_CONTENT_TYPE
            );

            $message = new QueueMessage();
            $message->setMessageText($messageText);
            $body = $message->toXml($this->dataSerializer);
        }

        $options->setLocationMode(LocationMode::PRIMARY_ONLY);

        return $this->sendAsync(
            $method,
            $headers,
            $queryParams,
            $postParams,
            $path,
            Resources::STATUS_NO_CONTENT,
            $body,
            $options
        )->then(function ($response) {
            $responseHeaders = HttpFormatter::formatHeaders($response->getHeaders());
            return UpdateMessageResult::create($responseHeaders);
        }, null);
    }

    /**
     * Gets the access control list (ACL)
     *
     * @param string                     $queue   The queue name.
     * @param Models\QueueServiceOptions $options The optional parameters.
     *
     * @return Models\QueueACL
     *
     * @see https://docs.microsoft.com/en-us/rest/api/storageservices/fileservices/get-queue-acl
     */
    public function getQueueAcl(
        $queue,
        Models\QueueServiceOptions $options = null
    ) {
        return $this->getQueueAclAsync($queue, $options)->wait();
    }

    /**
     * Creates the promise to gets the access control list (ACL)
     *
     * @param string                     $queue   The queue name.
     * @param Models\QueueServiceOptions $options The optional parameters.
     *
     * @return \GuzzleHttp\Promise\PromiseInterface
     *
     * @see https://docs.microsoft.com/en-us/rest/api/storageservices/fileservices/get-queue-acl
     */
    public function getQueueAclAsync(
        $queue,
        Models\QueueServiceOptions $options = null
    ) {
        Validate::canCastAsString($queue, 'queue');

        $method      = Resources::HTTP_GET;
        $headers     = array();
        $postParams  = array();
        $queryParams = array();
        $statusCode  = Resources::STATUS_OK;
        $path        = $queue;

        if (is_null($options)) {
            $options = new QueueServiceOptions();
        }

        $this->addOptionalQueryParam(
            $queryParams,
            Resources::QP_COMP,
            'acl'
        );

        $dataSerializer = $this->dataSerializer;

        $promise = $this->sendAsync(
            $method,
            $headers,
            $queryParams,
            $postParams,
            $path,
            Resources::STATUS_OK,
            Resources::EMPTY_STRING,
            $options
        );

        return $promise->then(function ($response) use ($dataSerializer) {
            $parsed       = $dataSerializer->unserialize($response->getBody());
            return QueueACL::create($parsed);
        }, null);
    }

    /**
     * Sets the ACL.
     *
     * @param string                     $queue   name
     * @param Models\QueueACL            $acl     access control list for Queue
     * @param Models\QueueServiceOptions $options optional parameters
     *
     * @return void
     *
     * @see https://docs.microsoft.com/en-us/rest/api/storageservices/fileservices/set-queue-acl
     */
    public function setQueueAcl(
        $queue,
        Models\QueueACL $acl,
        Models\QueueServiceOptions $options = null
    ) {
        $this->setQueueAclAsync($queue, $acl, $options)->wait();
    }

    /**
     * Creates promise to set the ACL
     *
     * @param string                     $queue   name
     * @param Models\QueueACL            $acl     access control list for Queue
     * @param Models\QueueServiceOptions $options optional parameters
     *
     * @return \GuzzleHttp\Promise\PromiseInterface
     *
     * @see https://docs.microsoft.com/en-us/rest/api/storageservices/fileservices/set-queue-acl
     */
    public function setQueueAclAsync(
        $queue,
        Models\QueueACL $acl,
        Models\QueueServiceOptions $options = null
    ) {
        Validate::canCastAsString($queue, 'queue');
        Validate::notNullOrEmpty($acl, 'acl');

        $method      = Resources::HTTP_PUT;
        $headers     = array();
        $postParams  = array();
        $queryParams = array();
        $body        = $acl->toXml($this->dataSerializer);
        $path        = $queue;

        if (is_null($options)) {
            $options = new QueueServiceOptions();
        }

        $this->addOptionalQueryParam(
            $queryParams,
            Resources::QP_COMP,
            'acl'
        );

        $options->setLocationMode(LocationMode::PRIMARY_ONLY);

        return $this->sendAsync(
            $method,
            $headers,
            $queryParams,
            $postParams,
            $path,
            Resources::STATUS_NO_CONTENT,
            $body,
            $options
        );
    }
}
