#include "poplib.h"

#include "lib.h"
#include "lprintf.h"
#define DMID 4
#include "debug.h"

#include "vsnprintf.h"

#ifndef FALSE
#define FALSE 0
#endif

#ifndef TRUE
#define TRUE 1
#endif

extern int emsg(const char * arg_list, ...);
extern int imsg(const char * arg_list, ...);
extern int dmsg(const char * arg_list, ...);

void pc_tcp_error(PopCon *p);
void pc_reset_cb(PopCon *p);

/****************/

PopCon *pc_new() {
	PopCon *p = (PopCon *)calloc(1,sizeof(PopCon));
	return p;
}

void pc_free(PopCon **p)
{
	if (!p || !*p) return ;
	pc_quit(*p);
	free(*p);
	*p = NULL;
}

void pc_set_cb(PopCon *p, void (*cb)(void*,PopCon*), void *data)
{
	p->data = data;
	p->cb = cb;
}

int pc_connect(PopCon *p, char *host, int port)
{
	if (!p) return FALSE;
	if (p->c) pc_disconnect(p);
	tcp_new(&p->c);
	if (!tcp_open(p->c,host,port,NULL)) {
		pc_tcp_error(p);
		return FALSE;
	}
	return TRUE;
}

void pc_disconnect(PopCon *p)
{
	if (!p) return ;
	if (!p->c) return ;
	tcp_close_nice(p->c);
	tcp_free(&p->c);
}

int pc_command(PopCon *p, int read_response, char *format, ...)
{
	char bf[BFSZ];
	va_list args;
	int x;

	if (!p) return FALSE;
	if (!p->c) return FALSE;

	va_start(args,format);
	x = vsnprintf(bf,BFSZ,format,args);
	if (x == -1 || x == BFSZ) bf[(x = BFSZ-1)] = 0;

	if (!pc_send_command(p,bf,x)) return FALSE;
	if (read_response) {
		if (!pc_read_response(p)) return FALSE;
		if (!pc_ok(p)) return FALSE;
	}

	return TRUE;
}

int pc_user(PopCon *p, char *user)
{
	return pc_command(p,TRUE,"USER %s",user);
}

int pc_pass(PopCon *p, char *pass)
{
	return pc_command(p,TRUE,"PASS %s",pass);
}

int pc_quit(PopCon *p)
{
	return pc_command(p,TRUE,"QUIT");
}

int pc_list(PopCon *p)
{
	return pc_command(p,TRUE,"LIST");
}

int pc_uidl(PopCon *p)
{
	return pc_command(p,TRUE,"UIDL");
}

int pc_retr(PopCon *p, char *msg)
{
	return pc_command(p,TRUE,"RETR %s",msg);
}

int pc_send_command(PopCon *p, char *bf, int x)
{
	if (!p) return FALSE;

	/* Ensure exactly one \r\n on command */
	for(; x > 0 && iseol(bf[x-1]); x--) bf[x-1] = '\0';
	if (x > BFSZ-3) x = BFSZ-3;
	strcpy(&bf[x],"\r\n");
	x += 2;

	/* Send command */
	if (tcp_write(p->c,bf,x) == x)
		return TRUE;

	pc_tcp_error(p);
	return FALSE;	
}

int pc_read_response(PopCon *p)
{
	int res = FALSE;
	if (p) {
		while(tcp_readline(p->c,p->result,BFSZ)) {
			if (pc_end(p)) { res = TRUE; break; }
			if (p->cb) p->cb(p->data,p);
		}
		pc_reset_cb(p);
		if (!res) pc_tcp_error(p);
	}
	return res;

}

int pc_end(PopCon *p)
{
	if (!p) return FALSE;
	if (strncmp(p->result,"+OK ",4)==0) return TRUE;
	if (strncmp(p->result,"-ERR ",5)==0) return TRUE;
	return FALSE;
}

int pc_ok(PopCon *p)
{
	if (!p) return FALSE;
	if (strncmp(p->result,"+OK ",4)==0) return TRUE;
	return FALSE;
}

int pc_err(PopCon *p)
{
	if (!p) return FALSE;
	if (strncmp(p->result,"-ERR ",5)==0) return TRUE;
	return FALSE;
}

int pc_read_data_direct(PopCon *p, char *mem, int bytes)
{
	int done,x;

	for(done = 0; done < bytes; done += x) {
		x = tcp_read_timed(p->c,&mem[done],bytes-done,60000);
		if (x <= 0) { pc_tcp_error(p); return FALSE; }
	}
	mem[done] = 0;

	return TRUE;
}

#define RDBUF 32000
int pc_read_data(PopCon *p, int bytes, int(*xwrite)(void*,int,int,void*), void *xp)
{
	char buffer[RDBUF+1];
	int block,done,x;

	for(done = 0; done < bytes; done += x) {
		block = bytes-done;
		if (block > RDBUF) block = RDBUF;
		x = tcp_read_timed(p->c,buffer,block,60000);
		if (x <= 0) { 
			pc_tcp_error(p);
			return FALSE;
		}
		if (xwrite && xwrite(buffer,1,x,xp) != x) {
			return FALSE;
		}
	}

	return TRUE;
}

/****************/

void pc_tcp_error(PopCon *p)
{
	if (!p) return ;
	ncpy(p->result,"-ERR ",BFSZ-1);
	tcp_error(p->c,&p->result[5]);
}

void pc_reset_cb(PopCon *p)
{
	if (!p) return ;
	p->cb = NULL;
	p->data = NULL;
}
