/* New sucking module, pulling :-) */
/*
New sucking module:
	
Allow 'n' parallel sucks per feed.
Use this sequence
	group x.y
	(fix upto counter if wrong)
If we hit a gap send
	xhdr Message-ID nnn-
Fetch items
	article ...

If not supported, 
Deal with 'n' sites instead of just the current
ones!!

Then 
	article n
	article n+1
	article n+2
	article n+3
	...
Each channel may to a different host
LImit on channels can be 'per host' 
Close channels not being used.
Improved queue of waiting suck requests.
(allow header sucking)
	
More informative downloading message if it fails.
Give reason for failure
Gives reasons of messages being accepted/rejected
Correctly detect close

Store file for each feed, with:
	  GID:UPTO:

*/
#define MAXSUCK 200

enum {P_CLOSED,P_OPEN,P_LIST};
typedef struck SUCK {
	char *site;
	char *user;
	char *pass;
	int nlinks;
	int maxlinks;
	char *mask;
	int nmask;
	int upto;
} Suck;
typedef struct LINK {
	int chan;
	char *site;
	int nsite;	/* Number of links to this site */
	int state;
	char *group;	/* Group we are fetching */
	struct LINK *next;
	Suck *suck;
} Link;
static int nsucks;
static Link *links;
static Suck sucks[MAX_SUCKS];
static Link *chan_links[MAX_CHAN];
void pull_init(void)
{
	pull_free(); /* close all and free data */
	pull_addone(config_get(C_NNTP_SUCK1));
	pull_addone(config_get(C_NNTP_SUCK2));
	pull_addone(config_get(C_NNTP_SUCK3));
	pull_addone(config_get(C_NNTP_SUCK4));
	pull_addone(config_get(C_NNTP_SUCK5));
	emsg("Should do rest, suck5...and other settings- re-arrange conf.h to maek this easy\n"):
}
void pull_addone(char *s)
{
	int i=nsucks++;
	sucks[i].site = mystrdup(s);
	sucks[i].maxlinks = 2;
}
void pull_idle(void)
{

}
void pull_start(int sk)
{
	int willbeone = FALSE;
	/* Find a free link to that site */
	for (l=links;l!=NULL;l=l->next) {
		if (l->suck!=sk) continue;	
		if (l->state==P_OPENING) {willbeone = TRUE; continue;}
		break;
	}
	if (l==NULL) if (!willbeone) for (l=links;l!=NULL;l=l->next) {
		if (l->suck!=sk) continue;	
		if (l->state==P_CLOSED) {
			pull_line_open(l); /* Open the link up again */
			return;
		}
		break;
	}
	if (l==NULL) {
		if (willbeone) return; /* No channel yet but its on the way */
		if (sucks[sk].nlinks>=sucks[sk].maxlinks) return; /* all busy*/
		l = pull_new_link(sk); /* Open a new one to that site */
		return; /* Not in right state yet */
	}
	pull_start_link(l);
} 
void pull_open_link(Link *l)
{
	l->chan = chan_open(l->site);
	if (l->chan<0) {emsg("pull: Open (%s) failed\n",l->site); return;}
	l->state = P_OPENING;
	chan_setstate(l->chan,PULL_TYPE)
}
void pull_start_link(Link *l)
{
	/* Get the next group to suck from that site */
	if (!pull_next(&grp,suck)) {
		/* Nothing to suck, close the link down */
		pull_link_close(l);
		return; 
	}
	pull_write(l,"GROUP %s\r\n",group_name(grp));
	l->state = P_GROUP;
}
void pull_data_chan(int chan, char *data, int len)
{
	Link *l = chan_links[chan];
	if (l==NULL) return;
	if (l->state==P_BODY) {pull_data_body(l,data,len); return;}
	data[len] = 0;
	for (;len>0;len--) {
		if (data[len-1]!='\n') if (data[len-1]!='\r') break;
		len--;
		data[len] = 0;
	}
	if (len>400) data[len] = 0;
	pull_data(l,data;
}
void pull_data(Link *l, char *line)
{
	int code = atoi(line);

again:
	switch (l->state) {
	case P_OPENING:
		 if ((code<200) || (code>299)) {
			 emsg("pull: Open (%s) site responded (%s)\n",l->site,line);
		 }
		 if (l->suck->user!=NULL) {
			 l->state = P_USER;
			 goto again;
		 }
		l->state = P_READY;
		goto again;
	case P_USER:
		if (l->suck->user==NULL) {emsg("Fatal internal error-usernull\n"); return;}
		pull_write(l,"AUTHINFO USER %s\r\n",l->suck->user);
		l->state = P_PASS;
		break;
	case P_PASS:
		if (code!=381) {emsg("pull: Fatal bad response to authinfo user (%s)\n",line); link_close(l); return;}
		pull_write(l,"AUTHINFO PASS %s\r\n",l->suck->user);
		l->state = P_PASS2;
		break;
	case P_PASS2:
		if (code!=281) {emsg("pull: Fatal bad response to authinfo pass (%s)\n",line); link_close(l); return;}
		l->state = P_READY;
		goto again;
	case P_READY: /*Dummy entry */ 
		pull_start_link(l);
		break;
	case P_GROUP: /* 211 3892 170808 174699 rec.humor selected*/
		if (code!=211) {
			imsg("pull: Group (%s) not accessable on that system (%s)\n",l->group,line); 
			l->state = P_READY; goto again;
		}
		l->nitems = atoi(get_word(line,1));
		l->min = atoi(get_word(line,2));
		l->max = atoi(get_word(line,3));
		l->upto = pull_upto_get(l->suck,l->group);
		if (l->upto>l->max+1) {
			imsg("pull: Correcting upto for (%s) from %d to %d\n",l->group,l->upto,l->max);
			l->upto = l->max;
			pull_upto_set(l->suck,l->group,l->upto);
		}
		maxold = atoi(config_get(C_SUCK_MAXOLD));
		if (maxold==0) maxold = 2000;
		if (l->upto<l->max-maxold) {
			imsg("pull: Correcting upto to limit maxold (%s) from %d to %d\n",l->group,l->upto,l->max-maxold);
			l->upto = l->max-maxold;
			pull_upto_set(l->suck,l->group,l->upto);
		}
		if (l->upto>=l->max) {
			imsg("pull: Nothing new in %s from %s\n",l->group,l->suck->site);
			l->state = P_READY; goto again;
		}
		l->uptoi = l->upto+1;
		pull_write(l,"STAT %d\r\n",l->uptoi);
		l->state = P_STAT;
		break;
	case P_STAT: /*223 170916 <37AFEF49.5F96@nospam.att.net> article retrieved - request text separately */
		/* If it's not there, then search until we find one */
		if (code>399) { /* Find a real one */
			if (l->uptoi>=l->max) {
				imsg("Got to end of group %s while searching for end of gap\r\n",l->group);
				pull_upto_set(l->suck,l->group,l->max);
				l->state = P_READY;
				goto again;
			}
			pull_write(l,"STAT %d\r\n",++(l->uptoi));
			break;
		}
		if (code!=223) emsg("pull: bad code (%s)\n",line);
		l->state = P_ARTICLE_WANT;
		goto again;
	case P_ARTICLE_WANT:
		n = atoi(get_word(line,1));
		ncpy(mid,get_word(line,2),BFSZ-10);
		if (l->uptoi>n) {
			emsg("pull: Server (%s) group (%s) returned bad line when asked for stat %d\n",l->suck->site,l->group,l->uptoi,line);
			pull_upto_set(l->suck,l->group,l->max);
			l->state = P_READY;
			goto again;
		}
		if (hist_gotit2(mid,l->chan)) {
			l->suck->ndup++;
			l->state = P_ARTICLE_NEXT;
			goto again;
		}
		l->state = P_ARTICLE;
		goto again;
	case P_ARTICLE: /*220 170916 <37AFEF49.5F96@nospam.att.net> article retrieved - head and body foll*/
		pull_write(l,"ARTICLE\r\n");
		l->state = P_ARTICLE_STATUS;
		break;
	case P_ARTICLE_STATUS:
	case P_ARTICLE_NEXT:
		++(l->uptoi);
		if (l->uptoi>=l->max) {
			pull_upto_set(l->suck,l->group,l->max);
			l->state = P_READY;
			goto again;
		}
		pull_write(l,"NEXT\r\n");
		l->state = P_ARTICLE_NEXT2;
		break;
	case P_ARTICLE_NEXT2:
		if (code!=223) { /* Find a real one */
			if (code!=421) emsg("pull: %s from %s, no next article %s\n",l->group,l->suck->site,line);
			pull_upto_set(l->suck,l->group,l->max);
			l->state = P_READY;
			goto again;
		}
		l->state = P_ARTICLE_WANT;
		goto again;
	case P_LISTGROUP:
		
	case P_NEXT:
	case P_NEXT:

	}

}
void pull_fetch(char *group, char *site)
{
	/* Try and find a free link to that site */
	for (l=links;l!=NULL:l=l->next) {
		if (strcmp(l->site,site)!=0) continue;
		{

		}
		
	}
	
	l = pull_find_link(site);
	if (l<0) l = pull_add_link(site);
}

/*
	Each sucking feed has it's own queue and mask
*/
extern int xmax_groups;



int pull_add(int grp, int suck, int isurgent) 
{
	Suck *p;
	if (suck<0) return FALSE; if (suck>=MAX_SUCKS) return FALSE;	 
	p = &sucks[suck];
	if (p->mask==NULL) {
		p->mask = mymalloc(xmax_groups/8+10);
		memset(p->mask,0,xmax_groups/8+1);
	}
	if (bmp_isset(p->mask,grp)) return TRUE;
	p->nmask++;
	if (isurgent) p->upto = grp-1;
	bmp_set(p->mask,grp,TRUE);
	pull_start(suck);
	return TRUE;
}
int pull_next(int *grp, int suck) 
{
	p = &sucks[suck];
	if (p->nmask<=0) return -1;
	p->upto++;
	for (;p->upto<xmax_groups;p->upto++) {
		if (bmp_isset(p->mask,p->upto) {
			bmp_set(p->mask,p->upto,FALSE);
			p->nmask--;
			return p->upto;
		}
	}
	for (p->upto=0;p->upto<xmax_groups;p->upto++) {
		if (bmp_isset(p->mask,p->upto) {
			bmp_se(p->mask,p->upto,FALSE);
			p->nmask--;
			return p->upto;
		}
	}
	p->nmask = 0;
	return -1;
}
