#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>
#include <dirent.h>

#include "globals.h"
#include "mh.h"


void mh_handle( struct mbox_struct * mb ) {        
	/* Since we never really intend to quit this function,
	 * all "global" stuff may go on the stack here */
	int mod_time = 0;
	int ret      = 0;
	
	if ( ! strlen(mb->file) ) {
	        fprintf(stderr, "asmail: mh_handle: no mailbox directory specified.\n");
		mb->status = STAT_FAIL;
		signal_update();
		pthread_exit(NULL);
	}
	if ( strlen(mb->file) > (MAX_INPUT_LENGTH-4)) {
		fprintf(stderr, "asmail: mh_handle: mailbox directory name is too long.\n");
		mb->status = STAT_FAIL;
		signal_update();
		pthread_exit(NULL);
	}

	while (1) {
		mb->status |= STAT_RUN;
		signal_update();

		if ( (ret = count_mh( mb, &mod_time )) < 0 ) {
			mb->status = STAT_FAIL;
			signal_update();
		} else {
		        if ( mb->cnew > 0 ) {
 				mb->mail = MAIL_NEW;
			}
			else if ( mb->ctotal ) {
				mb->mail = MAIL_OLD;
			}
			else {
				mb->mail = MAIL_NONE;
			}
		}

		if ( mb->status == STAT_RUN ) 
		        mb->status = STAT_IDLE;
	
		signal_update();
		sleep_check(mb->update);
	}
}


int count_mh( struct mbox_struct * mb, int *mod_time ) {

        if (mb->flags & FLAG_USE_MH_SEQ) {
	        return count_mh_sequences(mb, mod_time);
	} else {
	        return count_mh_files(mb);
	}
}


int count_mh_files( struct mbox_struct * mb ) {
        DIR           *dir        = NULL;
	FILE          *f          = NULL;
	char           line[MAX_INPUT_LENGTH+1]  = "";
	char           fname[MAX_INPUT_LENGTH+1] = "";
	int            ctotal     = 0;
	int            cnew       = 0;
	struct dirent *dir_entry  = NULL;

	/* Open the mH directory */
	if ((dir = opendir(mb->file)) == NULL) 
	        return(-1);

	/* Parse through each file in the directory */
	while ((dir_entry = readdir(dir)) != NULL) {
	        /* Ignore files that are not mH mail files */
	        if (dir_entry->d_name[0] != '.' && dir_entry->d_name[0] != ',') {
		        strcpy(fname, mb->file);
			/* Make sure the directory name ends in a slash. */
			if (fname[strlen(fname) - 1] != '/')
			        strncat(fname, "/",
					MAX_INPUT_LENGTH - strlen(fname));
		        strncat(fname, dir_entry->d_name,
				MAX_INPUT_LENGTH - strlen(fname));
		        fname[MAX_INPUT_LENGTH] = '\0';

			if ( (f = fopen(fname, "r")) == NULL ) {
			        return(-1);
			}
			
			++ctotal;
			++cnew;

			while ( fgets(line, MAX_INPUT_LENGTH, f) ) {
			        switch (line[0]) {		   
				case 'S':
				        if ( !strncmp(line, "Status: ", 8) ) {
					      if (mb->flags & FLAG_UNREAD_AS_NEW) {
						      if (!strncmp(line, "Status: RO", 10))
							      --cnew;
					      } else if (!strncmp(line, "Status: R", 9)) {
						      --cnew; 
					      }
					      /* Once we find the Status header, no need
						 to keep reading from this file. */
					      goto close;
					}
					break;
				}			
			}
		close:
			fclose(f);
		}
	}

	closedir(dir);
	
	if ( (mb->cnew != cnew) && (cnew != 0) ) {
	        pthread_mutex_lock(&mb->mutex);
		mb->flags |= FLAG_ARRIVED;
		pthread_mutex_unlock(&mb->mutex);
	}

	mb->ctotal = ctotal;
	mb->cnew   = cnew;
	
	return(cnew);
}


