/*
 * distributions.c Copyright 1999 Christopher M Sedore. All Rights Reserved.
 * Please see the "COPYING" file for license details.
 * 
 * This code selects articles for feeds, stores, and filtering
 */

#include "main.h"

typedef unsigned int DWORD;

extern DWORD MaxCrossposts;

struct distinclude {
	char *name;
	MATCHHEAD *dist;
	BOOL matched,poisoned;
	TAILQ_ENTRY(distinclude) list;
};

TAILQ_HEAD(mdist,distinclude) dIncHead;
	
	BOOL
	FreeMatchData(struct article *art) 
{
	if (art->ad) {
		if (art->ad->newsgroups)
			free(art->ad->newsgroups);
		if (art->ad->pathline)
			free(art->ad->pathline);
		free(art->ad);
		art->ad=NULL;
	}
	
	return TRUE;
}

BOOL
PrepareForMatch(struct article *art)
{
	char *s,*s1;
	DWORD l,b;
	BOOL cont;
	ARTICLEDATA *ad;
	
	art->ad=malloc(sizeof(ARTICLEDATA));
	bzero(art->ad,sizeof(ARTICLEDATA));
	
	ad=art->ad;
	
	ad->followupCount=0;
	
	if (GetHeaderRange(art,"Newsgroups:",&b,&l,&cont)) {
		s=s1=malloc(l+10);
		ad->numgroups=0;
		while (s<s1+l) {
			if (art->buf[b+(s-s1)]<33) {
				b++;
				l--;
				continue;
			}
			*s=art->buf[b+(s-s1)];
//			putchar(*s);
			if (*s==',') {
				*s=0;
				ad->numgroups++;
			}
			s++;
		}
		*s=0;	 
		s++;
		*s=0;
		ad->newsgroups=s1;
//		putchar('\n');
	} else {
		syslog(LOG_NEWS|LOG_INFO,"no newsgroups in %s\n",art->mid);
		return FALSE;
	}
	
	if (GetHeaderRange(art,"Path:",&b,&l,&cont)) {
		s=s1=malloc(l+10);
		ad->hopcount=0;
		while (s<s1+l) {
			if (art->buf[b+(s-s1)]<33) {
				b++;
				l--;
				continue;
			}
			*s=art->buf[b+(s-s1)];
			if (*s=='!') {
				*s=0;
				ad->hopcount++;
			}
			s++;
		}
		*s=0;	  
		s++;
		*s=0;
		ad->pathline=s1;
	} else {
		syslog(LOG_NEWS|LOG_INFO,"no path in %s\n",art->mid);
		return FALSE;
	}
	
	
	if (GetHeaderRange(art,"Followup-To:",&b,&l,&cont)) {
		ad->followupCount++;
		s=s1=art->buf;
		while (s<s1+l) {
			if (*s==',') {
				ad->followupCount++;
			}
			s++;
		}
	} 
	
	if (GetHeaderRange(art,"Control:",&b,&l,&cont)) {
		art->isControl=TRUE;
	} 

	if (GetHeaderRange(art,"Approved:",&b,&l,&cont)) {
		art->isApproved=TRUE;
	} 
	
	CheckIncludeMatch(art);
	
	return TRUE;
}

BOOL
MatchHeader(char *pattern,char *string)
{
	char *s;
	
	s=string;
	
	while (*s!=0) {
		if (StringMatch(pattern,s))
			return TRUE;
		while (*s!=0) s++;
		s++;
	}
	
	return FALSE;
}

BOOL
MatchHeaderSpec(char *pattern,char *headername,struct article *art)
{
	char *s,*s1;
	DWORD b,l;
	BOOL cont;
	
	s=malloc(8192);
	
	if (!GetHeaderFromBuf(art->buf,headername,s,8192,art->len)) {
		return FALSE;
	}
	
	if (StringMatch(pattern,s)) {
		free(s);
		return TRUE;
	}
	
	free(s);
	
	return FALSE;
}


