/*
 * AWS IoT Common V1.0.0
 * Copyright (C) 2020 Amazon.com, Inc. or its affiliates.  All Rights Reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy of
 * this software and associated documentation files (the "Software"), to deal in
 * the Software without restriction, including without limitation the rights to
 * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
 * the Software, and to permit persons to whom the Software is furnished to do so,
 * subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
 * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
 * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
 * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */

/**
 * @file aws_iot.h
 * @brief Provides routines and constants that are common to AWS IoT libraries.
 * This header should not be included in typical application code.
 */

#ifndef AWS_IOT_H_
#define AWS_IOT_H_

/* The config header is always included first. */
#include "iot_config.h"

/* Standard includes. */
#include <stdbool.h>
#include <stdint.h>

/* Platform types include. */
#include "types/iot_platform_types.h"

/* MQTT types include. */
#include "types/iot_mqtt_types.h"

/**
 * @brief The longest Thing Name accepted by AWS IoT, per the [AWS IoT
 * Service Limits](https://docs.aws.amazon.com/general/latest/gr/aws_service_limits.html#limits_iot).
 */
#define AWS_IOT_MAX_THING_NAME_LENGTH      ( 128 )

/**
 * @brief The common prefix of all AWS IoT MQTT topics.
 */
#define AWS_IOT_TOPIC_PREFIX               "$aws/things/"

/**
 * @brief The length of #AWS_IOT_TOPIC_PREFIX.
 */
#define AWS_IOT_TOPIC_PREFIX_LENGTH        ( ( uint16_t ) ( sizeof( AWS_IOT_TOPIC_PREFIX ) - 1 ) )

/**
 * @brief The suffix for an AWS IoT operation "accepted" topic.
 */
#define AWS_IOT_ACCEPTED_SUFFIX            "/accepted"

/**
 * @brief The length of #AWS_IOT_ACCEPTED_SUFFIX.
 */
#define AWS_IOT_ACCEPTED_SUFFIX_LENGTH     ( ( uint16_t ) ( sizeof( AWS_IOT_ACCEPTED_SUFFIX ) - 1 ) )

/**
 * @brief The suffix for an AWS IoT operation "rejected" topic.
 */
#define AWS_IOT_REJECTED_SUFFIX            "/rejected"

/**
 * @brief The length of #AWS_IOT_REJECTED_SUFFIX.
 */
#define AWS_IOT_REJECTED_SUFFIX_LENGTH     ( ( uint16_t ) ( sizeof( AWS_IOT_REJECTED_SUFFIX ) - 1 ) )

/**
 * @brief The JSON key used to represent client tokens for AWS IoT.
 */
#define AWS_IOT_CLIENT_TOKEN_KEY           "clientToken"

/**
 * @brief The length of #AWS_IOT_CLIENT_TOKEN_KEY.
 */
#define AWS_IOT_CLIENT_TOKEN_KEY_LENGTH    ( sizeof( AWS_IOT_CLIENT_TOKEN_KEY ) - 1 )

/**
 * @brief The length of the longest client token allowed by AWS IoT.
 */
#define AWS_IOT_CLIENT_TOKEN_MAX_LENGTH    ( 64 )

/**
 * @brief A flag to represent persistent subscriptions in a subscriptions
 * object.
 *
 * Its value is negative to distinguish it from valid subscription counts, which
 * are 0 or positive.
 */
#define AWS_IOT_PERSISTENT_SUBSCRIPTION    ( -1 )

