#
#/*
# * Copyright 2010-2017 Amazon.com, Inc. or its affiliates. All Rights Reserved.
# *
# * Licensed under the Apache License, Version 2.0 (the "License").
# * You may not use this file except in compliance with the License.
# * A copy of the License is located at
# *
# *  http://aws.amazon.com/apache2.0
# *
# * or in the "license" file accompanying this file. This file 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.
# */

from AWSIoTPythonSDK.core.util.providers import CertificateCredentialsProvider
from AWSIoTPythonSDK.core.util.providers import CiphersProvider
from AWSIoTPythonSDK.core.util.providers import IAMCredentialsProvider
from AWSIoTPythonSDK.core.util.providers import EndpointProvider
from AWSIoTPythonSDK.core.jobs.thingJobManager import jobExecutionTopicType
from AWSIoTPythonSDK.core.jobs.thingJobManager import jobExecutionTopicReplyType
from AWSIoTPythonSDK.core.protocol.mqtt_core import MqttCore
import AWSIoTPythonSDK.core.shadow.shadowManager as shadowManager
import AWSIoTPythonSDK.core.shadow.deviceShadow as deviceShadow
import AWSIoTPythonSDK.core.jobs.thingJobManager as thingJobManager

# Constants
# - Protocol types:
MQTTv3_1 = 3
MQTTv3_1_1 = 4

DROP_OLDEST = 0
DROP_NEWEST = 1