BOOL
MatchArticle(struct article *art,MATCHNODE *mnode,BOOL *poison) {
	
	BOOL match=FALSE,nDone=FALSE;
	MATCHNODE *mn;
	void *hold;
	DWORD op;
	char *ng;
	
	if (*poison)
		return FALSE;
	
	ng=art->ad->newsgroups;
	
	while ((*ng!=0) && (!*poison)) {
		mn=mnode;
		nDone=FALSE;
		while ((mn!=NULL) && (!nDone)) {
			match=FALSE;
			op=(mn->operation & 0x0F000000);
			switch (op) {
				case OP_HEADERMATCH: {
						switch (mn->operation & 0xFF) {
							case OP_GROUP: {
									match=StringMatch(ng,mn->cpat);
//					printf("%x mtch %s %s=%i?\n",mn->operation,ng,mn->cpat,match);
									if (match && (mn->operation & OP_POISON)) {
										*poison=TRUE; nDone=TRUE;
										continue;
									}
									if (match && (mn->operation & OP_NOT)) {
										match=FALSE;
										nDone=TRUE;
										continue;
									}
									if (match && !(mn->operation & OP_NOT)) {
										match=TRUE; nDone=TRUE;
										continue;
									}
									mn=mn->next;
									continue;
								} break;
							case OP_PATH: {
									match=MatchHeader(mn->cpat,art->ad->pathline);
//					printf("%x pmtch %s %s=%i?\n",mn->operation,art->ad->pathline,mn->cpat,match);
									if (match & (mn->operation & OP_NOT)) {
										match=TRUE;
										nDone=TRUE;
									} else {
										if (match) {	
											*poison=TRUE; nDone=TRUE;
										}
									}
									mn=mn->next;
									continue;
									
								} break;
						}
					} break;
				case OP_REFDIST: {
						struct distinclude *d;
						d=(struct distinclude *)mn->left;
//				printf("check include %s m=%u p=%u========",
//					d->name,d->matched,d->poisoned);
						if (d->poisoned) { 
//					printf("poisoned\n");
							*poison=TRUE; 
							nDone=TRUE;
							continue;
						}
						match=d->matched;
						if (mn->operation & (OP_NOT|OP_POISON)) {
							if (match) {
//						printf("poisoned\n");
								*poison=TRUE;
								nDone=TRUE;
								continue;
							}
						}
						if (!match) {
//					printf("not matched\n");
							mn=mn->next;
							continue;
						}	
//				printf("matched\n");
						nDone=TRUE;
						continue;
						
					} break;
				case OP_LOGICALOR: {
						if ((MatchArticle(art,mn->left,poison)) ||
							(MatchArticle(art,mn->right,poison)))
							match=TRUE;
						*poison=FALSE;
					} break;
				case OP_LOGICALAND: {
						if ((MatchArticle(art,mn->left,poison)) &&
							(MatchArticle(art,mn->right,poison)))
							match=TRUE;
						*poison=FALSE;
					} break;
				case OP_GREATER: {
						switch (mn->operation & 0xFF) {
							case OP_HOPCOUNT: {
									if (art->ad->hopcount>mn->count)
										match=TRUE;
								}break;
							case OP_GROUPCOUNT: {
									if (art->ad->numgroups>mn->count)
										match=TRUE;
								}break;
							case OP_SIZE: {
									if (art->len>(DWORD)mn->count)
										match=TRUE;
								} break;
							case OP_TIME: {
									if (art->artTime>mn->count) 
										match=TRUE;
								} break;
						}
					} break;
				case OP_LESS: {
						switch (mn->operation & 0xFF) {
							case OP_HOPCOUNT: {
									if (art->ad->hopcount<mn->count)
										match=TRUE;
								}break;
							case OP_GROUPCOUNT: {
									if (art->ad->numgroups<mn->count)
										match=TRUE;
								}break;
							case OP_SIZE: {
									if (art->len<(DWORD)mn->count)
										match=TRUE;
//					printf("%x sz %u<%u=%i?\n",mn->operation,art->len,mn->count,match);
								}break;
							case OP_TIME: {
									if (art->artTime<mn->count) 
										match=TRUE;
								} break;
						}
					} break;
				case OP_ISCONTROL: {
						match=art->isControl;
					} break;
				case OP_ISAPPROVED: {
						match=art->isApproved;
					} break;
				case OP_HSPEC: {
						match=MatchHeaderSpec(mn->cpat,mn->hname,art);
					} break;
			}
			
			if (mn->operation & OP_NOT) {
				match=!match;
				
			}
			
			if (mn->operation & OP_POISON) {
				if (match) {
					*poison=TRUE;
					break;
				}
			}
			
			mn=mn->next;
			
			if (!match) {
				*poison=TRUE;
				break;
			} 
			match=FALSE;
		}
		
		if (match) break;
		
		while (*ng!=0) ng++; 
		ng++;
	}
	
	return (match && (!*poison));
}

