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

struct mail_t {
	long start;
	long finish;
	char *msgid;
	struct mail_t *next;
};

struct mail_t *mailbase = NULL;

int get_line(FILE *file, char *buffer, size_t size) {
	size_t i = 0;
	int ret;
	while(i <= size) {
		if((ret = fgetc(file)) != EOF) {
			if(ret == '\n') {
				buffer[i] = '\0';
				return i;
			} else {
				buffer[i] = ret;
				i++;
			}
		} else {
		       	buffer[i] = '\0';
			return EOF;
		}
	}
	return -1;
}

long find_string_line(FILE *file, const char *string, int casesense) {
	char buffer[4096];
	int len = strlen(string);
	int ret;
	int match = 0;
	
	while(!match) {
		ret = get_line(file,buffer,4096);
		if(ret == EOF)
			return EOF;
		if(ret != -1 && ret >= len) {
			if(casesense) {
				if(strncmp(buffer,string,len) == 0)
					match = 1;
			} else {
				if(strncasecmp(buffer,string,len) == 0)
					match = 1;
			}
		}
		//fprintf(stderr,"No Match \"%s\"\n",buffer);
	}
	return ftell(file) - (ret + 1);
}

char *get_msgid(const char *line) {
	char *msgid;
	char *start;
	int i = 0;
	//fprintf(stderr,"get_msgid: line:\"%s\"\n",line);
	
	while(line[i] != '\0' && line[i] != ' ')
		i++;
	if(line[i] == '\0')
		return NULL;
	
	start = line + i + 1;
	msgid = malloc(strlen(line) - i + 1);
	strcpy(msgid,start);
	
	//fprintf(stderr,"get_msgid: msgid:\"%s\"\n",msgid);
	return msgid;
}

long find_mail_start(FILE *spool) {
	char buffer[4096];
	int begin;
	int found = 0;
	long off;
	if(ftell(spool) < 2) 
		begin = 1;
	else
		begin = 0;
	
	while(!found) {
		off = find_string_line(spool,"From ",1);
		if(off == EOF) {
			return EOF;
		}
		fprintf(stderr,"Found \"%s\" line at %d\n","From ",off);
		if(!begin) {
			fseek(spool,off - 2,SEEK_SET);
			fread(buffer,2,1,spool);
			buffer[2] = '\0';
			if(strncmp(buffer,"\n\n",4096) == 0)
				found = 1;
			else
				fseek(spool,off + 1, SEEK_SET);
		} else {
			found = 1;
		}
	}
	return off;
}

struct mail_t *get_mail(FILE *spool) {
	char buffer[4096];
	struct mail_t *mail = malloc(sizeof(struct mail_t));
	long off;
	long next;
	int begin;
	int ret;
	int found = 0;
	
	mail->msgid = NULL;
	mail->next = NULL;
	mail->finish = 0;
	
	mail->start = find_mail_start(spool);
	if(mail->start == EOF) {
		free(mail);
		return NULL;
	}
	fseek(spool,mail->start + 1, SEEK_SET);
	next = find_mail_start(spool);
	fseek(spool,mail->start,SEEK_SET);
	
	off = find_string_line(spool,"Message-ID:",0);
	if(next != EOF && off > next) {
		sleep(5);
		mail->msgid = strdup("Unknown");
		fseek(spool,next,SEEK_SET);
	} else {
		fseek(spool,off,SEEK_SET);
		ret = get_line(spool,buffer,4096);
		if(ret == EOF || ret == -1) {
			free(mail);
			return NULL;
		}
		fprintf(stderr,"Found \"%s\" line at %d\n","Message-ID:",off);
		mail->msgid = get_msgid(buffer);
	}
	return mail;
}

char *filter_name(const char *name) {
	static char *clean = NULL;
	int i,j,banned;
	if(clean != NULL)
		free(clean);
	banned = 0;
	for(i = 0; name[i] != '\0' && i < 248; ++i) {
		if(name[i] == '/')
			banned++;
	}
	clean = malloc(i + banned*4 + 1);
	for(i = 0, j = 0; name[i] != '\0' && i < (248 - banned*3); ++i) {
		if(name[i] != '/') {
			clean[j] = name[i];
			j += 1;
		} else {
			j += sprintf(clean + j,"%%%03o",name[i]);
		}
	}
	clean[j] = '\0';
	return clean;
}

void split_mail(FILE *spool, char *dirname, struct mail_t *mail) {
	struct stat tmp;
	FILE *out;
	char path[4096];
	char *path2 = NULL;
	char *buffer;
	int i = 0;
	long len = mail->finish - mail->start;

	if(mail->msgid == NULL)
		return;
	
	snprintf(path,4096,"%s/%s",dirname,filter_name(mail->msgid));
	while(stat(path,&tmp) == 0) {
		if(path2 == NULL)
			path2 = strdup(path);
		snprintf(path,4096,"%s.%05d",path2,i);
		i++;
	}
	if(path2 != NULL)
		free(path2);
	fprintf(stderr,"Splitting %s to %s\n",mail->msgid,path);
	
	buffer = malloc(len);

	if(buffer == NULL) {
		fprintf(stderr,"Fail to get %d bytes of memory\n",len);
		exit(1);
	}
	
	fseek(spool,mail->start,SEEK_SET);
	fread(buffer,len,1,spool);
	
	out = fopen(path,"w");
	if(out == NULL) {
		fprintf(stderr,"failed to open %s for write\n",path);
		exit(1);
	}
	fwrite(buffer,len,1,out);
	fclose(out);

	free(buffer);
}

int main(int argc, char *argv[]) {
	struct mail_t *ptr;
	struct mail_t *lptr;
	FILE *spool;
	int mails = 0;
	if(argc < 3)
		exit(1);

	spool = fopen(argv[1],"r");
	if(spool == NULL)
		exit(1);

	mailbase = get_mail(spool);
	lptr = mailbase;
	while((ptr = get_mail(spool)) != NULL && !feof(spool)) {
		lptr->next = ptr;
		lptr->finish = ptr->start;
		fprintf(stderr,"Found %s, at %d->%d\n",lptr->msgid,lptr->start,lptr->finish);
		lptr = ptr;
	}
	lptr->finish = ftell(spool);
	for(ptr = mailbase; ptr != NULL; ptr = ptr->next) {
		split_mail(spool,argv[2],ptr);
		mails++;
	}
	fclose(spool);
	fprintf(stderr,"mails:%d\n",mails);
	return 0;
}