int count_mh_sequences( struct mbox_struct * mb, int *mod_time ) {
        struct stat    stat_buf;
        DIR           *dir        = NULL;
	FILE          *f          = NULL;
	int            ctotal     = 0;
	int            cnew       = 0;
	int            lsize      = 0;
	int            i          = 0;
	struct dirent *dir_entry  = NULL;
	char          *line       = NULL;
	char         **tokens     = NULL;
	char           fname[MAX_INPUT_LENGTH+1] = "";

	/* Form the full path to the .mh_sequences file.
	   Make sure the directory name ends in a slash. */
	strcpy(fname, mb->file);
	if (fname[strlen(fname) - 1] != '/')
	        strncat(fname, "/", MAX_INPUT_LENGTH - strlen(fname));
	strncat(fname, ".mh_sequences", MAX_INPUT_LENGTH - strlen(fname));
	fname[MAX_INPUT_LENGTH] = '\0';

	/* Stat the .mh_sequences file to see if anything has changed. */
	if ( stat(mb->file, &stat_buf) ) {
  	        /* Yes, we have to keep trying. The mailbox
		 * may be on a filesystem that experiences
		 * problems, like NFS. */
	        fprintf(stderr, "asmail: mh_handle: stat (%s) failed.\n", mb->file);		
		return(-1);
	} else {
	        if ( stat_buf.st_ctime == *mod_time )
			return(0);
		else 
		        *mod_time = stat_buf.st_ctime;		
	}

	/* Open the mH directory */
	if ((dir = opendir(mb->file)) == NULL)
	        return(-1);

	/* Count the total number of messages in the directory. */
	while ((dir_entry = readdir(dir)) != NULL) {
	        if (dir_entry->d_name[0] != '.' && dir_entry->d_name[0] != ',')
		        ++ctotal;
	}
	
	closedir(dir);

	/* Now parse the .mh_sequences file to count the number of new messages.
	 * Although this is a user-configurable option in ~/.mh_profile, we will assume that the
	 * new messages are contained in the "unseen" sequence (balsa also makes this assumption).
	 *
	 * We look for a line beginning with "unseen: " which is followed by 
	 * a space separated list of message numbers, or ranges of message numbers.
	 * It looks similar to this:
	 * 
	 * unseen: 1 3 6-10 16-24 30
	 */

	if ( (f = fopen(fname, "r")) == NULL ) {
	        fprintf(stderr, "asmail: count_mh_sequences: unable to open sequences file: %s\n", fname);
		return(-1);
	}

	lsize = 256;
	line  = (char *) malloc(sizeof(char) * lsize);

	while(!feof(f)) {
	        readLine(f, &line, &lsize);
		
		if (strncasecmp(line, "unseen:", 7) == 0) {
		        tokenizeString(line, lsize, &tokens);

			for (i = 1; tokens[i] != '\0'; i++) {
			        cnew += parse_sequence(tokens[i]);
			}

			break;
		}
	}

	fclose(f);
	free(line);

	/* Free the tokens. */
	if (tokens != NULL) {
	        for (i = 0; tokens[i] != '\0'; i++) {
	                free(tokens[i]);
		}
		free(tokens);
	}

	if ( (mb->cnew != cnew) && (cnew != 0) ) {
	        pthread_mutex_lock(&mb->mutex);
		mb->flags |= FLAG_ARRIVED;
		pthread_mutex_unlock(&mb->mutex);
	}

	mb->ctotal = ctotal;
	mb->cnew   = cnew;

        return(cnew);
}


/* Read a line in from the file stream and check to make  *
 * sure that the entire string was read. The string is    *
 * terminated by "\n" (LF). If the character array is     *
 * too short, make it bigger.                             */
int readLine(FILE *stream, char **line, int *size) {
        off_t pos;
	char  buf[256];
	int   blen, llen;

	pos = ftell(stream);
	memset(*line, 0, *size);   
	llen = 0;

	/* Make sure we read the entire line. */
	do {
	        if (fgets(buf, sizeof(buf), stream) == NULL)
		        break;
		blen = strlen(buf);
		if (blen + llen <= (*size) - 1) {
 		        strcat(*line, buf);
			llen += blen;
		}
		else {
		        *size *= 2 ;
			llen  += blen;
			*line  = (char *) realloc(*line, *size);
			strcat(*line, buf);
		}
	} while (buf[blen - 1] != '\n'&& !feof(stream));
    
	return 0;
}


/* Return a NULL-terminated array of strings. */
int tokenizeString(char *string, int size, char ***output) {
        char  *start, *end;
	char  *p, *q;
	int   i;

	end = string + size;
    
	/* Remove spaces from end of string */
	for (p = string; p < end && *p != '\0'; p++);
	for (p--; isspace((int)*p); p--);
	end = p + 1;

	/* Remove spaces from beginning of string */
	for (p = string; p < end && isspace((int)*p); p++);
	start = p;

	/* If start == end, then we have an empty string. */
	if (start == end) {
	        *output = (char **) malloc(sizeof(char *) * 1);
		(*output)[0] = '\0';
		return 0;
	}

	/* Count number of tokens */
	for (p = end-1, i = 0; p > start; p--) {	                                                       
	        if (isspace((int)*p) && !isspace((int)*(p-1))) {
		        i++;                        
		}
	}
    
	/* Number of tokens equals spaces + 1 */
	i++;
    
	*output = (char **) malloc(sizeof(char *) * (i + 1));
    
	(*output)[i] = '\0';

	/* Copy tokens to the char array */
	for (i = 0, p = q = start; p < end && q < end; p = q, i++) {
	        for (q = p; !isspace((int)*q) && q < end; q++);
        
		/* Copy the token to the string array */
		(*output)[i] = (char *) malloc(sizeof(char) * (q - p + 1));
		strncpy((*output)[i], p, q-p);
		(*output)[i][q-p] = '\0';
        
		/* Forward past any extra spaces between tokens */
		for (q++; isspace((int)*q) && q < end; q++);
	}
    
	return(0);
}


/* Parse a sequence, either a single number, or a range of numbers. */
int parse_sequence(char *sequence) {
        char *p;
	int   a, b;

	for (p = sequence; *p != '\0'; p++) {
	        /* Found a range. */
	        if (*p == '-') {
		        *p = '\0';
			p++;
			a = atoi(sequence);
			b = atoi(p);

			return (b-a+1);
		}
	}

	return(1);
}
