#include "dnsResolver.hpp"

#include "DetachedScheduler.hpp"
#include "ObfusString.hpp"
#include "WeakRef.hpp"

#if SOUP_WASM
#include "dnsHttpResolver.hpp"
#elif SOUP_WINDOWS || SOUP_LINUX
#include "dnsOsResolver.hpp"
#else
#include "dnsSmartResolver.hpp"
#endif

NAMESPACE_SOUP
{
#if !SOUP_WASM
	static DetachedScheduler dns_async_sched;

	struct dnsAsyncExecTask : public dnsLookupTask
	{
		WeakRef<const dnsResolver> resolv;
		dnsType qtype;
		std::string name;

		dnsAsyncExecTask(const dnsResolver& resolv, dnsType qtype, const std::string& name)
			: resolv(&resolv), qtype(qtype), name(name)
		{
		}

		void onTick() final
		{
			auto pResolv = resolv.getPointer();
			SOUP_ASSERT(pResolv); // Resolver has been deleted in the time the task was started. Was it stack-allocated?
			result = pResolv->lookup(qtype, name);
			setWorkDone();
		}
	};

	struct dnsAsyncWatcherTask : public dnsLookupTask
	{
		SharedPtr<dnsAsyncExecTask> watched_task;

		dnsAsyncWatcherTask(SharedPtr<dnsAsyncExecTask>&& watched_task)
			: watched_task(std::move(watched_task))
		{
		}

		void onTick() final
		{
			if (watched_task->isWorkDone())
			{
				result = std::move(watched_task->result);
				watched_task.reset();
				setWorkDone();
			}
		}

		std::string toString() const SOUP_EXCAL final
		{
			return ObfusString("dnsAsyncWatcherTask");
		}
	};
#endif

	SharedPtr<dnsResolver> dnsResolver::makeDefault()
	{
#if SOUP_WASM
		return soup::make_shared<dnsHttpResolver>();
#elif SOUP_WINDOWS || SOUP_LINUX
		// Pros:
		// - Fast responses (thanks to caching and using UDP in most cases)
		// - Works as expected in Docker in regards to talking to other containers by name (respects /etc/hosts and such)
		// Cons:
		// - Many ISPs provide disingenuous DNS servers, even blocking sites like pastebin.com
		return soup::make_shared<dnsOsResolver>();
#else
		// Pros:
		// - Works on any platform with sockets
		// Cons:
		// - Might not work for Chinese users (https://github.com/net4people/bbs/issues/295#issuecomment-2700908050)
		return soup::make_shared<dnsSmartResolver>();
#endif
	}

#if !SOUP_WASM
	std::vector<IpAddr> dnsResolver::lookupIPv4(const std::string& name) const
	{
		return simplifyIPv4LookupResults(lookup(DNS_A, name));
	}

	std::vector<IpAddr> dnsResolver::lookupIPv6(const std::string& name) const
	{
		return simplifyIPv6LookupResults(lookup(DNS_AAAA, name));
	}

	Optional<std::vector<UniquePtr<dnsRecord>>> dnsResolver::lookup(dnsType qtype, const std::string& name) const
	{
		auto task = makeLookupTask(qtype, name);
		task->run();
		return std::move(task->result);
	}

	UniquePtr<dnsLookupTask> dnsResolver::makeLookupTask(dnsType qtype, const std::string& name) const
	{
		return soup::make_unique<dnsAsyncWatcherTask>(dns_async_sched.add<dnsAsyncExecTask>(*this, qtype, name));
	}

	std::vector<IpAddr> dnsResolver::simplifyIPv4LookupResults(const Optional<std::vector<UniquePtr<dnsRecord>>>& results)
	{
		std::vector<IpAddr> res{};
		if (results.has_value())
		{
			for (const auto& r : *results)
			{
				if (r->type == DNS_A)
				{
					res.emplace_back(static_cast<dnsARecord*>(r.get())->data);
				}
			}
		}
		return res;
	}

	std::vector<IpAddr> dnsResolver::simplifyIPv6LookupResults(const Optional<std::vector<UniquePtr<dnsRecord>>>& results)
	{
		std::vector<IpAddr> res{};
		if (results.has_value())
		{
			for (const auto& r : *results)
			{
				if (r->type == DNS_AAAA)
				{
					res.emplace_back(static_cast<dnsAaaaRecord*>(r.get())->data);
				}
			}
		}
		return res;
	}
#endif
}
