
#include "meta.h"
#include "caddrinfo.h"
#include "acfg.h"
#include "debug.h"
#include "lockable.h"
#include "cleaner.h"

using namespace MYSTD;

static map<string, CAddrInfo::SPtr> mapDnsCache;
static pthread_mutex_t lockDnsCache= PTHREAD_MUTEX_INITIALIZER;

CAddrInfo::CAddrInfo() :
	m_nExpTime(0), m_addrInfo(NULL), m_resolvedInfo(NULL)
{
}

bool CAddrInfo::Resolve(const string & sHostname, const string &sPort,
		string & sErrorBuf)
{
	LOGSTART2("CAddrInfo::Resolve", "Resolving " << sHostname);

	static struct addrinfo hints =
	{
	// we provide numbers, no resolution needed; only supported addresses
			AI_NUMERICSERV|AI_ADDRCONFIG, PF_UNSPEC, SOCK_STREAM, IPPROTO_TCP,
			0, NULL, NULL, NULL };

	// if only one family is specified, filter on this earlier
	if(acfg::conprotos[0] != PF_UNSPEC && acfg::conprotos[1] == PF_UNSPEC)
		hints.ai_family = acfg::conprotos[0];

	if (m_resolvedInfo)
	{
		freeaddrinfo(m_resolvedInfo);
		m_resolvedInfo=NULL;
	}

	int r=getaddrinfo(sHostname.c_str(), sPort.c_str(), &hints,
					&m_resolvedInfo);

	if (0!=r)
	{
		sErrorBuf=(tSS()<<"503 DNS error for hostname "<<sHostname<<": "<<gai_strerror(r)
				<<". If "<<sHostname<<" refers to a configured cache repository, "
				"please check the corresponding configuration file.");
		return false;
	}

	LOG("Host resolved");

	for (struct addrinfo * pCur=m_resolvedInfo; pCur; pCur = pCur->ai_next)
	{
		if (pCur->ai_socktype == SOCK_STREAM&& pCur->ai_protocol == IPPROTO_TCP)
		{
			m_addrInfo=pCur;
			return true;
		}
	}
	LOG("couldn't find working DNS config");

	sErrorBuf="500 DNS resolution error";

	// TODO: remove me from the map
	return false;
}

CAddrInfo::~CAddrInfo()
{
	if (m_resolvedInfo)
		freeaddrinfo(m_resolvedInfo);
}
	

CAddrInfo::SPtr CAddrInfo::CachedResolve(const string & sHostname, const string &sPort, string &sErrorMsgBuf)
{
	//time_t timeExpired=time(NULL)+acfg::dnscachetime;
	time_t now(time(0));

	SPtr p;

	{
		lockguard g(&lockDnsCache);
		SPtr & cand = mapDnsCache[sHostname];
		if(cand && cand->m_nExpTime >= now)
			return cand;
		cand.reset(new CAddrInfo);
		p=cand;
		// lock the internal class and keep it until we are done with preparations
		p->lock();
	}

	if(!p)
		return SPtr(); // weird...

	if (p->Resolve(sHostname, sPort, sErrorMsgBuf))
	{
		p->m_nExpTime = time(0) + acfg::dnscachetime;
		p->unlock();
	}
	else // not good, remove from the cache again
	{
		aclog::err( (tSS()<<"Error resolving "<<sHostname<<": " <<sErrorMsgBuf).c_str());
		lockguard g(&lockDnsCache);
		mapDnsCache.erase(sHostname);
		p->unlock();
		p.reset();
	}
	return p;
}

time_t CAddrInfo::ExpireCache()
{
	lockguard g(&lockDnsCache);
	time_t now(time(0)), ret(cleaner::never);

	for(map<string, CAddrInfo::SPtr>::iterator it=mapDnsCache.begin();
			it!=mapDnsCache.end(); )
	{
		if(it->second)
		{
			if(it->second->m_nExpTime<now)
			{
				mapDnsCache.erase(it++);
				continue;
			}
			else
				ret=min(ret, it->second->m_nExpTime);
		}

		++it;
	}
	return ret;
}

