/**
 * @brief MQTT Connection Function
 *
 * Called to establish an MQTT connection with the AWS IoT Service
 * This is the internal function which is called by the connect API to perform the operation.
 * Not meant to be called directly as it doesn't do validations or client state changes
 *
 * @param pClient Reference to the IoT Client
 * @param pConnectParams Pointer to MQTT connection parameters
 *
 * @return An IoT Error Type defining successful/failed connection
 */
static IoT_Error_t _aws_iot_mqtt_internal_connect(AWS_IoT_Client *pClient, IoT_Client_Connect_Params *pConnectParams) {
	Timer connect_timer;
	IoT_Error_t connack_rc = FAILURE;
	char sessionPresent = 0;
	size_t len = 0;
	IoT_Error_t rc = FAILURE;

	FUNC_ENTRY;

	if(NULL != pConnectParams) {
		/* override default options if new options were supplied */
		rc = aws_iot_mqtt_set_connect_params(pClient, pConnectParams);
		if(SUCCESS != rc) {
			FUNC_EXIT_RC(MQTT_CONNECTION_ERROR);
		}
	}

	rc = pClient->networkStack.connect(&(pClient->networkStack), NULL);
	if(SUCCESS != rc) {
		/* TLS Connect failed, return error */
		FUNC_EXIT_RC(rc);
	}

	init_timer(&connect_timer);
	countdown_ms(&connect_timer, pClient->clientData.commandTimeoutMs);

	pClient->clientData.keepAliveInterval = pClient->clientData.options.keepAliveIntervalInSec;
	rc = _aws_iot_mqtt_serialize_connect(pClient->clientData.writeBuf, pClient->clientData.writeBufSize,
										 &(pClient->clientData.options), &len);
	if(SUCCESS != rc || 0 >= len) {
		FUNC_EXIT_RC(rc);
	}

	/* send the connect packet */
	rc = aws_iot_mqtt_internal_send_packet(pClient, len, &connect_timer);
	if(SUCCESS != rc) {
		FUNC_EXIT_RC(rc);
	}

	/* this will be a blocking call, wait for the CONNACK */
	rc = aws_iot_mqtt_internal_wait_for_read(pClient, CONNACK, &connect_timer);
	if(SUCCESS != rc) {
		FUNC_EXIT_RC(rc);
	}

	/* Received CONNACK, check the return code */
	rc = _aws_iot_mqtt_deserialize_connack((unsigned char *) &sessionPresent, &connack_rc, pClient->clientData.readBuf,
										   pClient->clientData.readBufSize);
	if(SUCCESS != rc) {
		FUNC_EXIT_RC(rc);
	}

	if(MQTT_CONNACK_CONNECTION_ACCEPTED != connack_rc) {
		FUNC_EXIT_RC(connack_rc);
	}

	pClient->clientStatus.isPingOutstanding = false;
	countdown_sec(&pClient->pingTimer, pClient->clientData.keepAliveInterval);

	FUNC_EXIT_RC(SUCCESS);
}
IoT_Error_t aws_iot_mqtt_init(AWS_IoT_Client *pClient, IoT_Client_Init_Params *pInitParams) {
	uint32_t i;
	IoT_Error_t rc;
	IoT_Client_Connect_Params default_options = IoT_Client_Connect_Params_initializer;

	FUNC_ENTRY;

	if(NULL == pClient || NULL == pInitParams || NULL == pInitParams->pHostURL || 0 == pInitParams->port ||
	   NULL == pInitParams->pRootCALocation || NULL == pInitParams->pDevicePrivateKeyLocation ||
	   NULL == pInitParams->pDeviceCertLocation) {
		FUNC_EXIT_RC(NULL_VALUE_ERROR);
	}

	for(i = 0; i < AWS_IOT_MQTT_NUM_SUBSCRIBE_HANDLERS; ++i) {
		pClient->clientData.messageHandlers[i].topicName = NULL;
		pClient->clientData.messageHandlers[i].pApplicationHandler = NULL;
		pClient->clientData.messageHandlers[i].pApplicationHandlerData = NULL;
		pClient->clientData.messageHandlers[i].qos = QOS0;
	}

	pClient->clientData.packetTimeoutMs = pInitParams->mqttPacketTimeout_ms;
	pClient->clientData.commandTimeoutMs = pInitParams->mqttCommandTimeout_ms;
	pClient->clientData.writeBufSize = AWS_IOT_MQTT_TX_BUF_LEN;
	pClient->clientData.readBufSize = AWS_IOT_MQTT_RX_BUF_LEN;
	pClient->clientData.counterNetworkDisconnected = 0;
	pClient->clientData.disconnectHandler = pInitParams->disconnectHandler;
	pClient->clientData.disconnectHandlerData = pInitParams->disconnectHandlerData;
	pClient->clientData.nextPacketId = 1;

	/* Initialize default connection options */
	rc = aws_iot_mqtt_set_connect_params(pClient, &default_options);
	if(AWS_SUCCESS != rc) {
		FUNC_EXIT_RC(rc);
	}

#ifdef _ENABLE_THREAD_SUPPORT_
	pClient->clientData.isBlockOnThreadLockEnabled = pInitParams->isBlockOnThreadLockEnabled;
	rc = aws_iot_thread_mutex_init(&(pClient->clientData.state_change_mutex));
	if(AWS_SUCCESS != rc) {
		FUNC_EXIT_RC(rc);
	}
	rc = aws_iot_thread_mutex_init(&(pClient->clientData.tls_read_mutex));
	if(AWS_SUCCESS != rc) {
		FUNC_EXIT_RC(rc);
	}
	rc = aws_iot_thread_mutex_init(&(pClient->clientData.tls_write_mutex));
	if(AWS_SUCCESS != rc) {
		FUNC_EXIT_RC(rc);
	}
#endif

	pClient->clientStatus.isPingOutstanding = 0;
	pClient->clientStatus.isAutoReconnectEnabled = pInitParams->enableAutoReconnect;

	rc = iot_tls_init(&(pClient->networkStack), pInitParams->pRootCALocation, pInitParams->pDeviceCertLocation,
					  pInitParams->pDevicePrivateKeyLocation, pInitParams->pHostURL, pInitParams->port,
					  pInitParams->tlsHandshakeTimeout_ms, pInitParams->isSSLHostnameVerify);

	if(AWS_SUCCESS != rc) {
		pClient->clientStatus.clientState = CLIENT_STATE_INVALID;
		FUNC_EXIT_RC(rc);
	}

	init_timer(&(pClient->pingTimer));
	init_timer(&(pClient->reconnectDelayTimer));

	pClient->clientStatus.clientState = CLIENT_STATE_INITIALIZED;

	FUNC_EXIT_RC(AWS_SUCCESS);
}