/**
 * $Id$
 *
 * Copyright (C) 2013 Daniel-Constantin Mierla (asipto.com)
 *
 * This file is part of Kamailio, a free SIP server.
 *
 * This file is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version
 *
 *
 * This file is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 */


#include <assert.h>
#include <ctype.h>
#include <string.h>
#include <stdlib.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <netinet/in.h>

#include "../../dprint.h"
#include "../../hashes.h"
#include "../../pvar.h"


#define PV_DNS_ADDR 64
#define PV_DNS_RECS 32

typedef struct _sr_dns_record {
	int type;
	char addr[PV_DNS_ADDR];
} sr_dns_record_t;

typedef struct _sr_dns_item {
	str name;
	unsigned int hashid;
	char hostname[256];
	int count;
	int ipv4;
	int ipv6;
	sr_dns_record_t r[PV_DNS_RECS];
	struct _sr_dns_item *next;
} sr_dns_item_t;

#define SR_DNS_PVIDX	1

typedef struct _dns_pv {
	sr_dns_item_t *item;
	int type;
	int flags;
	pv_spec_t *pidx;
	int nidx;
} dns_pv_t;


static sr_dns_item_t *_sr_dns_list = NULL;

/**
 *
 */
sr_dns_item_t *sr_dns_get_item(str *name)
{
	sr_dns_item_t *it = NULL;
	unsigned int hashid = 0;

	hashid =  get_hash1_raw(name->s, name->len);

	it = _sr_dns_list;
	while(it!=NULL)
	{
		if(it->hashid==hashid && it->name.len == name->len
				&& strncmp(it->name.s, name->s, name->len)==0)
			return it;
		it = it->next;
	}
	return NULL;
}

/**
 *
 */
sr_dns_item_t *sr_dns_add_item(str *name)
{
	sr_dns_item_t *it = NULL;
	unsigned int hashid = 0;

	hashid =  get_hash1_raw(name->s, name->len);

	it = _sr_dns_list;
	while(it!=NULL)
	{
		if(it->hashid==hashid && it->name.len == name->len
				&& strncmp(it->name.s, name->s, name->len)==0)
			return it;
		it = it->next;
	}
	/* add new */
	it = (sr_dns_item_t*)pkg_malloc(sizeof(sr_dns_item_t));
	if(it==NULL)
	{
		LM_ERR("no more pkg\n");
		return NULL;
	}
	memset(it, 0, sizeof(sr_dns_item_t));
	it->name.s = (char*)pkg_malloc(name->len+1);
	if(it->name.s==NULL)
	{
		LM_ERR("no more pkg.\n");
		pkg_free(it);
		return NULL;
	}
	memcpy(it->name.s, name->s, name->len);
	it->name.s[name->len] = '\0';
	it->name.len = name->len;
	it->hashid = hashid;
	it->next = _sr_dns_list;
	_sr_dns_list = it;
	return it;
}


/**
 *
 */
int pv_parse_dns_name(pv_spec_t *sp, str *in)
{
	dns_pv_t *dpv=NULL;
	char *p;
	str pvc;
	str pvs;
	str pvi;
	int sign;

	if(sp==NULL || in==NULL || in->len<=0)
		return -1;

	dpv = (dns_pv_t*)pkg_malloc(sizeof(dns_pv_t));
	if(dpv==NULL)
		return -1;

	memset(dpv, 0, sizeof(dns_pv_t));

	p = in->s;

	while(p<in->s+in->len && (*p==' ' || *p=='\t' || *p=='\n' || *p=='\r'))
		p++;
	if(p>in->s+in->len || *p=='\0')
		goto error;
	pvc.s = p;
	while(p < in->s + in->len)
	{
		if(*p=='=' || *p==' ' || *p=='\t' || *p=='\n' || *p=='\r')
			break;
		p++;
	}
	if(p>in->s+in->len || *p=='\0')
		goto error;
	pvc.len = p - pvc.s;
	if(*p!='=')
	{
		while(p<in->s+in->len && (*p==' ' || *p=='\t' || *p=='\n' || *p=='\r'))
			p++;
		if(p>in->s+in->len || *p=='\0' || *p!='=')
			goto error;
	}
	p++;
	if(*p!='>')
		goto error;
	p++;

	pvs.len = in->len - (int)(p - in->s);
	pvs.s = p;
	pvi.s = 0;
	pvi.len = 0;
	if(pvs.s[pvs.len-1]==']') {
		/* index */
		p = memchr(pvs.s, '[', pvs.len-1);
		if(p==NULL) {
			goto error;
		}
		pvi.s = p + 1;
		pvi.len = pvs.s + pvs.len - pvi.s;
		pvs.len = p - pvs.s;
	}
	LM_DBG("dns [%.*s] - key [%.*s] index [%.*s]\n", pvc.len, pvc.s,
			pvs.len, pvs.s, (pvi.len>0)?pvi.len:0, (pvi.s!=NULL)?pvi.s:0);

	dpv->item = sr_dns_add_item(&pvc);
	if(dpv->item==NULL)
		goto error;

	switch(pvs.len)
	{
		case 4: 
			if(strncmp(pvs.s, "addr", 4)==0)
				dpv->type = 0;
			else if(strncmp(pvs.s, "type", 4)==0)
				dpv->type = 1;
			else if(strncmp(pvs.s, "ipv4", 4)==0)
				dpv->type = 2;
			else if(strncmp(pvs.s, "ipv6", 4)==0)
				dpv->type = 3;
			else goto error;
		break;
		case 5: 
			if(strncmp(pvs.s, "count", 5)==0)
				dpv->type = 4;
			else goto error;
		break;
		default:
			goto error;
	}

	if(pvi.len>0)
	{
		if(pvi.s[0]==PV_MARKER)
		{
			dpv->pidx = pv_cache_get(&pvi);
			if(dpv->pidx==NULL)
				goto error;
			dpv->flags |= SR_DNS_PVIDX;
		} else {
			sign = 1;
			p = pvi.s;
			if(*p=='-')
			{
				sign = -1;
				p++;
			}
			dpv->nidx = 0;
			while(p<pvi.s+pvi.len && *p>='0' && *p<='9')
			{
				dpv->nidx = dpv->nidx * 10 + *p - '0';
				p++;
			}
			if(p!=pvi.s+pvi.len)
			{
				LM_ERR("invalid index [%.*s]\n", in->len, in->s);
				return -1;
			}
			dpv->nidx *= sign;
		}
	}
	sp->pvp.pvn.u.dname = (void*)dpv;
	sp->pvp.pvn.type = PV_NAME_OTHER;

	return 0;

error:
	LM_ERR("error at PV dns name: %.*s\n", in->len, in->s);
	return -1;
}