class AWSIoTMQTTClient:

    def __init__(self, clientID, protocolType=MQTTv3_1_1, useWebsocket=False, cleanSession=True):
        """

        The client class that connects to and accesses AWS IoT over MQTT v3.1/3.1.1.

        The following connection types are available:

        - TLSv1.2 Mutual Authentication

        X.509 certificate-based secured MQTT connection to AWS IoT
        
        - Websocket SigV4

        IAM credential-based secured MQTT connection over Websocket to AWS IoT

        It provides basic synchronous MQTT operations in the classic MQTT publish-subscribe 
        model, along with configurations of on-top features:

        - Auto reconnect/resubscribe

        - Progressive reconnect backoff

        - Offline publish requests queueing with draining

        **Syntax**

        .. code:: python

          import AWSIoTPythonSDK.MQTTLib as AWSIoTPyMQTT

          # Create an AWS IoT MQTT Client using TLSv1.2 Mutual Authentication
          myAWSIoTMQTTClient = AWSIoTPyMQTT.AWSIoTMQTTClient("testIoTPySDK")
          # Create an AWS IoT MQTT Client using Websocket SigV4
          myAWSIoTMQTTClient = AWSIoTPyMQTT.AWSIoTMQTTClient("testIoTPySDK", useWebsocket=True)

        **Parameters**

        *clientID* - String that denotes the client identifier used to connect to AWS IoT.
        If empty string were provided, client id for this connection will be randomly generated 
        n server side.

        *protocolType* - MQTT version in use for this connection. Could be :code:`AWSIoTPythonSDK.MQTTLib.MQTTv3_1` or :code:`AWSIoTPythonSDK.MQTTLib.MQTTv3_1_1`

        *useWebsocket* - Boolean that denotes enabling MQTT over Websocket SigV4 or not.

        **Returns**

        :code:`AWSIoTPythonSDK.MQTTLib.AWSIoTMQTTClient` object

        """
        self._mqtt_core = MqttCore(clientID, cleanSession, protocolType, useWebsocket)

    # Configuration APIs
    def configureLastWill(self, topic, payload, QoS, retain=False):
        """
        **Description**

        Used to configure the last will topic, payload and QoS of the client. Should be called before connect.

        **Syntax**

        .. code:: python

          myAWSIoTMQTTClient.configureLastWill("last/Will/Topic", "lastWillPayload", 0)

        **Parameters**

        *topic* - Topic name that last will publishes to.

        *payload* - Payload to publish for last will.

        *QoS* - Quality of Service. Could be 0 or 1.

        **Returns**

        None

        """
        self._mqtt_core.configure_last_will(topic, payload, QoS, retain)

    def clearLastWill(self):
        """
        **Description**

        Used to clear the last will configuration that is previously set through configureLastWill.

        **Syntax**

        .. code:: python

          myAWSIoTMQTTClient.clearLastWill()

        **Parameter**

        None

        **Returns**

        None
        
        """
        self._mqtt_core.clear_last_will()

    def configureEndpoint(self, hostName, portNumber):
        """
        **Description**

        Used to configure the host name and port number the client tries to connect to. Should be called
        before connect.

        **Syntax**

        .. code:: python

          myAWSIoTMQTTClient.configureEndpoint("random.iot.region.amazonaws.com", 8883)

        **Parameters**

        *hostName* - String that denotes the host name of the user-specific AWS IoT endpoint.

        *portNumber* - Integer that denotes the port number to connect to. Could be :code:`8883` for
        TLSv1.2 Mutual Authentication or :code:`443` for Websocket SigV4 and TLSv1.2 Mutual Authentication
        with ALPN extension.

        **Returns**

        None

        """
        endpoint_provider = EndpointProvider()
        endpoint_provider.set_host(hostName)
        endpoint_provider.set_port(portNumber)
        self._mqtt_core.configure_endpoint(endpoint_provider)
        if portNumber == 443 and not self._mqtt_core.use_wss():
            self._mqtt_core.configure_alpn_protocols()

    def configureIAMCredentials(self, AWSAccessKeyID, AWSSecretAccessKey, AWSSessionToken=""):
        """
        **Description**

        Used to configure/update the custom IAM credentials for Websocket SigV4 connection to 
        AWS IoT. Should be called before connect.

        **Syntax**

        .. code:: python

          myAWSIoTMQTTClient.configureIAMCredentials(obtainedAccessKeyID, obtainedSecretAccessKey, obtainedSessionToken)

        .. note::

          Hard-coding credentials into custom script is NOT recommended. Please use AWS Cognito identity service
          or other credential provider.

        **Parameters**

        *AWSAccessKeyID* - AWS Access Key Id from user-specific IAM credentials.

        *AWSSecretAccessKey* - AWS Secret Access Key from user-specific IAM credentials.

        *AWSSessionToken* - AWS Session Token for temporary authentication from STS.

        **Returns**

        None

        """
        iam_credentials_provider = IAMCredentialsProvider()
        iam_credentials_provider.set_access_key_id(AWSAccessKeyID)
        iam_credentials_provider.set_secret_access_key(AWSSecretAccessKey)
        iam_credentials_provider.set_session_token(AWSSessionToken)
        self._mqtt_core.configure_iam_credentials(iam_credentials_provider)

    def configureCredentials(self, CAFilePath, KeyPath="", CertificatePath="", Ciphers=None):  # Should be good for MutualAuth certs config and Websocket rootCA config
        """
        **Description**

        Used to configure the rootCA, private key and certificate files. Should be called before connect.

        **Syntax**

        .. code:: python

          myAWSIoTMQTTClient.configureCredentials("PATH/TO/ROOT_CA", "PATH/TO/PRIVATE_KEY", "PATH/TO/CERTIFICATE")

        **Parameters**

        *CAFilePath* - Path to read the root CA file. Required for all connection types.

        *KeyPath* - Path to read the private key. Required for X.509 certificate based connection.

        *CertificatePath* - Path to read the certificate. Required for X.509 certificate based connection.

        *Ciphers* - String of colon split SSL ciphers to use.  If not passed, default ciphers will be used.

        **Returns**

        None

        """
        cert_credentials_provider = CertificateCredentialsProvider()
        cert_credentials_provider.set_ca_path(CAFilePath)
        cert_credentials_provider.set_key_path(KeyPath)
        cert_credentials_provider.set_cert_path(CertificatePath)

        cipher_provider = CiphersProvider()
        cipher_provider.set_ciphers(Ciphers)

        self._mqtt_core.configure_cert_credentials(cert_credentials_provider, cipher_provider)

    def configureAutoReconnectBackoffTime(self, baseReconnectQuietTimeSecond, maxReconnectQuietTimeSecond, stableConnectionTimeSecond):
        """
        **Description**

        Used to configure the auto-reconnect backoff timing. Should be called before connect.

        **Syntax**

        .. code:: python

          # Configure the auto-reconnect backoff to start with 1 second and use 128 seconds as a maximum back off time.
          # Connection over 20 seconds is considered stable and will reset the back off time back to its base.
          myAWSIoTMQTTClient.configureAutoReconnectBackoffTime(1, 128, 20)

        **Parameters**

        *baseReconnectQuietTimeSecond* - The initial back off time to start with, in seconds. 
        Should be less than the stableConnectionTime.

        *maxReconnectQuietTimeSecond* - The maximum back off time, in seconds.

        *stableConnectionTimeSecond* - The number of seconds for a connection to last to be considered as stable. 
        Back off time will be reset to base once the connection is stable.

        **Returns**

        None

        """
        self._mqtt_core.configure_reconnect_back_off(baseReconnectQuietTimeSecond, maxReconnectQuietTimeSecond, stableConnectionTimeSecond)

    def configureOfflinePublishQueueing(self, queueSize, dropBehavior=DROP_NEWEST):
        """
        **Description**

        Used to configure the queue size and drop behavior for the offline requests queueing. Should be
        called before connect. Queueable offline requests include publish, subscribe and unsubscribe.

        **Syntax**

        .. code:: python

          import AWSIoTPythonSDK.MQTTLib as AWSIoTPyMQTT

          # Configure the offline queue for publish requests to be 20 in size and drop the oldest
           request when the queue is full.
          myAWSIoTMQTTClient.configureOfflinePublishQueueing(20, AWSIoTPyMQTT.DROP_OLDEST)

        **Parameters**

        *queueSize* - Size of the queue for offline publish requests queueing.
         If set to 0, the queue is disabled. If set to -1, the queue size is set to be infinite.

        *dropBehavior* - the type of drop behavior when the queue is full.
         Could be :code:`AWSIoTPythonSDK.core.util.enums.DropBehaviorTypes.DROP_OLDEST` or
         :code:`AWSIoTPythonSDK.core.util.enums.DropBehaviorTypes.DROP_NEWEST`.

        **Returns**

        None

        """
        self._mqtt_core.configure_offline_requests_queue(queueSize, dropBehavior)

    def configureDrainingFrequency(self, frequencyInHz):
        """
        **Description**

        Used to configure the draining speed to clear up the queued requests when the connection is back.
        Should be called before connect.

        **Syntax**

        .. code:: python

          # Configure the draining speed to be 2 requests/second
          myAWSIoTMQTTClient.configureDrainingFrequency(2)

        .. note::

          Make sure the draining speed is fast enough and faster than the publish rate. Slow draining 
          could result in inifinite draining process.

        **Parameters**

        *frequencyInHz* - The draining speed to clear the queued requests, in requests/second.

        **Returns**

        None

        """
        self._mqtt_core.configure_draining_interval_sec(1/float(frequencyInHz))

    def configureConnectDisconnectTimeout(self, timeoutSecond):
        """
        **Description**

        Used to configure the time in seconds to wait for a CONNACK or a disconnect to complete. 
        Should be called before connect.

        **Syntax**

        .. code:: python

          # Configure connect/disconnect timeout to be 10 seconds
          myAWSIoTMQTTClient.configureConnectDisconnectTimeout(10)

        **Parameters**

        *timeoutSecond* - Time in seconds to wait for a CONNACK or a disconnect to complete.

        **Returns**

        None

        """
        self._mqtt_core.configure_connect_disconnect_timeout_sec(timeoutSecond)

    def configureMQTTOperationTimeout(self, timeoutSecond):
        """
        **Description**

        Used to configure the timeout in seconds for MQTT QoS 1 publish, subscribe and unsubscribe. 
        Should be called before connect.

        **Syntax**

        .. code:: python

          # Configure MQTT operation timeout to be 5 seconds
          myAWSIoTMQTTClient.configureMQTTOperationTimeout(5)

        **Parameters**

        *timeoutSecond* - Time in seconds to wait for a PUBACK/SUBACK/UNSUBACK.

        **Returns**

        None

        """
        self._mqtt_core.configure_operation_timeout_sec(timeoutSecond)

    def configureUsernamePassword(self, username, password=None):
        """
        **Description**

        Used to configure the username and password used in CONNECT packet.

        **Syntax**

        .. code:: python

          # Configure user name and password
          myAWSIoTMQTTClient.configureUsernamePassword("myUsername", "myPassword")

        **Parameters**

        *username* - Username used in the username field of CONNECT packet.

        *password* - Password used in the password field of CONNECT packet.

        **Returns**

        None

        """
        self._mqtt_core.configure_username_password(username, password)

    def configureSocketFactory(self, socket_factory):
        """
        **Description**

        Configure a socket factory to custom configure a different socket type for
        mqtt connection. Creating a custom socket allows for configuration of a proxy

        **Syntax**

        .. code:: python

          # Configure socket factory
          custom_args = {"arg1": "val1", "arg2": "val2"}
          socket_factory = lambda: custom.create_connection((host, port), **custom_args)
          myAWSIoTMQTTClient.configureSocketFactory(socket_factory)

        **Parameters**

        *socket_factory* - Anonymous function which creates a custom socket to spec.

        **Returns**

        None

        """
        self._mqtt_core.configure_socket_factory(socket_factory)
        
    def enableMetricsCollection(self):
        """
        **Description**

        Used to enable SDK metrics collection. Username field in CONNECT packet will be used to append the SDK name
        and SDK version in use and communicate to AWS IoT cloud. This metrics collection is enabled by default.

        **Syntax**

        .. code:: python

          myAWSIoTMQTTClient.enableMetricsCollection()

        **Parameters**

        None

        **Returns**

        None

        """
        self._mqtt_core.enable_metrics_collection()

    def disableMetricsCollection(self):
        """
        **Description**

        Used to disable SDK metrics collection.

        **Syntax**

        .. code:: python

          myAWSIoTMQTTClient.disableMetricsCollection()

        **Parameters**

        None

        **Returns**

        None

        """
        self._mqtt_core.disable_metrics_collection()

    # MQTT functionality APIs
    def connect(self, keepAliveIntervalSecond=600):
        """
        **Description**

        Connect to AWS IoT, with user-specific keepalive interval configuration.

        **Syntax**

        .. code:: python

          # Connect to AWS IoT with default keepalive set to 600 seconds
          myAWSIoTMQTTClient.connect()
          # Connect to AWS IoT with keepalive interval set to 1200 seconds
          myAWSIoTMQTTClient.connect(1200)

        **Parameters**

        *keepAliveIntervalSecond* - Time in seconds for interval of sending MQTT ping request.
        A shorter keep-alive interval allows the client to detect disconnects more quickly.
        Default set to 600 seconds.

        **Returns**

        True if the connect attempt succeeded. False if failed.

        """
        self._load_callbacks()
        return self._mqtt_core.connect(keepAliveIntervalSecond)

    def connectAsync(self, keepAliveIntervalSecond=600, ackCallback=None):
        """
        **Description**

        Connect asynchronously to AWS IoT, with user-specific keepalive interval configuration and CONNACK callback.

        **Syntax**

        .. code:: python

          # Connect to AWS IoT with default keepalive set to 600 seconds and a custom CONNACK callback
          myAWSIoTMQTTClient.connectAsync(ackCallback=my_connack_callback)
          # Connect to AWS IoT with default keepalive set to 1200 seconds and a custom CONNACK callback
          myAWSIoTMQTTClient.connectAsync(keepAliveInternvalSecond=1200, ackCallback=myConnackCallback)

        **Parameters**

        *keepAliveIntervalSecond* - Time in seconds for interval of sending MQTT ping request.
        Default set to 600 seconds.

        *ackCallback* - Callback to be invoked when the client receives a CONNACK. Should be in form
        :code:`customCallback(mid, data)`, where :code:`mid` is the packet id for the connect request
        and :code:`data` is the connect result code.

        **Returns**

        Connect request packet id, for tracking purpose in the corresponding callback.

        """
        self._load_callbacks()
        return self._mqtt_core.connect_async(keepAliveIntervalSecond, ackCallback)

    def _load_callbacks(self):
        self._mqtt_core.on_online = self.onOnline
        self._mqtt_core.on_offline = self.onOffline
        self._mqtt_core.on_message = self.onMessage

    def disconnect(self):
        """
        **Description**

        Disconnect from AWS IoT.

        **Syntax**

        .. code:: python

          myAWSIoTMQTTClient.disconnect()

        **Parameters**

        None

        **Returns**

        True if the disconnect attempt succeeded. False if failed.

        """
        return self._mqtt_core.disconnect()

    def disconnectAsync(self, ackCallback=None):
        """
        **Description**

        Disconnect asynchronously to AWS IoT.

        **Syntax**

        .. code:: python

          myAWSIoTMQTTClient.disconnectAsync(ackCallback=myDisconnectCallback)

        **Parameters**

        *ackCallback* - Callback to be invoked when the client finishes sending disconnect and internal clean-up.
        Should be in form :code:`customCallback(mid, data)`, where :code:`mid` is the packet id for the disconnect
        request and :code:`data` is the disconnect result code.

        **Returns**

        Disconnect request packet id, for tracking purpose in the corresponding callback.

        """
        return self._mqtt_core.disconnect_async(ackCallback)

    def publish(self, topic, payload, QoS):
        """
        **Description**

        Publish a new message to the desired topic with QoS.

        **Syntax**

        .. code:: python

          # Publish a QoS0 message "myPayload" to topic "myTopic"
          myAWSIoTMQTTClient.publish("myTopic", "myPayload", 0)
          # Publish a QoS1 message "myPayloadWithQos1" to topic "myTopic/sub"
          myAWSIoTMQTTClient.publish("myTopic/sub", "myPayloadWithQos1", 1)

        **Parameters**

        *topic* - Topic name to publish to.

        *payload* - Payload to publish.

        *QoS* - Quality of Service. Could be 0 or 1.

        **Returns**

        True if the publish request has been sent to paho. False if the request did not reach paho.

        """
        return self._mqtt_core.publish(topic, payload, QoS, False)  # Disable retain for publish by now

    def publishAsync(self, topic, payload, QoS, ackCallback=None):
        """
        **Description**

        Publish a new message asynchronously to the desired topic with QoS and PUBACK callback. Note that the ack
        callback configuration for a QoS0 publish request will be ignored as there are no PUBACK reception.

        **Syntax**

        .. code:: python

          # Publish a QoS0 message "myPayload" to topic "myTopic"
          myAWSIoTMQTTClient.publishAsync("myTopic", "myPayload", 0)
          # Publish a QoS1 message "myPayloadWithQos1" to topic "myTopic/sub", with custom PUBACK callback
          myAWSIoTMQTTClient.publishAsync("myTopic/sub", "myPayloadWithQos1", 1, ackCallback=myPubackCallback)

        **Parameters**

        *topic* - Topic name to publish to.

        *payload* - Payload to publish.

        *QoS* - Quality of Service. Could be 0 or 1.

        *ackCallback* - Callback to be invoked when the client receives a PUBACK. Should be in form
        :code:`customCallback(mid)`, where :code:`mid` is the packet id for the disconnect request.

        **Returns**

        Publish request packet id, for tracking purpose in the corresponding callback.

        """
        return self._mqtt_core.publish_async(topic, payload, QoS, False, ackCallback)

    def subscribe(self, topic, QoS, callback):
        """
        **Description**

        Subscribe to the desired topic and register a callback.

        **Syntax**

        .. code:: python

          # Subscribe to "myTopic" with QoS0 and register a callback
          myAWSIoTMQTTClient.subscribe("myTopic", 0, customCallback)
          # Subscribe to "myTopic/#" with QoS1 and register a callback
          myAWSIoTMQTTClient.subscribe("myTopic/#", 1, customCallback)

        **Parameters**

        *topic* - Topic name or filter to subscribe to.

        *QoS* - Quality of Service. Could be 0 or 1.

        *callback* - Function to be called when a new message for the subscribed topic
        comes in. Should be in form :code:`customCallback(client, userdata, message)`, where
        :code:`message` contains :code:`topic` and :code:`payload`. Note that :code:`client` and :code:`userdata` are
        here just to be aligned with the underneath Paho callback function signature. These fields are pending to be
        deprecated and should not be depended on.

        **Returns**

        True if the subscribe attempt succeeded. False if failed.

        """
        return self._mqtt_core.subscribe(topic, QoS, callback)

    def subscribeAsync(self, topic, QoS, ackCallback=None, messageCallback=None):
        """
        **Description**

        Subscribe to the desired topic and register a message callback with SUBACK callback.

        **Syntax**

        .. code:: python

          # Subscribe to "myTopic" with QoS0, custom SUBACK callback and a message callback
          myAWSIoTMQTTClient.subscribe("myTopic", 0, ackCallback=mySubackCallback, messageCallback=customMessageCallback)
          # Subscribe to "myTopic/#" with QoS1, custom SUBACK callback and a message callback
          myAWSIoTMQTTClient.subscribe("myTopic/#", 1, ackCallback=mySubackCallback, messageCallback=customMessageCallback)

        **Parameters**

        *topic* - Topic name or filter to subscribe to.

        *QoS* - Quality of Service. Could be 0 or 1.

        *ackCallback* - Callback to be invoked when the client receives a SUBACK. Should be in form
        :code:`customCallback(mid, data)`, where :code:`mid` is the packet id for the disconnect request and
        :code:`data` is the granted QoS for this subscription.

        *messageCallback* - Function to be called when a new message for the subscribed topic
        comes in. Should be in form :code:`customCallback(client, userdata, message)`, where
        :code:`message` contains :code:`topic` and :code:`payload`. Note that :code:`client` and :code:`userdata` are
        here just to be aligned with the underneath Paho callback function signature. These fields are pending to be
        deprecated and should not be depended on.

        **Returns**

        Subscribe request packet id, for tracking purpose in the corresponding callback.

        """
        return self._mqtt_core.subscribe_async(topic, QoS, ackCallback, messageCallback)

    def unsubscribe(self, topic):
        """
        **Description**

        Unsubscribe to the desired topic.

        **Syntax**

        .. code:: python

          myAWSIoTMQTTClient.unsubscribe("myTopic")

        **Parameters**

        *topic* - Topic name or filter to unsubscribe to.

        **Returns**

        True if the unsubscribe attempt succeeded. False if failed.

        """
        return self._mqtt_core.unsubscribe(topic)

    def unsubscribeAsync(self, topic, ackCallback=None):
        """
        **Description**

        Unsubscribe to the desired topic with UNSUBACK callback.

        **Syntax**

        .. code:: python

          myAWSIoTMQTTClient.unsubscribe("myTopic", ackCallback=myUnsubackCallback)

        **Parameters**

        *topic* - Topic name or filter to unsubscribe to.

        *ackCallback* - Callback to be invoked when the client receives a UNSUBACK. Should be in form
        :code:`customCallback(mid)`, where :code:`mid` is the packet id for the disconnect request.

        **Returns**

        Unsubscribe request packet id, for tracking purpose in the corresponding callback.

        """
        return self._mqtt_core.unsubscribe_async(topic, ackCallback)

    def onOnline(self):
        """
        **Description**

        Callback that gets called when the client is online. The callback registration should happen before calling
        connect/connectAsync.

        **Syntax**

        .. code:: python

          # Register an onOnline callback
          myAWSIoTMQTTClient.onOnline = myOnOnlineCallback

        **Parameters**

        None

        **Returns**

        None

        """
        pass

    def onOffline(self):
        """
        **Description**

        Callback that gets called when the client is offline. The callback registration should happen before calling
        connect/connectAsync.

        **Syntax**

        .. code:: python

          # Register an onOffline callback
          myAWSIoTMQTTClient.onOffline = myOnOfflineCallback

        **Parameters**

        None

        **Returns**

        None

        """
        pass

    def onMessage(self, message):
        """
        **Description**

        Callback that gets called when the client receives a new message. The callback registration should happen before
        calling connect/connectAsync. This callback, if present, will always be triggered regardless of whether there is
        any message callback registered upon subscribe API call. It is for the purpose to aggregating the processing of
        received messages in one function.

        **Syntax**

        .. code:: python

          # Register an onMessage callback
          myAWSIoTMQTTClient.onMessage = myOnMessageCallback

        **Parameters**

        *message* - Received MQTT message. It contains the source topic as :code:`message.topic`, and the payload as
        :code:`message.payload`.

        **Returns**

        None

        """
        pass

