#include "mqtt_client.hpp"

namespace mqtt_dataio
{
	bool client::__m_init = false;
	struct mosquitto *client::__m_mosq = nullptr;
	std::shared_ptr<std::thread> client::__m_thread = nullptr;
	std::mutex client::__m_mtx;
	std::map<std::string, mqtt_dataio::subscriber_basic *> client::__m_sub_list;

	void client::init(const std::string &ip, const uint16_t &port, const std::string &user, const std::string &pwd)
	{
		if (__m_init)
			return;

		/* Required before calling other mosquitto functions */
		mosquitto_lib_init();

		/* Create a new client instance.
		 * id = NULL -> ask the broker to generate a client id for us
		 * clean session = true -> the broker should remove old sessions when we connect
		 * obj = NULL -> we aren't passing any of our private data for callbacks
		 */
		__m_mosq = mosquitto_new(nullptr, true, nullptr);
		if (__m_mosq == nullptr)
		{
			std::cout << "Error: Out of memory." << std::endl;
			return;
		}

		int rc;
		rc = mosquitto_username_pw_set(__m_mosq, user.data(), pwd.data());
		if (rc != MOSQ_ERR_SUCCESS)
		{
			std::cout << "Error: Problem setting username and password" << std::endl;
		}

		/* Configure callbacks. This should be done before connecting ideally. */
		mosquitto_connect_callback_set(__m_mosq, &client::on_connect);
		mosquitto_subscribe_callback_set(__m_mosq, &client::on_subscribe);
		mosquitto_message_callback_set(__m_mosq, &client::on_message);

		/* Connect to test.mosquitto.org on port 1883, with a keepalive of 60 seconds.
		 * This call makes the socket connection only, it does not complete the MQTT
		 * CONNECT/CONNACK flow, you should use mosquitto_loop_start() or
		 * mosquitto_loop_forever() for processing net traffic. */
		
		rc = mosquitto_connect(__m_mosq, ip.data(), port, 60);
		if (rc != MOSQ_ERR_SUCCESS)
		{
			mosquitto_destroy(__m_mosq);
			std::cout << "Error: " << mosquitto_strerror(rc) << std::endl;
			__m_init = false;
			return;
		}

		__m_thread = std::make_shared<std::thread>([=]()
												   { mosquitto_loop_forever(__m_mosq, -1, 1); });
		__m_thread->detach();

		{
			std::lock_guard<std::mutex> lck(__m_mtx);
			__m_init = true;
		}

		std::cout << "mqtt client init done, connecting to " << ip << ":" << port << std::endl;
	}

	void client::add_suber(const std::string &topic, mqtt_dataio::subscriber_basic *ptr)
	{
		{
			std::lock_guard<std::mutex> lck(__m_mtx);

			if (__m_sub_list.find(topic) == __m_sub_list.end())
			{
				__m_sub_list[topic] = ptr;
				std::cout << "create mqtt_subcriber topic: " << topic << std::endl;
			}
			else
			{
				std::cout << "create mqtt_subcriber failed, had exist same topic: " << topic << std::endl;
			}
		}

		int rc = mosquitto_subscribe(__m_mosq, NULL, topic.data(), 1);
		if (rc != MOSQ_ERR_SUCCESS)
		{
			std::cout << "Error subscribing: " << mosquitto_strerror(rc) << std::endl;
			/* We might as well disconnect if we were unable to subscribe */
			// mosquitto_disconnect(__m_mosq);
		}
	}

	void client::rm_suber(const std::string &topic)
	{
		{
			std::lock_guard<std::mutex> lck(__m_mtx);
			if (__m_sub_list.find(topic) != __m_sub_list.end())
			{
				__m_sub_list.erase(topic);
				std::cout << "destroy mqtt_subcriber topic: " << topic << std::endl;
			}
			else
			{
				std::cout << "destroy mqtt_subcriber failed, not exist same topic: "  << topic << std::endl;
			}
		}
	}

	void client::publish(const std::string &topic, const std::vector<uint8_t> &buff)
	{
		if (!__m_init)
			return;
		int rc = mosquitto_publish(__m_mosq, NULL, topic.data(), buff.size(), buff.data(), 1, false);
		if (rc != MOSQ_ERR_SUCCESS)
		{
			std::cout << "Error publishing: "  << mosquitto_strerror(rc) << std::endl;
		}
	}

	client::~client()
	{
		mosquitto_disconnect(__m_mosq);
		mosquitto_lib_cleanup();

		__m_thread = nullptr;

		{
			std::lock_guard<std::mutex> lck(__m_mtx);
			__m_sub_list.clear();
			__m_init = false;
		}
	}

	void client::on_connect(struct mosquitto *mosq, void *obj, int reason_code)
	{
		std::cout << "on_connect: "  << mosquitto_connack_string(reason_code) << std::endl;
		if (reason_code != 0)
		{
			/* If the connection fails for any reason, we don't want to keep on
			 * retrying in this example, so disconnect. Without this, the client
			 * will attempt to reconnect. */
			// mosquitto_disconnect(mosq);
		}

		// /* Making subscriptions in the on_connect() callback means that if the
		//  * connection drops and is automatically resumed by the client, then the
		//  * subscriptions will be recreated when the client reconnects. */
		// rc = mosquitto_subscribe(mosq, NULL, "example/temperature", 1);
		// if(rc != MOSQ_ERR_SUCCESS){
		// 	fprintf(stderr, "Error subscribing: %s\n", mosquitto_strerror(rc));
		// 	/* We might as well disconnect if we were unable to subscribe */
		// 	mosquitto_disconnect(mosq);
		// }
	}

	/* Callback called when the broker sends a SUBACK in response to a SUBSCRIBE. */
	void client::on_subscribe(struct mosquitto *mosq, void *obj, int mid, int qos_count, const int *granted_qos)
	{
		int i;
		bool have_subscription = false;

		/* In this example we only subscribe to a single topic at once, but a
		 * SUBSCRIBE can contain many topics at once, so this is one way to check
		 * them all. */
		for (i = 0; i < qos_count; i++)
		{
			std::cout << "on_subscribe: "  << i << " :granted qos = " << granted_qos[i] << std::endl;
			if (granted_qos[i] <= 2)
			{
				have_subscription = true;
			}
		}
		if (have_subscription == false)
		{
			/* The broker rejected all of our subscriptions, we know we only sent
			 * the one SUBSCRIBE, so there is no point remaining connected. */
			std::cout << "Error: All subscriptions rejected." << std::endl;
			// mosquitto_disconnect(mosq);
		}
	}

	void client::on_message(struct mosquitto *mosq, void *obj, const struct mosquitto_message *msg)
	{
		std::string topic(msg->topic);
		{
			std::lock_guard<std::mutex> lck(__m_mtx);
			for (auto &it : __m_sub_list)
			{
				if (it.first == topic)
				{
					std::vector<uint8_t> data;
					data.resize(msg->payloadlen);
					memcpy(data.data(), msg->payload, msg->payloadlen);
					it.second->on_message_arrival(data);
				}
			}
		}
	}
} 