/*
 * Copyright (c) 2017 Digi International Inc.
 *
 * Permission to use, copy, modify, and/or distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
 * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
 * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
 * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
 * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
 * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 * PERFORMANCE OF THIS SOFTWARE.
 * =======================================================================
 */

#include <aws_iot_config.h>
#include <aws_iot_log.h>
#include <aws_iot_shadow_interface.h>
#include <limits.h>
#include <string.h>

#include "aws_config.h"
#include "aws_control.h"
#include "device_control.h"

/*------------------------------------------------------------------------------
                             D E F I N I T I O N S
------------------------------------------------------------------------------*/
#define MAX_LENGTH_OF_UPDATE_JSON_BUFFER	200

/*------------------------------------------------------------------------------
                    F U N C T I O N  D E C L A R A T I O N S
------------------------------------------------------------------------------*/
static void led_actuate_callback(const char *json_string, uint32_t len,
				  jsonStruct_t *json_struct);
static void shadow_update_status_callback(const char *pThingName,
					  ShadowActions_t action,
					  Shadow_Ack_Status_t status,
					  const char *pReceivedJsonDocument,
					  void *pContextData);
static void disconnect_callback(AWS_IoT_Client *mqtt_client, void *data);

/*------------------------------------------------------------------------------
                         G L O B A L  V A R I A B L E S
------------------------------------------------------------------------------*/
aws_iot_cfg_t aws_cfg;
device_shadow_t *device_shadow;

/*------------------------------------------------------------------------------
                     F U N C T I O N  D E F I N I T I O N S
------------------------------------------------------------------------------*/
/*
 * initialize_shadow_client() - Initialize AWS IoT Device SDK
 *
 * @mqtt_client:	MQTT client.
 * @aws_config:		Absolute path of the configuration file to use.
 * 			NULL to use the default one (/etc/awsiotsdk.conf).
 *
 * Return: An IoT error code defining successful/failed initialization.
 */
IoT_Error_t initialize_shadow_client(AWS_IoT_Client *mqtt_client, const char *config_file)
{
	ShadowInitParameters_t shadow_init_params = ShadowInitParametersDefault;
	IoT_Error_t rc = FAILURE;
	char root_ca[PATH_MAX + 1];
	char client_crt[PATH_MAX + 1];
	char client_key[PATH_MAX + 1];

	if (parse_configuration(config_file ? config_file : AWS_IOT_CONFIG_FILE, &aws_cfg) != 0)
		return FAILURE;

	snprintf(root_ca, PATH_MAX + 1, "%s/%s", aws_cfg.certs_path, aws_cfg.rootca_fname);
	snprintf(client_crt, PATH_MAX + 1, "%s/%s", aws_cfg.certs_path, aws_cfg.signed_cert_fname);
	snprintf(client_key, PATH_MAX + 1, "%s/%s", aws_cfg.certs_path, aws_cfg.priv_key_fname);

	IOT_DEBUG("Root CA file: %s", root_ca);
	IOT_DEBUG("Device signed certificate file: %s", client_crt);
	IOT_DEBUG("Device private key file: %s", client_key);

	/* Initialize the MQTT client */
	shadow_init_params.pHost = aws_cfg.host;
	shadow_init_params.port = aws_cfg.port;
	shadow_init_params.pRootCA = root_ca;
	shadow_init_params.pClientCRT = client_crt;
	shadow_init_params.pClientKey = client_key;
	shadow_init_params.enableAutoReconnect = false; /* Enable later */

	IOT_INFO("Initializing MQTT...");
	rc = aws_iot_shadow_init(mqtt_client, &shadow_init_params);
	if (rc != SUCCESS)
		return rc;

	rc = aws_iot_mqtt_set_disconnect_handler(mqtt_client,
						 disconnect_callback, NULL);
	if (rc != SUCCESS) {
		IOT_ERROR("Unable to set MQTT disconnect handler, error: %d",
			  rc);
		return rc;
	}

	/*
	 * Enable Auto Reconnect functionality.
	 * Min and max time of exponential backoff are set in 'aws_iot_config.h':
	 * #AWS_IOT_MQTT_MIN_RECONNECT_WAIT_INTERVAL
	 * #AWS_IOT_MQTT_MAX_RECONNECT_WAIT_INTERVAL
	 */
	rc = aws_iot_shadow_set_autoreconnect_status(mqtt_client, true);
	if (rc != SUCCESS)
		IOT_ERROR("Unable to enable auto-reconnect, error: %d", rc);

	/*
	 * Workaround for shadow updates getting out of sync:
	 *
	 * WARN:  shadow_delta_callback L#504 Old Delta Message received -
	 * Ignoring rx: 40408 local: 40408
	 *
	 * See discussion here:
	 *     https://github.com/aws/aws-iot-device-sdk-embedded-C/issues/32
	 */
	aws_iot_shadow_disable_discard_old_delta_msgs();

	return rc;
}