/**
 * @brief Function pointer representing an MQTT blocking operation.
 *
 * Currently, this is used to represent @ref mqtt_function_subscribesync or
 * @ref mqtt_function_unsubscribesync.
 *
 * @param[in] mqttConnection The MQTT connection to use for the subscription.
 * @param[in] pSubscriptionList Pointer to the first element in the array of
 * subscriptions.
 * @param[in] subscriptionCount The number of elements in pSubscriptionList.
 * @param[in] flags Flags which modify the behavior of this function. See @ref mqtt_constants_flags.
 * Currently, flags are ignored by this function; this parameter is for
 * future-compatibility.
 * @param[in] timeoutMs If the MQTT server does not acknowledge the subscriptions within
 * this timeout in milliseconds, this function returns #IOT_MQTT_TIMEOUT.
 *
 * @return One of the following:
 * - #IOT_MQTT_SUCCESS
 * - #IOT_MQTT_NOT_INITIALIZED
 * - #IOT_MQTT_BAD_PARAMETER
 * - #IOT_MQTT_NO_MEMORY
 * - #IOT_MQTT_NETWORK_ERROR
 * - #IOT_MQTT_SCHEDULING_ERROR
 * - #IOT_MQTT_BAD_RESPONSE
 * - #IOT_MQTT_TIMEOUT
 * - #IOT_MQTT_SERVER_REFUSED
 */
typedef IotMqttError_t ( * AwsIotMqttFunction_t )( IotMqttConnection_t mqttConnection,
                                                   const IotMqttSubscription_t * pSubscriptionList,
                                                   size_t subscriptionCount,
                                                   uint32_t flags,
                                                   uint32_t timeoutMs );

/**
 * @brief Function pointer representing an MQTT library callback function.
 *
 * @param[in] pArgument Ignored.
 * @param[in] pMessage The received DELTA document (as an MQTT PUBLISH message).
 */
typedef void ( * AwsIotMqttCallbackFunction_t )( void * pArgument,
                                                 IotMqttCallbackParam_t * pMessage );

/**
 * @brief Enumerations representing each of the statuses that may be parsed
 * from a topic.
 */
typedef enum AwsIotStatus
{
    AWS_IOT_ACCEPTED = 0, /**< Operation accepted. */
    AWS_IOT_REJECTED = 1, /**< Operation rejected. */
    AWS_IOT_UNKNOWN = 2   /**< Unknown status (neither accepted nor rejected). */
} AwsIotStatus_t;

/**
 * @brief Information required to generate a topic for AWS IoT.
 */
typedef struct AwsIotTopicInfo
{
    const char * pThingName;                 /**< @brief The Thing Name associated with the operation. */
    size_t thingNameLength;                  /**< @brief The length of `pThingName`. */
    const char * pOperationName;             /**< @brief The operation name to place in the topic. */
    uint16_t operationNameLength;            /**< @brief The length of `pOperationName`. */
    uint16_t longestSuffixLength;            /**< @brief The length of longest suffix that will be placed at the end of the topic. */
    void * ( *mallocString )( size_t size ); /**< @brief Function used to allocate a string, if needed. */
} AwsIotTopicInfo_t;

/**
 * @brief Information needed to modify AWS IoT subscription topics.
 *
 * @warning The buffer passed as `pTopicFilterBase` must be large enough to
 * accommodate the "/accepted" and "/rejected" suffixes.
 */
typedef struct AwsIotSubscriptionInfo_t
{
    IotMqttConnection_t mqttConnection;            /**< @brief The MQTT connection to use. */
    AwsIotMqttCallbackFunction_t callbackFunction; /**< @brief Callback function for MQTT subscribe. */
    uint32_t timeout;                              /**< @brief Timeout for MQTT function. */

    /* Topic filter. */
    char * pTopicFilterBase;        /**< @brief Contains the base topic filter, without "/accepted" or "/rejected". */
    uint16_t topicFilterBaseLength; /**< @brief Length of the base topic filter. */
} AwsIotSubscriptionInfo_t;

/**
 * @brief Thing Name and length, used to match subscriptions.
 */
typedef struct AwsIotThingName
{
    const char * pThingName; /**< @brief Thing Name to compare. */
    size_t thingNameLength;  /**< @brief Length of `pThingName`. */
} AwsIotThingName_t;

/**
 * @brief Initializes the lists used by AWS IoT operations.
 *
 * @param[in] pPendingOperationsList The list that holds pending operations for
 * a library.
 * @param[in] pSubscriptionsList The list that holds subscriptions for a library.
 * @param[in] pPendingOperationsMutex The mutex that guards the pending operations
 * list.
 * @param[in] pSubscriptionsMutex The mutex that guards the subscriptions list.
 *
 * @return `true` if all initialization succeeded; `false` otherwise.
 */
