#include "stdafx.h"

void CToxProto::BootstrapUdpNode(Tox *tox, const char *address, int port, const char *hexKey)
{
	if (address == nullptr || hexKey == nullptr)
		return;

	ToxBinAddress binKey(hexKey, TOX_PUBLIC_KEY_SIZE * 2);
	TOX_ERR_BOOTSTRAP error;
	if (!tox_bootstrap(tox, address, port, binKey, &error))
		debugLogA(__FUNCTION__ ": failed to bootstrap node %s:%d \"%s\" (%d)", address, port, hexKey, error);
}

void CToxProto::BootstrapTcpRelay(Tox *tox, const char *address, int port, const char *hexKey)
{
	if (address == nullptr || hexKey == nullptr)
		return;

	ToxBinAddress binKey(hexKey, TOX_PUBLIC_KEY_SIZE * 2);
	TOX_ERR_BOOTSTRAP error;
	if (!tox_add_tcp_relay(tox, address, port, binKey, &error))
		debugLogA(__FUNCTION__ ": failed to add tcp relay %s:%d \"%s\" (%d)", address, port, hexKey, error);
}

void CToxProto::BootstrapNodesFromDb(Tox *tox, bool isIPv6)
{
	char module[MAX_PATH];
	mir_snprintf(module, "%s_Nodes", m_szModuleName);
	int nodeCount = db_get_w(0, module, TOX_SETTINGS_NODE_COUNT, 0);
	if (nodeCount == 0)
		return;

	char setting[MAX_PATH];
	for (int i = 0; i < nodeCount; i++) {
		mir_snprintf(setting, TOX_SETTINGS_NODE_IPV4, i);
		ptrA address(db_get_sa(0, module, setting));
		mir_snprintf(setting, TOX_SETTINGS_NODE_PORT, i);
		int port = db_get_w(0, module, setting, 33445);
		mir_snprintf(setting, TOX_SETTINGS_NODE_PKEY, i);
		ptrA pubKey(db_get_sa(0, module, setting));
		BootstrapUdpNode(tox, address, port, pubKey);
		BootstrapTcpRelay(tox, address, port, pubKey);
		if (isIPv6) {
			mir_snprintf(setting, TOX_SETTINGS_NODE_IPV6, i);
			address = db_get_sa(0, module, setting);
			BootstrapUdpNode(tox, address, port, pubKey);
			BootstrapTcpRelay(tox, address, port, pubKey);
		}
	}
}

void CToxProto::BootstrapNodesFromJson(Tox *tox, bool isIPv6)
{
	VARSW path(TOX_JSON_PATH);
	long lastUpdate = getDword("NodesUpdate", 0);
	if (!IsFileExists(path) || lastUpdate < (now() - 86400 /* 24h */))
		UpdateNodes();

	JSONNode nodes = ParseNodes();
	auto nodeCount = nodes.size();
	if (nodeCount == 0)
		return;

	static int j = rand() % nodeCount;

	int i = 0;
	while (i < 2) {
		JSONNode node = nodes[j % nodeCount];

		bool udpStatus = node.at("status_udp").as_bool();
		bool tcpStatus = node.at("status_tcp").as_bool();
		if (!udpStatus && !tcpStatus) {
			nodeCount = j - 1;
			j = rand() % nodeCount;
			if (j == 0)
				return;
			continue;
		}

		JSONNode address = node.at("ipv4");
		JSONNode pubKey = node.at("public_key");

		if (udpStatus) {
			int port = node.at("port").as_int();
			BootstrapUdpNode(tox, address.as_string().c_str(), port, pubKey.as_string().c_str());
			if (isIPv6) {
				address = node.at("ipv6");
				BootstrapUdpNode(tox, address.as_string().c_str(), port, pubKey.as_string().c_str());
			}
		}

		if (tcpStatus) {
			JSONNode tcpPorts = node.at("tcp_ports").as_array();
			for (size_t k = 0; k < tcpPorts.size(); k++) {
				int port = tcpPorts[k].as_int();
				BootstrapTcpRelay(tox, address.as_string().c_str(), port, pubKey.as_string().c_str());
				if (isIPv6) {
					address = node.at("ipv6");
					BootstrapTcpRelay(tox, address.as_string().c_str(), port, pubKey.as_string().c_str());
				}
			}
		}

		j++;
		i++;
	}
}

void CToxProto::BootstrapNodes(Tox *tox)
{
	debugLogA(__FUNCTION__": bootstraping DHT");
	bool isIPv6 = getBool("EnableIPv6", 0);
	BootstrapNodesFromJson(tox, isIPv6);
	BootstrapNodesFromDb(tox, isIPv6);
}

void CToxProto::UpdateNodes()
{
	VARSW path(TOX_JSON_PATH);

	debugLogA(__FUNCTION__": updating nodes");
	HttpRequest request(REQUEST_GET, "https://nodes.tox.chat/json");
	NLHR_PTR response(request.Send(m_hNetlibUser));
	if (!response || response->resultCode != HTTP_CODE_OK || response->body.IsEmpty()) {
		debugLogA(__FUNCTION__": failed to dowload tox.json");
		return;
	}

	JSONNode root = JSONNode::parse(response->body);
	if (root.empty()) {
		debugLogA(__FUNCTION__": failed to dowload tox.json");
		return;
	}

	uint32_t lastUpdate = root.at("last_scan").as_int();
	if (lastUpdate <= getDword("NodesUpdate", 0))
		return;

	FILE *hFile = _wfopen(path, L"w");
	if (hFile == nullptr) {
		debugLogA(__FUNCTION__": failed to open tox.json");
		return;
	}

	if (fwrite(response->body, sizeof(char), response->body.GetLength(), hFile) != (size_t)response->body.GetLength())
		debugLogA(__FUNCTION__": failed to write tox.json");

	fclose(hFile);

	setDword("NodesUpdate", lastUpdate);
}

JSONNode CToxProto::ParseNodes()
{
	VARSW path(TOX_JSON_PATH);

	if (!IsFileExists(path)) {
		debugLogA(__FUNCTION__": could not find tox.json");
		return JSONNode(JSON_ARRAY);
	}

	FILE *hFile = _wfopen(path, L"r");
	if (hFile == nullptr) {
		debugLogA(__FUNCTION__": failed to open tox.json");
		return JSONNode(JSON_ARRAY);
	}

	_fseeki64(hFile, 0, SEEK_END);
	size_t size = _ftelli64(hFile);
	ptrA json((char*)mir_calloc(size));
	rewind(hFile);
	fread(json, sizeof(char), size, hFile);
	fclose(hFile);

	JSONNode root = JSONNode::parse(json);
	if (root.empty()) {
		debugLogA(__FUNCTION__": failed to parse tox.json");
		return JSONNode(JSON_ARRAY);
	}

	return root.at("nodes").as_array();
}