#if HAVE_CONFIG_H
#include <config.h>
#endif

#include <sys/types.h>
#include <sys/socket.h>

#include <netinet/in.h>
#include <arpa/inet.h>

#include <netdb.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdarg.h>
#include <errno.h>


#if HAVE_STRINGS_H
#include <strings.h>
#endif

#include "irrdi.h"
#include "trace.h"
#include "utils.h"

char conn_err[512];

extern int debug;

#ifndef HAVE_HSTRERROR

const char*
hstrerror(int err)
{ 
	static char  buffer[512];
	snprintf(buffer,sizeof(buffer),"resolver error %i",err);
	return buffer;
};
#endif
int
fill_sca(char* host,int port,struct sockaddr_in* sca)
{ 
	struct hostent* hte;

	bzero(conn_err,sizeof(conn_err));
	bzero(sca,sizeof(struct sockaddr_in));

	hte=gethostbyname(host);
	if(!hte) { 
		snprintf(conn_err,sizeof(conn_err),"gethostbyname: %s",hstrerror(h_errno));
		return h_errno;
	};

	bcopy(hte->h_addr_list[0],&sca->sin_addr.s_addr,hte->h_length);
	sca->sin_family=PF_INET;
	sca->sin_port=htons(port);

	return 0;
};

struct irrdc* 
irrd_init(char* host, int port)
{ 
	struct irrdc* ir=malloc(sizeof(struct irrdc));
	if(!ir) return NULL;
	bzero(ir,sizeof(struct irrdc));
	if(host) ir->host=strdup(host);
	if(port) ir->port=port;

	if(host&&port) { 
		if(fill_sca(host,port,&ir->sca)) { 
			SX_TRACE2("%s",conn_err);
			if(ir->host) free(ir->host);
			free(ir);
			return NULL;
		};
	};
	return ir;
};

struct irrdcentry*
irrd_cache_find(struct irrdc* ir, char* q)
{ 
	struct irrdcentry* ic;
	if(!ir||!q) return NULL;

	ic=ir->cache;
	while(ic) { 
		if(ic->qwes && !strcmp(ic->qwes,q)) return ic;
		ic=ic->next;
	};
	return NULL;
};

int
irrd_cache_add(struct irrdc* ir, char* q, char* ans)
{ 
	struct irrdcentry* ic, *id;
	if(!ir || !q || !ans) return 0;

	ic=malloc(sizeof(struct irrdcentry));
	if(!ic) return 0;

	bzero(ic,sizeof(struct irrdcentry));
	ic->qwes=strdup(q);
	ic->answer=strdup(ans);

	if(!ir->cache) { 
		ir->cache=ic;
		return 1;
	};
	
	id=ir->cache;

	while(id->next) id=id->next;

	id->next=ic;
	
	return 1;
};
int
irrd_connect(struct irrdc* ir)
{ 
	if(ir->s) close(ir->s);
	ir->s=socket(AF_INET,SOCK_STREAM,0);
	if(ir->s==-1) { 
		return 0;
	};
	if(debug>2) { 
		SX_TRACE2("Connecting to %s...",inet_ntoa(ir->sca.sin_addr));
	};
	if(connect(ir->s,(struct sockaddr*)&ir->sca,sizeof(struct sockaddr_in))==-1) { 
		return 0;
	};
	ir->f=fdopen(ir->s,"r+");
	if(debug>2) { 
		SX_TRACE2("sending !!");
	};
	fprintf(ir->f,"!!\n");

	return 1;
};

int
irrd_question(struct irrdc* ir, char* quest, ...)
{ 
	va_list ap;
	char qbuff[512];
	char rbuff[2048];
	int len;
	struct irrdcentry* ice;

	va_start(ap,quest);
	
	vsnprintf(qbuff,sizeof(qbuff),quest,ap);

	va_end(ap);

	if(ir->answer) free(ir->answer);
	ir->answer=NULL;

	if(debug>2)  
		SX_TRACE2("checking cache for %s", qbuff);

	if((ice=irrd_cache_find(ir,qbuff))) { 
		if(debug>2) 
			SX_TRACE2("oki, found cached entry");
		
		ir->answer=strdup(ice->answer);
		return 1;
	};

	if(debug>2) 
		SX_TRACE2("sending %s", qbuff);
		
	fprintf(ir->f,"%s\n",qbuff);
	fflush(ir->f);

	if(!fgets(rbuff,sizeof(rbuff),ir->f)) { 
		SX_TRACE2("error reading from socket: %i %s",errno,
			strerror(errno));
		if(ferror(ir->f)) { 
			SX_ERROR("fgets ferror: %i",ferror(ir->f));
		};
		if(feof(ir->f)) { 
			SX_ERROR("fgets feof: %i",ferror(ir->f));
		};
		return 0;
	};
	
	if(debug>2) 
		SX_TRACE2("Answer is %s", rbuff);

	switch(rbuff[0]) { 
		case 'A': 
			len=atoi(rbuff+1);
			ir->answer=malloc(len+1);
			if(!ir->answer) { 
				return 0;
			};
			bzero(ir->answer,len+1);

			if(debug>2) 
				SX_TRACE2("Waiting for %i data bytes..",len);

			fread(ir->answer,1,len,ir->f);
			
			if(debug>2) 
				SX_TRACE2("Got %s",ir->answer);

			fgets(rbuff,sizeof(rbuff),ir->f);
			if(debug>2) 
				SX_TRACE2("And resulting control code is %s", rbuff);

			if(rbuff[0]!='C') { 
				/* smth strange.. :( */
				return 0;
			};
			irrd_cache_add(ir,qbuff,ir->answer);
			break;
		case 'C': /* query successfull, no data */
		case 'D': /* no such key */
			break;
		case 'E': /* multiple keys */
		case 'F': /* another error.. */
			return 0;
		default:
			return 0;
	};
	return 1;
};

int
irrd_disc(struct irrdc* ir)
{ 
	if(!ir) { 
		return 0;
	};
	if(debug>2) { 
		SX_TRACE2("sending !q");
	};
	fprintf(ir->f,"!q\n");
	fclose(ir->f);
	close(ir->s);
	return 1;
};

int
irrd_free(struct irrdc* ir)
{
	if(!ir) return 0;

	if(ir->answer) free(ir->answer);
	free(ir);
	return 1;
};