/**
 *
 */
int pv_get_dns(sip_msg_t *msg, pv_param_t *param,
		pv_value_t *res)
{
	dns_pv_t *dpv;
	pv_value_t val;

	if(msg==NULL || param==NULL)
		return -1;

	dpv = (dns_pv_t*)param->pvn.u.dname;
	if(dpv==NULL || dpv->item==NULL)
		return -1;

	if(dpv->pidx!=NULL)
	{
		if(pv_get_spec_value(msg, dpv->pidx, &val)<0
				|| (!(val.flags&PV_VAL_INT)))
		{
			LM_ERR("failed to evaluate index variable\n");
			return pv_get_null(msg, param, res);
		}
	} else {
		val.ri = dpv->nidx;
	}
	if(val.ri<0)
	{
		if(dpv->item->count+val.ri<0) {
			return pv_get_null(msg, param, res);
		}
		val.ri = dpv->item->count+val.ri;
	}
	if(val.ri>=dpv->item->count) {
		return pv_get_null(msg, param, res);
	}
	switch(dpv->type)
	{
		case 0: /* address */
			return pv_get_strzval(msg, param, res,
					dpv->item->r[val.ri].addr);
		case 1: /* type */
			return pv_get_sintval(msg, param, res,
					dpv->item->r[val.ri].type);
		case 2: /* ipv4 */
			return pv_get_sintval(msg, param, res,
					dpv->item->ipv4);
		case 3: /* ipv6 */
			return pv_get_sintval(msg, param, res,
					dpv->item->ipv6);
		case 4: /* count */
			return pv_get_sintval(msg, param, res,
					dpv->item->count);
		default: /* else */
			return pv_get_null(msg, param, res);
	}
}

/**
 *
 */
int dns_init_pv(char *path)
{
	return 0;
}

/**
 *
 */
void dns_destroy_list(void)
{
	return;
}

/**
 *
 */
void dns_destroy_pv(void)
{
	return;
}

/**
 *
 */
int dns_update_pv(str *hostname, str *name)
{
	sr_dns_item_t *dr = NULL;
	struct addrinfo hints, *res, *p;
	struct sockaddr_in *ipv4;
	struct sockaddr_in6 *ipv6;
	void *addr;
	int status;
	int i;
	
	if(hostname->len>255)
	{
		LM_DBG("target hostname too long (max 255): %s\n", hostname->s);
		return -2;
	}
	
	dr = sr_dns_get_item(name);
	if(dr==NULL)
	{
		LM_DBG("container not found: %s\n", name->s);
		return -3;
	}

	/* reset the counter */
	dr->count = 0;
	dr->ipv4  = 0;
	dr->ipv6  = 0;

	strncpy(dr->hostname, hostname->s, hostname->len);
	dr->hostname[hostname->len] = '\0';
	LM_DBG("attempting to resolve: %s\n", dr->hostname);

	memset(&hints, 0, sizeof(hints));
	hints.ai_family = AF_UNSPEC; /* allow any of AF_INET or AF_INET6 */
	// hints.ai_socktype = SOCK_STREAM;
	hints.ai_socktype = SOCK_DGRAM;

	if ((status = getaddrinfo(dr->hostname, NULL, &hints, &res)) != 0)
	{
        LM_ERR("unable to resolve %s - getaddrinfo: %s\n",
				dr->hostname, gai_strerror(status));
        return -4;
    }

	i=0;
	for(p=res; p!=NULL; p=p->ai_next)
	{
		if (p->ai_family==AF_INET)
		{
			dr->ipv4 = 1;
			dr->r[i].type = 4;
			ipv4 = (struct sockaddr_in *)p->ai_addr;
			addr = &(ipv4->sin_addr);
		} else {
			dr->ipv6 = 1;
			dr->r[i].type = 6;
			ipv6 = (struct sockaddr_in6 *)p->ai_addr;
			addr = &(ipv6->sin6_addr);
		}
		inet_ntop(p->ai_family, addr, dr->r[i].addr,
				PV_DNS_ADDR);
		LM_DBG("#%d - type %d addr: %s (%d)\n", i, dr->r[i].type,
				dr->r[i].addr, p->ai_socktype);
		i++;
		if(i==PV_DNS_RECS) {
			LM_WARN("more than %d addresses for %s - truncating\n",
					PV_DNS_RECS, dr->hostname);
			break;
		}
    }
	freeaddrinfo(res);

	dr->count = i;

	LM_DBG("dns PV updated for: %s (%d)\n", dr->hostname, i);

	return 1;
}