class _AWSIoTMQTTDelegatingClient(object):

    def __init__(self, clientID, protocolType=MQTTv3_1_1, useWebsocket=False, cleanSession=True, awsIoTMQTTClient=None):
        """

        This class is used internally by the SDK and should not be instantiated directly.

        It delegates to a provided AWS IoT MQTT Client or creates a new one given the configuration
        parameters and exposes core operations for subclasses provide convenience methods

        **Syntax**

        None

        **Parameters**

        *clientID* - String that denotes the client identifier used to connect to AWS IoT.
        If empty string were provided, client id for this connection will be randomly generated 
        n server side.

        *protocolType* - MQTT version in use for this connection. Could be :code:`AWSIoTPythonSDK.MQTTLib.MQTTv3_1` or :code:`AWSIoTPythonSDK.MQTTLib.MQTTv3_1_1`

        *useWebsocket* - Boolean that denotes enabling MQTT over Websocket SigV4 or not.

        **Returns**

        AWSIoTPythonSDK.MQTTLib._AWSIoTMQTTDelegatingClient object

        """
        # AWSIOTMQTTClient instance
        self._AWSIoTMQTTClient = awsIoTMQTTClient if awsIoTMQTTClient is not None else AWSIoTMQTTClient(clientID, protocolType, useWebsocket, cleanSession)

    # Configuration APIs
    def configureLastWill(self, topic, payload, QoS):
        """
        **Description**

        Used to configure the last will topic, payload and QoS of the client. Should be called before connect. This is a public
        facing API inherited by application level public clients.

        **Syntax**

        .. code:: python

          myShadowClient.configureLastWill("last/Will/Topic", "lastWillPayload", 0)
          myJobsClient.configureLastWill("last/Will/Topic", "lastWillPayload", 0)

        **Parameters**

        *topic* - Topic name that last will publishes to.

        *payload* - Payload to publish for last will.

        *QoS* - Quality of Service. Could be 0 or 1.

        **Returns**

        None

        """
        # AWSIoTMQTTClient.configureLastWill(srcTopic, srcPayload, srcQos)
        self._AWSIoTMQTTClient.configureLastWill(topic, payload, QoS)

    def clearLastWill(self):
        """
        **Description**

        Used to clear the last will configuration that is previously set through configureLastWill. This is a public
        facing API inherited by application level public clients.

        **Syntax**

        .. code:: python

          myShadowClient.clearLastWill()
          myJobsClient.clearLastWill()

        **Parameter**

        None

        **Returns**

        None
        
        """
        # AWSIoTMQTTClient.clearLastWill()
        self._AWSIoTMQTTClient.clearLastWill()

    def configureEndpoint(self, hostName, portNumber):
        """
        **Description**

        Used to configure the host name and port number the underneath AWS IoT MQTT Client tries to connect to. Should be called
        before connect. This is a public facing API inherited by application level public clients.

        **Syntax**

        .. code:: python

          myShadowClient.clearLastWill("random.iot.region.amazonaws.com", 8883)
          myJobsClient.clearLastWill("random.iot.region.amazonaws.com", 8883)

        **Parameters**

        *hostName* - String that denotes the host name of the user-specific AWS IoT endpoint.

        *portNumber* - Integer that denotes the port number to connect to. Could be :code:`8883` for
        TLSv1.2 Mutual Authentication or :code:`443` for Websocket SigV4 and TLSv1.2 Mutual Authentication
        with ALPN extension.

        **Returns**

        None

        """
        # AWSIoTMQTTClient.configureEndpoint
        self._AWSIoTMQTTClient.configureEndpoint(hostName, portNumber)

    def configureIAMCredentials(self, AWSAccessKeyID, AWSSecretAccessKey, AWSSTSToken=""):
        """
        **Description**

        Used to configure/update the custom IAM credentials for the underneath AWS IoT MQTT Client 
        for Websocket SigV4 connection to AWS IoT. Should be called before connect. This is a public
        facing API inherited by application level public clients.

        **Syntax**

        .. code:: python

          myShadowClient.clearLastWill(obtainedAccessKeyID, obtainedSecretAccessKey, obtainedSessionToken)
          myJobsClient.clearLastWill(obtainedAccessKeyID, obtainedSecretAccessKey, obtainedSessionToken)

        .. note::

          Hard-coding credentials into custom script is NOT recommended. Please use AWS Cognito identity service
          or other credential provider.

        **Parameters**

        *AWSAccessKeyID* - AWS Access Key Id from user-specific IAM credentials.

        *AWSSecretAccessKey* - AWS Secret Access Key from user-specific IAM credentials.

        *AWSSessionToken* - AWS Session Token for temporary authentication from STS.

        **Returns**

        None

        """
        # AWSIoTMQTTClient.configureIAMCredentials
        self._AWSIoTMQTTClient.configureIAMCredentials(AWSAccessKeyID, AWSSecretAccessKey, AWSSTSToken)

    def configureCredentials(self, CAFilePath, KeyPath="", CertificatePath=""):  # Should be good for MutualAuth and Websocket
        """
        **Description**

        Used to configure the rootCA, private key and certificate files. Should be called before connect. This is a public
        facing API inherited by application level public clients.

        **Syntax**

        .. code:: python

          myShadowClient.clearLastWill("PATH/TO/ROOT_CA", "PATH/TO/PRIVATE_KEY", "PATH/TO/CERTIFICATE")
          myJobsClient.clearLastWill("PATH/TO/ROOT_CA", "PATH/TO/PRIVATE_KEY", "PATH/TO/CERTIFICATE")

        **Parameters**

        *CAFilePath* - Path to read the root CA file. Required for all connection types.

        *KeyPath* - Path to read the private key. Required for X.509 certificate based connection.

        *CertificatePath* - Path to read the certificate. Required for X.509 certificate based connection.

        **Returns**

        None

        """
        # AWSIoTMQTTClient.configureCredentials
        self._AWSIoTMQTTClient.configureCredentials(CAFilePath, KeyPath, CertificatePath)

    def configureAutoReconnectBackoffTime(self, baseReconnectQuietTimeSecond, maxReconnectQuietTimeSecond, stableConnectionTimeSecond):
        """
        **Description**

        Used to configure the auto-reconnect backoff timing. Should be called before connect. This is a public
        facing API inherited by application level public clients.

        **Syntax**

        .. code:: python

          # Configure the auto-reconnect backoff to start with 1 second and use 128 seconds as a maximum back off time.
          # Connection over 20 seconds is considered stable and will reset the back off time back to its base.
          myShadowClient.clearLastWill(1, 128, 20)
          myJobsClient.clearLastWill(1, 128, 20)

        **Parameters**

        *baseReconnectQuietTimeSecond* - The initial back off time to start with, in seconds.
        Should be less than the stableConnectionTime.

        *maxReconnectQuietTimeSecond* - The maximum back off time, in seconds.

        *stableConnectionTimeSecond* - The number of seconds for a connection to last to be considered as stable.
        Back off time will be reset to base once the connection is stable.

        **Returns**

        None

        """
        # AWSIoTMQTTClient.configureBackoffTime
        self._AWSIoTMQTTClient.configureAutoReconnectBackoffTime(baseReconnectQuietTimeSecond, maxReconnectQuietTimeSecond, stableConnectionTimeSecond)

    def configureConnectDisconnectTimeout(self, timeoutSecond):
        """
        **Description**

        Used to configure the time in seconds to wait for a CONNACK or a disconnect to complete. 
        Should be called before connect. This is a public facing API inherited by application level public clients.

        **Syntax**

        .. code:: python

          # Configure connect/disconnect timeout to be 10 seconds
          myShadowClient.configureConnectDisconnectTimeout(10)
          myJobsClient.configureConnectDisconnectTimeout(10)

        **Parameters**

        *timeoutSecond* - Time in seconds to wait for a CONNACK or a disconnect to complete.

        **Returns**

        None

        """
        # AWSIoTMQTTClient.configureConnectDisconnectTimeout
        self._AWSIoTMQTTClient.configureConnectDisconnectTimeout(timeoutSecond)

    def configureMQTTOperationTimeout(self, timeoutSecond):
        """
        **Description**

        Used to configure the timeout in seconds for MQTT QoS 1 publish, subscribe and unsubscribe. 
        Should be called before connect. This is a public facing API inherited by application level public clients.

        **Syntax**

        .. code:: python

          # Configure MQTT operation timeout to be 5 seconds
          myShadowClient.configureMQTTOperationTimeout(5)
          myJobsClient.configureMQTTOperationTimeout(5)

        **Parameters**

        *timeoutSecond* - Time in seconds to wait for a PUBACK/SUBACK/UNSUBACK.

        **Returns**

        None

        """
        # AWSIoTMQTTClient.configureMQTTOperationTimeout
        self._AWSIoTMQTTClient.configureMQTTOperationTimeout(timeoutSecond)

    def configureUsernamePassword(self, username, password=None):
        """
        **Description**

        Used to configure the username and password used in CONNECT packet. This is a public facing API
        inherited by application level public clients.

        **Syntax**

        .. code:: python

          # Configure user name and password
          myShadowClient.configureUsernamePassword("myUsername", "myPassword")
          myJobsClient.configureUsernamePassword("myUsername", "myPassword")

        **Parameters**

        *username* - Username used in the username field of CONNECT packet.

        *password* - Password used in the password field of CONNECT packet.

        **Returns**

        None

        """
        self._AWSIoTMQTTClient.configureUsernamePassword(username, password)

    def configureSocketFactory(self, socket_factory):
        """
        **Description**

        Configure a socket factory to custom configure a different socket type for
        mqtt connection. Creating a custom socket allows for configuration of a proxy

        **Syntax**

        .. code:: python

          # Configure socket factory
          custom_args = {"arg1": "val1", "arg2": "val2"}
          socket_factory = lambda: custom.create_connection((host, port), **custom_args)
          myAWSIoTMQTTClient.configureSocketFactory(socket_factory)

        **Parameters**

        *socket_factory* - Anonymous function which creates a custom socket to spec.

        **Returns**

        None

        """
        self._AWSIoTMQTTClient.configureSocketFactory(socket_factory)
        
    def enableMetricsCollection(self):
        """
        **Description**

        Used to enable SDK metrics collection. Username field in CONNECT packet will be used to append the SDK name
        and SDK version in use and communicate to AWS IoT cloud. This metrics collection is enabled by default.
        This is a public facing API inherited by application level public clients.

        **Syntax**

        .. code:: python

          myShadowClient.enableMetricsCollection()
          myJobsClient.enableMetricsCollection()

        **Parameters**

        None

        **Returns**

        None

        """
        self._AWSIoTMQTTClient.enableMetricsCollection()

    def disableMetricsCollection(self):
        """
        **Description**

        Used to disable SDK metrics collection. This is a public facing API inherited by application level public clients.

        **Syntax**

        .. code:: python

          myShadowClient.disableMetricsCollection()
          myJobsClient.disableMetricsCollection()

        **Parameters**

        None

        **Returns**

        None

        """
        self._AWSIoTMQTTClient.disableMetricsCollection()

    # Start the MQTT connection
    def connect(self, keepAliveIntervalSecond=600):
        """
        **Description**

        Connect to AWS IoT, with user-specific keepalive interval configuration. This is a public facing API inherited
        by application level public clients.

        **Syntax**

        .. code:: python

          # Connect to AWS IoT with default keepalive set to 600 seconds
          myShadowClient.connect()
          myJobsClient.connect()
          # Connect to AWS IoT with keepalive interval set to 1200 seconds
          myShadowClient.connect(1200)
          myJobsClient.connect(1200)

        **Parameters**

        *keepAliveIntervalSecond* - Time in seconds for interval of sending MQTT ping request. 
        Default set to 30 seconds.

        **Returns**

        True if the connect attempt succeeded. False if failed.

        """
        self._load_callbacks()
        return self._AWSIoTMQTTClient.connect(keepAliveIntervalSecond)

    def _load_callbacks(self):
        self._AWSIoTMQTTClient.onOnline = self.onOnline
        self._AWSIoTMQTTClient.onOffline = self.onOffline

    # End the MQTT connection
    def disconnect(self):
        """
        **Description**

        Disconnect from AWS IoT. This is a public facing API inherited by application level public clients.

        **Syntax**

        .. code:: python

          myShadowClient.disconnect()
          myJobsClient.disconnect()

        **Parameters**

        None

        **Returns**

        True if the disconnect attempt succeeded. False if failed.

        """
        return self._AWSIoTMQTTClient.disconnect()

    # MQTT connection management API
    def getMQTTConnection(self):
        """
        **Description**

        Retrieve the AWS IoT MQTT Client used underneath, making it possible to perform
        plain MQTT operations along with specialized operations using the same single connection.
        This is a public facing API inherited by application level public clients.

        **Syntax**

        .. code:: python

          # Retrieve the AWS IoT MQTT Client used in the AWS IoT MQTT Delegating Client
          thisAWSIoTMQTTClient = myShadowClient.getMQTTConnection()
          thisAWSIoTMQTTClient = myJobsClient.getMQTTConnection()
          # Perform plain MQTT operations using the same connection
          thisAWSIoTMQTTClient.publish("Topic", "Payload", 1)
          ...

        **Parameters**

        None

        **Returns**

        AWSIoTPythonSDK.MQTTLib.AWSIoTMQTTClient object

        """
        # Return the internal AWSIoTMQTTClient instance
        return self._AWSIoTMQTTClient

    def onOnline(self):
        """
        **Description**

        Callback that gets called when the client is online. The callback registration should happen before calling
        connect. This is a public facing API inherited by application level public clients.

        **Syntax**

        .. code:: python

          # Register an onOnline callback
          myShadowClient.onOnline = myOnOnlineCallback
          myJobsClient.onOnline = myOnOnlineCallback

        **Parameters**

        None

        **Returns**

        None

        """
        pass

    def onOffline(self):
        """
        **Description**

        Callback that gets called when the client is offline. The callback registration should happen before calling
        connect. This is a public facing API inherited by application level public clients.

        **Syntax**

        .. code:: python

          # Register an onOffline callback
          myShadowClient.onOffline = myOnOfflineCallback
          myJobsClient.onOffline = myOnOfflineCallback

        **Parameters**

        None

        **Returns**

        None

        """
        pass