bool AwsIot_InitLists( IotListDouble_t * pPendingOperationsList,
                       IotListDouble_t * pSubscriptionsList,
                       IotMutex_t * pPendingOperationsMutex,
                       IotMutex_t * pSubscriptionsMutex );

/**
 * @brief Checks that a Thing Name is valid for AWS IoT.
 *
 * @param[in] pThingName Thing Name to validate.
 * @param[in] thingNameLength Length of `pThingName`.
 *
 * @return `true` if `pThingName` is valid; `false` otherwise.
 */
bool AwsIot_ValidateThingName( const char * pThingName,
                               size_t thingNameLength );

/**
 * @brief Extracts the client token from a JSON document.
 *
 * The client token is used to differentiate AWS IoT operations. It is unique per
 * operation.
 *
 * @param[in] pJsonDocument The JSON document to parse.
 * @param[in] jsonDocumentLength The length of `pJsonDocument`.
 * @param[out] pClientToken Set to point to the client token in `pJsonDocument`.
 * @param[out] pClientTokenLength Set to the length of the client token.
 *
 * @return `true` if the client token was found; `false` otherwise. The output
 * parameters are only valid if this function returns `true`.
 */
bool AwsIot_GetClientToken( const char * pJsonDocument,
                            size_t jsonDocumentLength,
                            const char ** pClientToken,
                            size_t * pClientTokenLength );

/**
 * @brief Parse the Thing Name from an MQTT topic.
 *
 * @param[in] pTopicName The topic to parse.
 * @param[in] topicNameLength The length of `pTopicName`.
 * @param[out] pThingName Set to point to the Thing Name.
 * @param[out] pThingNameLength Set to the length of the Thing Name.
 *
 * @return `true` if a Thing Name was successfully parsed; `false` otherwise. The output
 * parameters are only valid if this function returns `true`.
 */
bool AwsIot_ParseThingName( const char * pTopicName,
                            uint16_t topicNameLength,
                            const char ** pThingName,
                            size_t * pThingNameLength );

/**
 * @brief Parse the operation status (accepted or rejected) from an MQTT topic.
 *
 * @param[in] pTopicName The topic to parse.
 * @param[in] topicNameLength The length of `pTopicName`.
 *
 * @return Any #AwsIotStatus_t.
 */
AwsIotStatus_t AwsIot_ParseStatus( const char * pTopicName,
                                   uint16_t topicNameLength );

/**
 * @brief Generate a topic to use for an AWS IoT operation.
 *
 * @param[in] pTopicInfo Information needed to generate an AWS IoT topic.
 * @param[in,out] pTopicBuffer Where to place the generated topic. An existing
 * buffer may be passed in. If `NULL`, this function will attempt to allocate a
 * new buffer.
 * @param[out] pOperationTopicLength Set to the length of the generated topic.
 *
 * @warning This function does not check the length of `pTopicBuffer`! Any provided
 * buffer must be long enough to accommodate the Thing Name, operation name, and
 * any other suffixes.
 *
 * @return `true` if the topic was successfully generated; `false` otherwise.
 * This function will always succeed when an input buffer is provided.
 */
bool AwsIot_GenerateOperationTopic( const AwsIotTopicInfo_t * pTopicInfo,
                                    char ** pTopicBuffer,
                                    uint16_t * pOperationTopicLength );

/**
 * @brief Add or remove subscriptions for AWS IoT operations.
 *
 * @param[in] mqttOperation Either @ref mqtt_function_subscribesync or
 * @ref mqtt_function_unsubscribesync.
 * @param[in] pSubscriptionInfo Information needed to process an MQTT
 * operation.
 *
 * @return See the return values of @ref mqtt_function_subscribesync or
 * @ref mqtt_function_unsubscribesync.
 */
IotMqttError_t AwsIot_ModifySubscriptions( AwsIotMqttFunction_t mqttOperation,
                                           const AwsIotSubscriptionInfo_t * pSubscriptionInfo );

#endif /* ifndef AWS_IOT_H_ */