/**
 * connect_shadow_client() - Establish MQTT connection
 *
 * @mqtt_client:	MQTT client.
 *
 * Return: An IoT error code defining successful/failed connection.
 */
IoT_Error_t connect_shadow_client(AWS_IoT_Client *mqtt_client)
{
	ShadowConnectParameters_t conn_params = ShadowConnectParametersDefault;

	conn_params.pMyThingName = aws_cfg.thing_name;
	conn_params.pMqttClientId = aws_cfg.client_id;
	conn_params.mqttClientIdLen = (uint16_t) strlen(aws_cfg.client_id);
	conn_params.deleteActionHandler = NULL;

	IOT_INFO("Shadow connecting...");
	return aws_iot_shadow_connect(mqtt_client, &conn_params);
}

/**
 * disconnect_shadow_client() - Close MQTT connection
 *
 * @mqtt_client:	MQTT client.
 *
 * Return: An IoT error code defining successful/failed disconnect status.
 */
IoT_Error_t disconnect_shadow_client(AWS_IoT_Client *mqtt_client)
{
	IoT_Error_t rc = FAILURE;

	IOT_INFO("Disconnecting...");

	rc = aws_iot_shadow_disconnect(mqtt_client);

	free_configuration();

	return rc;
}

/*
 * initialize_shadow() - Initialize device Shadow
 *
 * @mqtt_client:	MQTT client.
 * @dev_shadow:		Device shadow.
 *
 * Return: An IoT error code defining successful/failed initialization.
 */
IoT_Error_t initialize_shadow(AWS_IoT_Client *mqtt_client,
			      device_shadow_t *dev_shadow)
{
	IoT_Error_t rc = FAILURE;

	device_shadow = dev_shadow;

	device_shadow->temp_handler.cb = NULL;
	device_shadow->temp_handler.pKey = ATTR_TEMPERATURE;
	device_shadow->temp_handler.pData = &(device_shadow->temp);
	device_shadow->temp_handler.type = SHADOW_JSON_DOUBLE;

	device_shadow->temp = 0;
	device_shadow->temp_update = 0;

	device_shadow->cpu_load_handler.cb = NULL;
	device_shadow->cpu_load_handler.pKey = ATTR_CPU_LOAD;
	device_shadow->cpu_load_handler.pData = &(device_shadow->cpu_load);
	device_shadow->cpu_load_handler.type = SHADOW_JSON_DOUBLE;

	device_shadow->cpu_load = 0;
	device_shadow->cpu_load_update = 0;

	device_shadow->led_actuator.cb = led_actuate_callback;
	device_shadow->led_actuator.pKey = ATTR_LED;
	device_shadow->led_actuator.pData = &(device_shadow->led_on);
	device_shadow->led_actuator.type = SHADOW_JSON_BOOL;

	device_shadow->led_on = false;
	device_shadow->led_update = 0;

	device_shadow->aws_config = &aws_cfg;

	rc = aws_iot_shadow_register_delta(mqtt_client,
					   &(device_shadow->led_actuator));
	if (rc != SUCCESS)
		IOT_ERROR("Unable to register Shadow Delta for LED, error: %d", rc);

	return rc;
}

/**
 * update_shadow() - Update thing shadow.
 *
 * @mqtt_client:	MQTT client.
 *
 * Return: An IoT error code defining successful/failed update action.
 */