class AWSIoTMQTTShadowClient(_AWSIoTMQTTDelegatingClient):

    def __init__(self, clientID, protocolType=MQTTv3_1_1, useWebsocket=False, cleanSession=True, awsIoTMQTTClient=None):
        """

        The client class that manages device shadow and accesses its functionality in AWS IoT over MQTT v3.1/3.1.1.

        It delegates to the AWS IoT MQTT Client and exposes devive shadow related operations.
        It shares the same connection types, synchronous MQTT operations and partial on-top features
        with the AWS IoT MQTT Client:

        - Auto reconnect/resubscribe

        Same as AWS IoT MQTT Client.

        - Progressive reconnect backoff

        Same as AWS IoT MQTT Client.

        - Offline publish requests queueing with draining

        Disabled by default. Queueing is not allowed for time-sensitive shadow requests/messages.

        **Syntax**

        .. code:: python

          import AWSIoTPythonSDK.MQTTLib as AWSIoTPyMQTT

          # Create an AWS IoT MQTT Shadow Client using TLSv1.2 Mutual Authentication
          myAWSIoTMQTTShadowClient = AWSIoTPyMQTT.AWSIoTMQTTShadowClient("testIoTPySDK")
          # Create an AWS IoT MQTT Shadow Client using Websocket SigV4
          myAWSIoTMQTTShadowClient = AWSIoTPyMQTT.AWSIoTMQTTShadowClient("testIoTPySDK",  useWebsocket=True)

        **Parameters**

        *clientID* - String that denotes the client identifier used to connect to AWS IoT.
        If empty string were provided, client id for this connection will be randomly generated
        n server side.

        *protocolType* - MQTT version in use for this connection. Could be :code:`AWSIoTPythonSDK.MQTTLib.MQTTv3_1` or :code:`AWSIoTPythonSDK.MQTTLib.MQTTv3_1_1`

        *useWebsocket* - Boolean that denotes enabling MQTT over Websocket SigV4 or not.

        **Returns**

        AWSIoTPythonSDK.MQTTLib.AWSIoTMQTTShadowClient object

        """
        super(AWSIoTMQTTShadowClient, self).__init__(clientID, protocolType, useWebsocket, cleanSession, awsIoTMQTTClient)
        #leave passed in clients alone
        if awsIoTMQTTClient is None:
            # Configure it to disable offline Publish Queueing
            self._AWSIoTMQTTClient.configureOfflinePublishQueueing(0)  # Disable queueing, no queueing for time-sensitive shadow messages
            self._AWSIoTMQTTClient.configureDrainingFrequency(10)
        # Now retrieve the configured mqttCore and init a shadowManager instance
        self._shadowManager = shadowManager.shadowManager(self._AWSIoTMQTTClient._mqtt_core)

    # Shadow management API
    def createShadowHandlerWithName(self, shadowName, isPersistentSubscribe):
        """
        **Description**

        Create a device shadow handler using the specified shadow name and isPersistentSubscribe.

        **Syntax**

        .. code:: python

          # Create a device shadow handler for shadow named "Bot1", using persistent subscription
          Bot1Shadow = myAWSIoTMQTTShadowClient.createShadowHandlerWithName("Bot1", True)
          # Create a device shadow handler for shadow named "Bot2", using non-persistent subscription
          Bot2Shadow = myAWSIoTMQTTShadowClient.createShadowHandlerWithName("Bot2", False)

        **Parameters**

        *shadowName* - Name of the device shadow.

        *isPersistentSubscribe* - Whether to unsubscribe from shadow response (accepted/rejected) topics
        when there is a response. Will subscribe at the first time the shadow request is made and will
        not unsubscribe if isPersistentSubscribe is set.

        **Returns**

        AWSIoTPythonSDK.core.shadow.deviceShadow.deviceShadow object, which exposes the device shadow interface.

        """
        # Create and return a deviceShadow instance
        return deviceShadow.deviceShadow(shadowName, isPersistentSubscribe, self._shadowManager)
        # Shadow APIs are accessible in deviceShadow instance":
        ###
        # deviceShadow.shadowGet
        # deviceShadow.shadowUpdate
        # deviceShadow.shadowDelete
        # deviceShadow.shadowRegisterDelta
        # deviceShadow.shadowUnregisterDelta

