#pragma once
#include <nngpp/nngpp.h>
#include <nngpp/protocol/bus0.h>

#include <nlohmann/json.hpp>
using json = nlohmann::json;

#include <iostream>
#include <string>
#include <mutex>
#include <functional>
#include <thread>

enum msg_t {EVENT, COMMAND};

namespace Shuuki::Socket
{
class Connection
{
private:
	static void socket_listen_thread(Connection* sock_con, std::function<bool(json::string_t)> filter_func)
	{
		while (true)
		{
			nng::msg msg = sock_con->sock.recv_msg();
			std::string msg_content((const char*)msg.body().data(), msg.body().size());
			json::string_t data(msg_content);

			if (!filter_func || filter_func(data))
			{
				sock_con->con_mtx.lock();
				sock_con->listen_queue.push_back(data);
				sock_con->con_mtx.unlock();
			}

		}
	}

public:
	nng::socket sock;
	std::mutex con_mtx;
	std::list<json> listen_queue;

	Connection(const char* addr)
	{
		try
		{
			this->sock = nng::bus::open();
			this->sock.listen(addr);
		}
		catch (const nng::exception& e)
		{
			this->sock.dial(addr);
		}
	}

	template <class T, class Cast = T>
	void send_delta_event(const std::string& time_stmp, const std::string& s_name, T val)
	{
		json data = {
			{"time_stamp", time_stmp},
			{"signal_name", s_name},
			{"value", (Cast)val}
		};

		send_msg(EVENT, "signal_value_delta", data);
	}

	void send_msg(msg_t message_t, std::string subtype, json data)
	{
		json pl = {
			{"message_type", message_t == COMMAND ? "COMMAND" : "EVENT"},
			{"message_sub_type", subtype},
			{"origin", NULL},
			{"destination", NULL},
			{"data", data}
		};

		std::string pl_str = pl.dump();

		nng::msg msg(pl_str.length());
		memcpy(msg.body().data(), pl_str.c_str(), pl_str.length());

		try
		{
			this->sock.send(std::move(msg));
		}
		catch (const std::system_error& e)
		{
			std::cout << "Sending of message: '" << pl.dump(4) << "' failed!" << std::endl;
		}
	}

	json::string_t get_msg()
	{
		nng::msg msg = sock.recv_msg();
		std::string msg_content((const char*)msg.body().data(), msg.body().size());
		return json::string_t(msg_content);
	}

	void listen_socket(std::function<bool(json::string_t)> filter_func)
	{
		std::thread listen_t(socket_listen_thread, this, filter_func);
	}

	std::list<json>& get_queue_content()
	{
		std::lock_guard<std::mutex> mtx_grd(con_mtx);

		std::list<json> queue_cpy(this->listen_queue);
		this->listen_queue.clear();

		return queue_cpy;
	}
};
}
