#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <netdb.h>
#include <dirent.h>
#include <string.h>
#include <sys/stat.h>
#include <errno.h>
#include "both.h"

/* for use in do_dedupe and allocnode */
typedef struct LinkList {
	struct LinkList *next;
	char *msgnr;
} List, *PList;

/* function prototypes */
int get_message_index(int *, int);
int get_articles(long, int, int, int, int);
int get_announcements(int);
int do_dedupe(void);
int checkdir(char *);
int do_innbatch(int, char *);
int do_rnewsbatch(int, char *);
PList allocnode(FILE *, char *);


#define N_RESTART "suck.restart"
#define N_NEWSRC "sucknewsrc"
#define N_TMP "suck.tmp"
#define N_INDEX "suck.index"
#define N_SORTED "suck.sorted"

#define TRUE 0
#define FALSE 1

enum { RETVAL_ERROR = -1, RETVAL_OK = 0, RETVAL_NOARTICLES };
enum { B_FALSE, B_INNXMIT, B_RNEWS };	/* poss values for batch variable */

/*------------------------------------------------------------------*/
void main(int argc, char *argv[]) {
	
	FILE *fp;
	int sockfd;
	struct hostent *hi;

	char *host = NULL, *batchfile = NULL;
	long offset=0;
	int loop, batch = B_FALSE, nron = 0, articleCount=0, retval = 0, MultiFile = FALSE;

	/* allow no args, only the hostname, or hostname as first arg */
	host = getenv("NNTPSERVER");		/* the default */
	switch(argc) {
	  case 1:
		break;
	  case 2:   
		host = argv[1];
		break;
	  default:
		loop = 1;
		if(argv[1][0] != '-') {
			host = 	argv[loop++];
		}			
		for(;loop<argc;loop++) {
			if(argv[loop][0] == '-') {
				switch(argv[loop][1]) {
				  case 'h':
					host = (loop+1 == argc) ? getenv("NNTPSERVER") : argv[++loop];
					break;	
				  case 'm':
					MultiFile = TRUE;
					break;
				  case 'b':	/* batch file implies MultiFile Mode */
					switch (argv[loop][2]) {
					  case 'i':
						batch = B_INNXMIT;
						MultiFile = TRUE;		
						break;
					  case 'r':
						batch = B_RNEWS;
						MultiFile = TRUE;
						break;
					  default:
						fprintf(stderr, "Invalid Batch arg: %s\n", argv[loop]);
						retval = -1;
						break;
				  	}
					if(retval == 0 && loop+1 == argc) {
						fprintf(stderr, "No Batch file name provided\n");
						retval = -1;
					}
					else {
						batchfile = argv[++loop];
					}
					break;
				  default:
					fprintf(stderr, "Invalid argument: %s\n", argv[loop]);
					retval = -1;
					break;
				}			
			}
			else {
				fprintf(stderr, "Invalid argument: %s\n", argv[loop]);
				retval = -1;
			}	
		}
		break;
	}
	if(retval == 0) {
		sockfd = connect_to_nntphost( host, &hi);
		if(sockfd < 0 ) {
			retval = -1;
		}
		else if(get_announcements(sockfd) < 0) {
			retval = -1;
		}
		else if(get_message_index(&articleCount, sockfd) < 0) {
			retval = -1;
		}
		else {
			switch(articleCount) {
			  case 0:
 				fprintf(stderr,"No articles.\r\n");
				retval = RETVAL_NOARTICLES;
				break;
			  case -1:
				fp = fopen(full_path(N_PATH, N_RESTART),"r");
				fscanf(fp,"%ld\n%d\n%d\n",&offset,&nron, &articleCount);
				fclose(fp);
				fprintf(stderr,"Initiating restart at article %d\r\n",articleCount-nron);
  				retval = get_articles(offset, nron, articleCount, sockfd, MultiFile);
				break;
			  default:
				unlink(full_path(N_PATH, N_SORTED));
				fprintf(stderr,"Removing duplicates from list of %d articles.\r\n", articleCount);
				if((loop = do_dedupe()) >= 0) {
					articleCount -= loop;
					retval = get_articles(offset, nron, articleCount, sockfd, MultiFile);
				}
				break;
			}	
			sputline( sockfd, "quit\r\n" );
		}
		if(sockfd >= 0) {
			close(sockfd);
			fprintf(stderr,"Closed connection to %s\n",hi->h_name);
		}
		if(retval == RETVAL_OK) {
			switch(batch) {
			  case B_INNXMIT:
				fprintf(stderr, "Building Inn Batch File\n");
				do_innbatch(articleCount, batchfile);
				break;
			  case B_RNEWS:
				fprintf(stderr, "Building Rnews Batch File\n");
				do_rnewsbatch(articleCount, batchfile);
				break;
			  default:
				break;
			}
		}
	}	
	exit(retval);
}
/*--------------------------------------------------------------------*/
int get_message_index(int *articleCount, int sockfd) {

	int count,low,high,response,lastread,retval,i;
	char *sp,*inbuf,buf[MAXLINLEN],group[512];
	FILE *indexfp,*ifp,*tmpfp;

	retval = 0;
	indexfp = ifp = tmpfp = NULL;
	/* If suck.restart exists something went wrong */
	if((ifp = fopen(full_path(N_PATH, N_RESTART), "r" )) != NULL) {
		fclose(ifp);
		*articleCount = -1;
	}
	else if((ifp = fopen(full_path(N_NEWSRCDIR, N_NEWSRC), "r" )) == NULL) {
		perror(full_path(N_NEWSRCDIR, N_NEWSRC));
		retval = -1;
	}
	else if((tmpfp = fopen(full_path(N_PATH, N_TMP), "w" )) == NULL) {
		perror(full_path(N_PATH, N_TMP));
		retval = -1;
	}
	else if((indexfp = fopen(full_path(N_PATH, N_INDEX), "w" )) == NULL) {
		perror(full_path(N_PATH, N_INDEX));
		retval = -1;
	}

	while(retval == 0 && fscanf(ifp, "%s %d\n", group, &lastread) == 2) {
  		sprintf(buf,"group %s\r\n",group);
		sputline(sockfd,buf);
		i = sgetline(sockfd,&inbuf);
#ifdef DEBUG2
		do_debug("\ngroup %s\n%s",group,inbuf);
#endif
		if(i < 0) {
			retval = -1;
		}
		else if((sp = number(inbuf, &response)) == NULL) {
			fprintf(stderr,"Protocol error!\r\n");
			retval = -1;
		}
		else {
			if(response != 211) {
				fprintf(stderr,"GROUP <%s>, unexpected response %d\r\n", group, response );
   				continue; /* next newsgroup */
			}
			sp = number(sp, &count);
  			sp = number(sp, &low);
			sp = number(sp, &high);
  			fprintf(tmpfp, "%s %d\n", group, high);

			if (lastread >= 0 && lastread < high) {
   				if(lastread < low) {
					lastread = low - 1;
				}
   
   				sprintf(buf, "xhdr Message-ID %d-\r\n", lastread+1);
   				fprintf(stderr,"%s...articles %d-%d\r\n", group, lastread+1, high);
   				sputline(sockfd, buf);
				i = sgetline(sockfd, &inbuf);
				number(inbuf, &response);
				if(i < 0) {
					retval = -1;
				}
				else if (response == 221) {
					do {
     						if(sgetline(sockfd, &inbuf) < 0) {
							retval = -1;
						}
						else if (*inbuf != '.' ) {
      							for(sp = inbuf; *sp != '<'; sp++) {
								 /* find begin of index */
								if (sp== inbuf+(MAXLINLEN-4)) {
									continue; /* the NNTP server tried to pull your leg */
								}
							}
      							fprintf(indexfp, "%s", sp);
      							*articleCount += 1;
						}
					} while (retval == 0 && strcmp(inbuf, ".\n")!=0);
				} /* end if response */
   				else {
					fprintf(stderr,"Unexpected response %d getting IDs\r\n", response);
					retval = -1;
				}
			} /* end if lastread */
		} /* end else */
	} /* end while */

	if(indexfp != NULL) {
		fclose(indexfp);
	}
	if(tmpfp != NULL) {
		fclose(tmpfp);
	}
	if(ifp != NULL) {
		fclose(ifp);
	}
	return retval;
}
/*-----------------------------------------------------------*/
int get_articles(long offset, int nron, int articleCount, int sockfd, int MultiFile) {

	int response,len, retval, done, partial;
	char buf[MAXLINLEN], *inbuf;
	FILE *logfp,*ifp, *fptr;

	retval = 0;
	logfp = ifp = NULL;
	if((ifp = fopen(full_path(N_PATH, N_SORTED), "r" )) == NULL) {
		perror(full_path(N_PATH, N_SORTED));
		retval = -1;
	}
	else if(MultiFile == TRUE && checkdir(N_MSGDIR) == FALSE) {
		retval = -1;
	}
	else {
		fseek(ifp,offset,SEEK_SET); /* goto indexline */

	 	if((logfp = fopen(full_path(N_PATH, N_RESTART), "w" )) == NULL) {
			perror(full_path(N_PATH, N_RESTART));
			retval = -1;
		}
 		else {
			for(sprintf(buf,"article "); retval == 0 && fgets(&buf[8], 512, ifp) != NULL; sprintf(buf,"article ")) {
				rewind(logfp);
				fprintf(logfp,"%ld\n%d\n%d\n",offset,nron,articleCount);
  				fflush(logfp);
  				offset=ftell(ifp);
				nron++;
  				fprintf(stderr,"%5d\r", articleCount - nron);
  				len=strlen(buf);
  				buf[len++]='\r';
  				buf[len++]='\n';
  				buf[len]='\0';

#ifdef DEBUG2
				do_debug("\nSENDING REQUEST\n%s",buf);
#endif
 		 		sputline(sockfd,buf);
 		 		if(sgetline(sockfd,&inbuf) < 0) {
					retval = -1;
				}
				else {
 		 			number(inbuf, &response);
					if (response != 220) {
						fprintf(stderr,"*** Warning: Unexpected response\r\n*** ");
						fputs(buf,stderr);
		  			}
					else {
						if(MultiFile == TRUE) {
							/* open file */
							/* file name will be ###-#### ex 1-166 (nron,total) */
							sprintf(buf,"%d-%d", nron ,articleCount);
							if((fptr = fopen(full_path(N_MSGDIR, buf), "w")) == NULL) {
								perror(full_path(N_MSGDIR,buf));
								retval = -1;
							}
						}
						else {
							fptr = stdout;
						}
						if(retval == 0) {
							done = FALSE;
							partial = FALSE;
							/* partial = was the previous line a complete line or not */
							/* this is needed to avoid a scenario where the line is MAXLINLEN+1 */
							/* long and the last character is a ., which would make us think */
							/* that we are at the end of the message when we actually aren't */	
							do {
								len=sgetline(sockfd, &inbuf);
								if(partial == FALSE && len == 2 && strcmp(inbuf, ".\n") == 0) {
									done = TRUE;
									if(MultiFile == FALSE) {
										/* this is needed as a separator */
										/* in stdout version */
										fputs(inbuf, fptr);
									}
								}
								else {	
									fputs(inbuf, fptr);
									fflush(fptr);
									partial= (len==MAXLINLEN&&inbuf[len-1]!='\n') ? TRUE : FALSE;
								}
							} while (done == FALSE);
							if(len < 0) {
								retval = - 1;
							}
							if(fptr != stdout) {
								fclose(fptr);
							}	
						}
					}
				}
		 	} /* end for */
			fclose(logfp);
			unlink(full_path(N_PATH, N_RESTART));
		}
		fclose(ifp);
	}
	return retval;
}
/*------------------------------------------------------------*/
int get_announcements(int sockfd) {
	char *inbuf;
	int retval = 0;
		
	/* Get the announcement line */
	if(sgetline(sockfd, &inbuf) < 0) {
		retval = -1;
	}
	else {
		fprintf(stderr,"%s", inbuf );
		sputline(sockfd,"mode reader\r\n");
		if( sgetline(sockfd,&inbuf) < 0) {
			retval = -1;
		}
		else {
			/* Again the announcement */
			fprintf(stderr,"%s",inbuf);
		}
	}
	return retval;
}
/*--------------------------------------------------------------*/
/* read in each item fron index, and add it to link list and	*/
/* write out to sorted if it's not already there 		*/
/*--------------------------------------------------------------*/ 
int do_dedupe() {

	int retval, match, removed, kept;
	FILE *fpi, *fpo;
	char linein[MAXLINLEN+1];

	PList head, curr, prev; 

	head = curr = NULL;
	fpi = fpo = NULL;
	retval = removed = kept = 0;		
	
	if((fpi = fopen(full_path(N_PATH, N_INDEX), "r")) == NULL) {
		retval = -1;
		perror(full_path(N_PATH, N_INDEX));
	}
	else if((fpo = fopen(full_path(N_PATH, N_SORTED), "w")) == NULL) {
		retval = -1;
		perror(full_path(N_PATH, N_SORTED));
	}
	else {
		while(retval == 0 && fgets(linein, MAXLINLEN, fpi) != NULL) {
			/* now see if it's in the linked list */
			curr = head;
			if(curr == NULL) {
				/* special case, 1st node */
				if((head = allocnode(fpo, linein)) == NULL) {
					retval = -1;
				}
				else {
					kept++;
				}
			}
			else {	
				do {
					match = strcmp(linein, curr->msgnr);
					prev = curr;
					curr = curr->next;			
				}
				while(match != 0 && curr != NULL);
				if(match != 0) {
					/* reached end of list */
					if((prev->next = allocnode(fpo, linein)) == NULL) {
						retval = -1;
					}
					else {
						kept++;
					}
				}
				else {
					removed++;
				}
			}
		}
	}
	/* all done, free up memory etc */
	curr = head;
	while(curr != NULL) {
		prev = curr->next;
		free(curr->msgnr);
		free(curr);
		curr = prev;
	}	
	if(fpi != NULL) {
		fclose(fpi);
	}
	if(fpo != NULL) {
		fclose(fpo);
	}
	if(retval == 0) {
		fprintf(stderr, "Removed %d dupes, Downloading %d messages\n", removed, kept);
	}
	return (retval < 0) ? retval : removed;
}
/*-----------------------------------------------------------------------*/
PList allocnode(FILE *fpo, char *linein) {
	
	PList ptr;

	if((ptr = malloc(sizeof(List))) == NULL) {
		fprintf(stderr, "Out of Memory, aborting");
	}
	else if((ptr->msgnr = calloc(strlen(linein)+1, sizeof(char))) == NULL) {
		fprintf(stderr, "Out of Memory, aborting");
		free(ptr);
	}
	else {
		strcpy(ptr->msgnr, linein);
		ptr->next = NULL;
		if(fputs(linein, fpo) == EOF) {
			perror(full_path(N_PATH, N_SORTED));
			free(ptr->msgnr);
			free(ptr);
			ptr = NULL;
		}
	}
	return ptr;
}
/*------------------------------------------------------------------------*/
/* check if directory exists, if not, try to create it.			  */
/* return TRUE if made/exists and can write to it			  */
/* -----------------------------------------------------------------------*/
int checkdir(char *dirname) {
	
	struct stat buf;
	int retval = TRUE;

	if(stat(dirname, &buf)== 0) {
		/* it exists */
		if(!S_ISDIR(buf.st_mode)) {
			fprintf(stderr, "%s: Not a directory\n", dirname);
			retval = FALSE;
		}
		else if(access(dirname, W_OK) != 0) {
			fprintf(stderr, "%s: Write permission denied\n", dirname);
			retval = FALSE;
		}
	}
	else if(errno == ENOENT) {
		/* doesn't exist, make it */
		if(mkdir(dirname, S_IRUSR | S_IWUSR | S_IXUSR) != 0) {
			perror(dirname);
			retval = FALSE;
		}
	}
	else {
		/* huh? */
		perror(dirname);
		retval = FALSE;
	} 
	return retval;	
}
/*---------------------------------------------------------*/
int do_innbatch(int articleCount, char *batchfile) {

	/* build batch file that contains article listing */
	/* needed by innxmit */
	int i, retval = RETVAL_OK;
	FILE *fptr;
	
#ifdef DEBUG2
	do_debug("BATCH FILE: %s\n", batchfile);
#endif
	if((fptr = fopen(batchfile, "w")) == NULL) {
		perror(batchfile);
		retval = RETVAL_ERROR;
	}
	else {
		for(i=0;i<articleCount;i++) {
			/* full path puts the trailing slant on, so I don't have to here */
			fprintf(fptr, "%s%d-%d\n", full_path(N_MSGDIR,""), i+1,articleCount);
		}
		fclose(fptr);
	}
	return retval;
}
/*----------------------------------------------------------*/
int do_rnewsbatch(int articleCount, char *batchfile) {
	/* build rnews formated file of msgs */
	
	int i, retval = RETVAL_OK;
	FILE *fpout, *fpin;
	char buf[MAXLINLEN], *tmp;
	struct stat sbuf;

#ifdef DEBUG2
	do_debug("BATCH FILE: %s\n", batchfile);
#endif
	if((fpout = fopen(batchfile, "w")) == NULL) {
		perror(batchfile);
		retval = RETVAL_ERROR;
	}
	else {
		for(i=0;i<articleCount;i++) {
			sprintf(buf, "%d-%d", i+1,articleCount);
			tmp = full_path(N_MSGDIR, buf);
			if(stat(tmp, &sbuf) == -1) {
				perror(tmp);
				retval = -1;
			}
			else {
				/* first put #! rnews size */
				fprintf(fpout, "#! rnews %ld\n", sbuf.st_size);
				if((fpin = fopen(tmp, "r")) == NULL) {
					perror(tmp);
					retval = -1;
				}
				else {
					while(fgets(buf, MAXLINLEN, fpin) != NULL) {
						fputs(buf, fpout);
					}
					fclose(fpin);
					unlink(tmp);	/* we are done with it, nuke it */
				}
			}
			
		}
		fclose(fpout);
	}


	return retval;
}