BOOL
CheckMatch(struct article *art, MATCHHEAD *mp) 
{
	BOOL poison=FALSE;
	int r=FALSE;
	
	
	if (mp->poisonhead) {
		r=MatchArticle(art,mp->poisonhead,&poison);
	}
	
	if (!poison) 
		r=MatchArticle(art,mp->head,&poison);
//	printf(">>>>>>>>>>match=%u<<<<<<<<<<<<<<\n",r);
	return r;
}

struct distinclude *
FindInclude(char *name)
{
	struct distinclude *d;
	
	for (d=TAILQ_FIRST(&dIncHead);d;d=TAILQ_NEXT(d,list)) {
		if (!strcasecmp(d->name,name)) {
			return d;
		}
	}
	
	return NULL;
}

BOOL
AddInclude(char *name,char *diststr) {
	struct distinclude *d;
	
	d=malloc(sizeof(struct distinclude));
	
	bzero(d,sizeof(struct distinclude));
	
	d->name=strdup(name);
	
	if (diststr)
		d->dist=CompileMatchString(diststr);
	
	TAILQ_INSERT_TAIL(&dIncHead,d,list);	
	
	return 1;
}

BOOL
CheckIncludeMatch(struct article *art) 
{
	BOOL poison=FALSE;
	int r;
	struct distinclude *d;
	MATCHHEAD *mp;
	
	for (d=TAILQ_FIRST(&dIncHead);d;d=TAILQ_NEXT(d,list)) {
		d->poisoned=d->matched=poison=FALSE; 
		mp=d->dist;
		if (mp->poisonhead) {
			r=MatchArticle(art,mp->poisonhead,&poison);
			if (poison) {
//				printf("inclpoisoned\n");
				d->poisoned=TRUE;
				continue;
			}
		}
		
		r=MatchArticle(art,mp->head,&poison);
		if (r) {
			d->matched=TRUE;
		}
	}
	
/*	for (d=TAILQ_FIRST(&dIncHead);d;d=TAILQ_NEXT(d,list)) {
		printf("%s m=%u p=%u\n",d->name,d->matched,d->poisoned);
	}   */
}