class AWSIoTMQTTThingJobsClient(_AWSIoTMQTTDelegatingClient):

    def __init__(self, clientID, thingName, QoS=0, protocolType=MQTTv3_1_1, useWebsocket=False, cleanSession=True, awsIoTMQTTClient=None):
        """

        The client class that specializes in handling jobs messages and accesses its functionality in AWS IoT over MQTT v3.1/3.1.1.

        It delegates to the AWS IoT MQTT Client and exposes jobs related operations.
        It shares the same connection types, synchronous MQTT operations and partial on-top features
        with the AWS IoT MQTT Client:

        - Auto reconnect/resubscribe

        Same as AWS IoT MQTT Client.

        - Progressive reconnect backoff

        Same as AWS IoT MQTT Client.

        - Offline publish requests queueing with draining

        Same as AWS IoT MQTT Client

        **Syntax**

        .. code:: python

          import AWSIoTPythonSDK.MQTTLib as AWSIoTPyMQTT

          # Create an AWS IoT MQTT Jobs Client using TLSv1.2 Mutual Authentication
          myAWSIoTMQTTJobsClient = AWSIoTPyMQTT.AWSIoTMQTTThingJobsClient("testIoTPySDK")
          # Create an AWS IoT MQTT Jobs Client using Websocket SigV4
          myAWSIoTMQTTJobsClient = AWSIoTPyMQTT.AWSIoTMQTTThingJobsClient("testIoTPySDK",  useWebsocket=True)

        **Parameters**

        *clientID* - String that denotes the client identifier and client token for jobs requests
        If empty string is provided, client id for this connection will be randomly generated
        on server side. If an awsIotMQTTClient is specified, this will not override the client ID
        for the existing MQTT connection and only impact the client token for jobs request payloads

        *thingName* - String that represents the thingName used to send requests to proper topics and subscribe
        to proper topics.

        *QoS* - QoS used for all requests sent through this client

        *awsIoTMQTTClient* - An instance of AWSIoTMQTTClient to use if not None. If not None, clientID, protocolType, useWebSocket,
        and cleanSession parameters are not used. Caller is expected to invoke connect() prior to calling the pub/sub methods on this client.

        *protocolType* - MQTT version in use for this connection. Could be :code:`AWSIoTPythonSDK.MQTTLib.MQTTv3_1` or :code:`AWSIoTPythonSDK.MQTTLib.MQTTv3_1_1`

        *useWebsocket* - Boolean that denotes enabling MQTT over Websocket SigV4 or not.

        **Returns**

        AWSIoTPythonSDK.MQTTLib.AWSIoTMQTTJobsClient object

        """
        # AWSIOTMQTTClient instance
        super(AWSIoTMQTTThingJobsClient, self).__init__(clientID, protocolType, useWebsocket, cleanSession, awsIoTMQTTClient)
        self._thingJobManager = thingJobManager.thingJobManager(thingName, clientID)
        self._QoS = QoS

    def createJobSubscription(self, callback, jobExecutionType=jobExecutionTopicType.JOB_WILDCARD_TOPIC, jobReplyType=jobExecutionTopicReplyType.JOB_REQUEST_TYPE, jobId=None):
        """
        **Description**

        Synchronously creates an MQTT subscription to a jobs related topic based on the provided arguments

        **Syntax**

        .. code:: python

          #Subscribe to notify-next topic to monitor change in job referred to by $next
          myAWSIoTMQTTJobsClient.createJobSubscription(callback, jobExecutionTopicType.JOB_NOTIFY_NEXT_TOPIC)
          #Subscribe to notify topic to monitor changes to jobs in pending list
          myAWSIoTMQTTJobsClient.createJobSubscription(callback, jobExecutionTopicType.JOB_NOTIFY_TOPIC)
          #Subscribe to receive messages for job execution updates
          myAWSIoTMQTTJobsClient.createJobSubscription(callback, jobExecutionTopicType.JOB_UPDATE_TOPIC, jobExecutionTopicReplyType.JOB_ACCEPTED_REPLY_TYPE)
          #Subscribe to receive messages for describing a job execution
          myAWSIoTMQTTJobsClient.createJobSubscription(callback, jobExecutionTopicType.JOB_DESCRIBE_TOPIC, jobExecutionTopicReplyType.JOB_ACCEPTED_REPLY_TYPE, jobId)

        **Parameters**

        *callback* - Function to be called when a new message for the subscribed job topic
        comes in. Should be in form :code:`customCallback(client, userdata, message)`, where
        :code:`message` contains :code:`topic` and :code:`payload`. Note that :code:`client` and :code:`userdata` are
        here just to be aligned with the underneath Paho callback function signature. These fields are pending to be
        deprecated and should not be depended on.

        *jobExecutionType* - Member of the jobExecutionTopicType class specifying the jobs topic to subscribe to
        Defaults to jobExecutionTopicType.JOB_WILDCARD_TOPIC

        *jobReplyType* - Member of the jobExecutionTopicReplyType class specifying the (optional) reply sub-topic to subscribe to
        Defaults to jobExecutionTopicReplyType.JOB_REQUEST_TYPE which indicates the subscription isn't intended for a jobs reply topic

        *jobId* - JobId string  if the topic type requires one.
        Defaults to None

        **Returns**

        True if the subscribe attempt succeeded. False if failed.

        """
        topic = self._thingJobManager.getJobTopic(jobExecutionType, jobReplyType, jobId)
        return self._AWSIoTMQTTClient.subscribe(topic, self._QoS, callback)

    def createJobSubscriptionAsync(self, ackCallback, callback, jobExecutionType=jobExecutionTopicType.JOB_WILDCARD_TOPIC, jobReplyType=jobExecutionTopicReplyType.JOB_REQUEST_TYPE, jobId=None):
        """
        **Description**

        Asynchronously creates an MQTT subscription to a jobs related topic based on the provided arguments

        **Syntax**

        .. code:: python

          #Subscribe to notify-next topic to monitor change in job referred to by $next
          myAWSIoTMQTTJobsClient.createJobSubscriptionAsync(callback, jobExecutionTopicType.JOB_NOTIFY_NEXT_TOPIC)
          #Subscribe to notify topic to monitor changes to jobs in pending list
          myAWSIoTMQTTJobsClient.createJobSubscriptionAsync(callback, jobExecutionTopicType.JOB_NOTIFY_TOPIC)
          #Subscribe to receive messages for job execution updates
          myAWSIoTMQTTJobsClient.createJobSubscriptionAsync(callback, jobExecutionTopicType.JOB_UPDATE_TOPIC, jobExecutionTopicReplyType.JOB_ACCEPTED_REPLY_TYPE)
          #Subscribe to receive messages for describing a job execution
          myAWSIoTMQTTJobsClient.createJobSubscriptionAsync(callback, jobExecutionTopicType.JOB_DESCRIBE_TOPIC, jobExecutionTopicReplyType.JOB_ACCEPTED_REPLY_TYPE, jobId)

        **Parameters**

        *ackCallback* - Callback to be invoked when the client receives a SUBACK. Should be in form
        :code:`customCallback(mid, data)`, where :code:`mid` is the packet id for the disconnect request and
        :code:`data` is the granted QoS for this subscription.

        *callback* - Function to be called when a new message for the subscribed job topic
        comes in. Should be in form :code:`customCallback(client, userdata, message)`, where
        :code:`message` contains :code:`topic` and :code:`payload`. Note that :code:`client` and :code:`userdata` are
        here just to be aligned with the underneath Paho callback function signature. These fields are pending to be
        deprecated and should not be depended on.

        *jobExecutionType* - Member of the jobExecutionTopicType class specifying the jobs topic to subscribe to
        Defaults to jobExecutionTopicType.JOB_WILDCARD_TOPIC

        *jobReplyType* - Member of the jobExecutionTopicReplyType class specifying the (optional) reply sub-topic to subscribe to
        Defaults to jobExecutionTopicReplyType.JOB_REQUEST_TYPE which indicates the subscription isn't intended for a jobs reply topic

        *jobId* - JobId of the topic if the topic type requires one.
        Defaults to None

        **Returns**

        Subscribe request packet id, for tracking purpose in the corresponding callback.

        """
        topic = self._thingJobManager.getJobTopic(jobExecutionType, jobReplyType, jobId)
        return self._AWSIoTMQTTClient.subscribeAsync(topic, self._QoS, ackCallback, callback)

    def sendJobsQuery(self, jobExecTopicType, jobId=None):
        """
        **Description**

        Publishes an MQTT jobs related request for a potentially specific jobId (or wildcard)

        **Syntax**

        .. code:: python

          #send a request to describe the next job
          myAWSIoTMQTTJobsClient.sendJobsQuery(jobExecutionTopicType.JOB_DESCRIBE_TOPIC, '$next')
          #send a request to get list of pending jobs
          myAWSIoTMQTTJobsClient.sendJobsQuery(jobExecutionTopicType.JOB_GET_PENDING_TOPIC)

        **Parameters**

        *jobExecutionType* - Member of the jobExecutionTopicType class that correlates the jobs topic to publish to

        *jobId* - JobId string if the topic type requires one.
        Defaults to None

        **Returns**

        True if the publish request has been sent to paho. False if the request did not reach paho.

        """
        topic = self._thingJobManager.getJobTopic(jobExecTopicType, jobExecutionTopicReplyType.JOB_REQUEST_TYPE, jobId)
        payload = self._thingJobManager.serializeClientTokenPayload()
        return self._AWSIoTMQTTClient.publish(topic, payload, self._QoS)

    def sendJobsStartNext(self, statusDetails=None, stepTimeoutInMinutes=None):
        """
        **Description**

        Publishes an MQTT message to the StartNextJobExecution topic. This will attempt to get the next pending
        job execution and change its status to IN_PROGRESS.

        **Syntax**

        .. code:: python

          #Start next job (set status to IN_PROGRESS) and update with optional statusDetails
          myAWSIoTMQTTJobsClient.sendJobsStartNext({'StartedBy': 'myClientId'})

        **Parameters**

        *statusDetails* - Dictionary containing the key value pairs to use for the status details of the job execution

        *stepTimeoutInMinutes - Specifies the amount of time this device has to finish execution of this job. 

        **Returns**

        True if the publish request has been sent to paho. False if the request did not reach paho.

        """
        topic = self._thingJobManager.getJobTopic(jobExecutionTopicType.JOB_START_NEXT_TOPIC, jobExecutionTopicReplyType.JOB_REQUEST_TYPE)
        payload = self._thingJobManager.serializeStartNextPendingJobExecutionPayload(statusDetails, stepTimeoutInMinutes)
        return self._AWSIoTMQTTClient.publish(topic, payload, self._QoS)

    def sendJobsUpdate(self, jobId, status, statusDetails=None, expectedVersion=0, executionNumber=0, includeJobExecutionState=False, includeJobDocument=False, stepTimeoutInMinutes=None):
        """
        **Description**

        Publishes an MQTT message to a corresponding job execution specific topic to update its status according to the parameters.
        Can be used to change a job from QUEUED to IN_PROGRESS to SUCEEDED or FAILED.

        **Syntax**

        .. code:: python

          #Update job with id 'jobId123' to succeeded state, specifying new status details, with expectedVersion=1, executionNumber=2.
          #For the response, include job execution state and not the job document
          myAWSIoTMQTTJobsClient.sendJobsUpdate('jobId123', jobExecutionStatus.JOB_EXECUTION_SUCCEEDED, statusDetailsMap, 1, 2, True, False)


          #Update job with id 'jobId456' to failed state
          myAWSIoTMQTTJobsClient.sendJobsUpdate('jobId456', jobExecutionStatus.JOB_EXECUTION_FAILED)

        **Parameters**

        *jobId* - JobID String of the execution to update the status of

        *status* - job execution status to change the job execution to. Member of jobExecutionStatus

        *statusDetails* - new status details to set on the job execution

        *expectedVersion* - The expected current version of the job execution. IoT jobs increments expectedVersion each time you update the job execution.
        If the version of the job execution stored in Jobs does not match, the update is rejected with a VersionMismatch error, and an ErrorResponse
        that contains the current job execution status data is returned. (This makes it unnecessary to perform a separate DescribeJobExecution request
        n order to obtain the job execution status data.)

        *executionNumber* - A number that identifies a particular job execution on a particular device. If not specified, the latest job execution is used.

        *includeJobExecutionState* - When included and set to True, the response contains the JobExecutionState field. The default is False.

        *includeJobDocument* - When included and set to True, the response contains the JobDocument. The default is False.

        *stepTimeoutInMinutes - Specifies the amount of time this device has to finish execution of this job. 

        **Returns**

        True if the publish request has been sent to paho. False if the request did not reach paho.

        """
        topic = self._thingJobManager.getJobTopic(jobExecutionTopicType.JOB_UPDATE_TOPIC, jobExecutionTopicReplyType.JOB_REQUEST_TYPE, jobId)
        payload = self._thingJobManager.serializeJobExecutionUpdatePayload(status, statusDetails, expectedVersion, executionNumber, includeJobExecutionState, includeJobDocument, stepTimeoutInMinutes)
        return self._AWSIoTMQTTClient.publish(topic, payload, self._QoS)

    def sendJobsDescribe(self, jobId, executionNumber=0, includeJobDocument=True):
        """
        **Description**

        Publishes a method to the describe topic for a particular job.

        **Syntax**

        .. code:: python

          #Describe job with id 'jobId1' of any executionNumber, job document will be included in response
          myAWSIoTMQTTJobsClient.sendJobsDescribe('jobId1')

          #Describe job with id 'jobId2', with execution number of 2, and includeJobDocument in the response
          myAWSIoTMQTTJobsClient.sendJobsDescribe('jobId2', 2, True)

        **Parameters**

        *jobId* - jobID to describe. This is allowed to be a wildcard such as '$next'

        *executionNumber* - A number that identifies a particular job execution on a particular device. If not specified, the latest job execution is used.

        *includeJobDocument* - When included and set to True, the response contains the JobDocument.

        **Returns**

        True if the publish request has been sent to paho. False if the request did not reach paho.

        """
        topic = self._thingJobManager.getJobTopic(jobExecutionTopicType.JOB_DESCRIBE_TOPIC, jobExecutionTopicReplyType.JOB_REQUEST_TYPE, jobId)
        payload = self._thingJobManager.serializeDescribeJobExecutionPayload(executionNumber, includeJobDocument)
        return self._AWSIoTMQTTClient.publish(topic, payload, self._QoS)