IoT_Error_t update_shadow(AWS_IoT_Client *mqtt_client)
{
	char json_doc_buf[MAX_LENGTH_OF_UPDATE_JSON_BUFFER];
	size_t size_json_doc_buf = sizeof(json_doc_buf) / sizeof(json_doc_buf[0]);
	IoT_Error_t rc = FAILURE;

	rc = aws_iot_shadow_init_json_document(json_doc_buf, size_json_doc_buf);
	if (rc != SUCCESS)
		return rc;

	rc = aws_iot_shadow_add_reported(json_doc_buf, size_json_doc_buf,
					 3,
					 &(device_shadow->temp_handler),
					 &(device_shadow->cpu_load_handler),
					 &(device_shadow->led_actuator));
	if (rc != SUCCESS)
		return rc;

	rc = aws_iot_finalize_json_document(json_doc_buf, size_json_doc_buf);
	if (rc != SUCCESS)
		return rc;

	return aws_iot_shadow_update(mqtt_client, aws_cfg.thing_name,
				     json_doc_buf,
				     shadow_update_status_callback,
				     (void *) device_shadow, 4, true);
}

/**
 * led_actuate_callback() - Callback to be executed on receiving the LED value
 *
 * @json_string:	Thing name of the shadow to be updated.
 * @len:		The response of the action.
 * @json_struct:	The struct used to parse JSON value.
 */
static void led_actuate_callback(const char *json_string, uint32_t len,
				 jsonStruct_t *json_struct)
{
	bool status = false;
	int led_gpio = aws_cfg.led_gpio;
	IOT_UNUSED(json_string);
	IOT_UNUSED(len);

	if (json_struct == NULL)
		return;

	status = *(bool *) (json_struct->pData);

	if (led_gpio <= -1 ||
	    (led_gpio > -1 && set_gpio_value(led_gpio, status) == 0)) {
		IOT_INFO("Delta - LED state changed to %s", status ? ON : OFF);
		device_shadow->led_update = 1;
	} else {
		IOT_ERROR("Error setting LED to %s", status ? ON : OFF);
	}
}

/**
 * shadow_update_status_callback() - AWS IoT Device SDK update status callback
 *
 * Callback to inform of the response from the AWS IoT Shadow service.
 *
 * @pThingName:		Thing name of the shadow to be updated.
 * @action:		The response of the action.
 * @status:		Update action status: accepted, rejected or time out.
 * @pReceivedJsonDocument: Received JSON document.
 * @pContextData:	Data for the callback. NULL if not used.
 */
static void shadow_update_status_callback(const char *pThingName,
					  ShadowActions_t action,
					  Shadow_Ack_Status_t status,
					  const char *pReceivedJsonDocument,
					  void *pContextData)
{
	device_shadow_t *dev_shadow = NULL;

	IOT_UNUSED(pThingName);
	IOT_UNUSED(pReceivedJsonDocument);

	if (action != SHADOW_UPDATE)
		return;

	dev_shadow = (device_shadow_t *)pContextData;

	switch(status) {
	case SHADOW_ACK_TIMEOUT:
		IOT_INFO("Shadow update timeout");
		break;
	case SHADOW_ACK_REJECTED:
		IOT_INFO("Shadow update rejected");
		break;
	case SHADOW_ACK_ACCEPTED:
		IOT_INFO("Shadow update accepted");
		dev_shadow->temp_update = 0;
		dev_shadow->cpu_load_update = 0;
		dev_shadow->led_update = 0;
		break;
	}
}

/**
 * disconnect_callback() - AWS IoT Device SDK disconnect callback
 *
 * It is called whenever the client disconnects with error.
 *
 * @mqtt_client:	MQTT client.
 * @data:		Reference to the data to be passed as argument when
 * 			disconnect handler is called
 */
static void disconnect_callback(AWS_IoT_Client *mqtt_client, void *data)
{
	IoT_Error_t rc = FAILURE;

	IOT_WARN("MQTT Disconnect");

	if (mqtt_client == NULL)
		return;

	IOT_UNUSED(data);

	if (aws_iot_is_autoreconnect_enabled(mqtt_client)) {
		IOT_INFO("Auto-reconnect enabled. Reconnecting attempt will start now");
	} else {
		IOT_WARN("Auto-reconnect not enabled. Starting manual reconnect...");
		rc = aws_iot_mqtt_attempt_reconnect(mqtt_client);
		if (rc == NETWORK_RECONNECTED) {
			IOT_WARN("Manual Reconnect successful");
		} else {
			IOT_WARN("Manual Reconnect failed, error: %d", rc);
		}
	}
}