MATCHNODE *
ParseNodeString(char *s)
{
	MATCHNODE *m;
	char *s1;
	BOOL intValue=FALSE;
	
	m=malloc(sizeof(MATCHNODE));
	ZeroMemory(m,sizeof(MATCHNODE));
	
	switch (*s) {
		case '!' : {
				m->operation|=OP_NOT;
				s++;
			} break;
		case '~' : {
				m->operation|=OP_POISON;
				s++;
			} break;
	}
	
	switch (*s) {
		case '|': {
				m->operation|=OP_LOGICALOR;
				s1=strtok(NULL,",");
				if (!s1) {
					free(m);
					return NULL;
				}
				m->left=ParseNodeString(s1);
				s1=strtok(NULL,",");
				if (!s1) {
					free(m);
					return NULL;
				}
				m->right=ParseNodeString(s1);
				s++;
				return m;
			} break;
		case '&' : {
				m->operation|=OP_LOGICALAND;
				s1=strtok(NULL,",");
				if (!s1) {
					free(m);
					return NULL;
				}	
				m->left=ParseNodeString(s1);
				s1=strtok(NULL,",");
				if (!s1) {
					free(m);
					return NULL;
				}
				m->right=ParseNodeString(s1);
				s++;
				return m;
			} break;
		case '+' : {
				s++;
				if (m->left=(MATCHNODE *) FindInclude(s)) {
					m->operation|=OP_REFDIST;
					return m;
				} else {
					printf("included distribution %s not found!\n",s);
					free(m);
					return NULL;
				}
			} break;		
	}
	
	if (*s=='$') {
		s++;
		switch (*s) {
			case 'I': {
					m->operation|=OP_HOPCOUNT;
					intValue=TRUE;
				} break;
			case 'G': {
					m->operation|=OP_GROUPCOUNT;
					intValue=TRUE;
				} break;
			case 'S': {
					m->operation|=OP_SIZE;
					intValue=TRUE;
				} break;
			case 'A': {
					m->operation|=OP_TIME;
					intValue=TRUE;
				} break;
			case 'P': {
					m->operation=OP_PATH|OP_HEADERMATCH;
				} break;
/*		  	case 'F': {
					m->operation|=OP_FOLLOWUPCOUNT;
					intValue=TRUE;
				} break; */ 
			case 'V': {
					s++;
					m->operation|=OP_ISAPPROVED;
					return m;
				  } break;
			case 'H': {
					char *h;
					s++;
					if (*s!='[') {
						free(m);
						return NULL;
					}
					s++;
					if (*s!='"') {
						free(m);
						return NULL;
					}
					s++;
					h=m->hname=malloc(strlen(s));
					while (*s!='"') {
						*h=*s; s++; h++;
					}
					*h=0;
					s++;
					if (*s!=']') {
						free(m);
						return NULL;
					}
					m->operation|=OP_HSPEC;
				} break;
			case 'C' : {
					s++;
					m->operation|=OP_ISCONTROL;
					return m;
				} break;
		}
		
		s++;
	} else {
		m->operation|=OP_HEADERMATCH|OP_GROUP;
		m->cpat=strdup(s);
		return m;
	}
	
	switch (*s) {
		case '>' : {
				m->operation|=OP_GREATER;
				intValue=TRUE;
			} break;
		case '<' : {
				m->operation|=OP_LESS;
				intValue=TRUE;
			} break;
		case '=' : {
				if (!(m->operation & OP_HEADERMATCH)) {
				}
			} break;
		default : {
				if (!(m->operation & OP_HEADERMATCH)) {
					free(m);
					return NULL;
				}
			} break;
	}
	
	s++;
	
	if (intValue) {
		m->count=atoi(s);
	} else {
		m->cpat=strdup(s);
	}
	
	
	return m;
	
}


MATCHHEAD *
CompileMatchString(char *feedinfo) 
{
	
	char *s1;
	MATCHHEAD *a=malloc(sizeof(MATCHHEAD));
	MATCHNODE *p,*n=NULL,*np=NULL;
	char *hold;
	
	ZeroMemory(a,sizeof(MATCHHEAD));
	
	hold=strdup(feedinfo);
	
	p=a->head=malloc(sizeof(MATCHNODE));
	
	ZeroMemory(p,sizeof(MATCHNODE));
	
	s1=strtok(hold,",\r\n\t");
	
	while (s1!=NULL) {
		p=ParseNodeString(s1);
//	printf(">>>>>>>>s1==%s\n",s1);
		assert(p);
		
		s1=strtok(NULL," ,\n");
		
		
		if ((p) && (p->operation & OP_POISON)) {
			if (!np) {
				np=p;
				a->poisonhead=p;
			} else {
				np->next=p;
				np=p;
			}
		} else if (p) {
			if (!n) {
				n=p;
				a->head=p;
			} else {
				n->next=p;
				n=p;
			}
		}
	}
	
	p->next=NULL;
	
	free(hold);
	
	return a;
	
}


BOOL
DistributionInit()
{
	TAILQ_INIT(&dIncHead);
	return TRUE;
}
