#include <stdlib.h>
#include <malloc.h>
#include <stdio.h>
#include <string.h>
#include <io.h>
#include <dir.h>
#include <dos.h>
#include <ctype.h>
#include <stdarg.h>
#include "tmalloc.h"
#include "list.h"
#include "iw.h"

struct list message_list = {0,0};
struct message {
    unsigned short number;
    unsigned short len;
    unsigned long pos;
    char *message;
    struct message *next;
} *cm=0;
int line_number=0;
int error_occurred;
int mls_number;
char mls_prefix[128];
unsigned long mpos;

#define IBUFFLEN 4096
char ibuff[IBUFFLEN];

int error_printf(char *fmt, ...)
{
    char buffer[512];
    register int i;
    va_list argptr;
    int cnt;
    
    fprintf(stderr, "ERROR: line %d: ", line_number);
    va_start(argptr, fmt);
    cnt = vsprintf(buffer, fmt, argptr);
    if (cnt != EOF) {
	for (i=0; i != cnt; i++) {
	    fputc(buffer[i], stderr);
	}
    }
    va_end(argptr);
    error_occurred = 1;
    return(cnt);
}

char *strncopy(char *src, int len)
{
    char *dst;
    
    dst = malloc(len+1);
    if (dst == 0) {
    	fprintf(stderr, "Out of memory\n");
	exit(-5);
    }
    strncpy(dst, src, len+1);
    dst[len] = 0;
    return(dst);
}

void strip1(struct message *m)
{
    char *cp;
    
    if (m->len) {
        cp = m->message + m->len - 1;
	while (isspace(*cp) && cp >= m->message) {
	    m->len--;
	    cp--;
	}
	*(++cp) = 0;
    }
}

int strip(struct message *m)
{
    while (m && m->next && m->next->next) m = m->next;
    if (m && m->next) {
    	strip1(m->next);
	if (m->next->len == 0) {
	    free(m->next->message);
	    free(m->next);
	    m->next = 0;
	    return(1);
	}
	return(0);
    }
    if (m) strip1(m);
    return(0);
}

int compile(char *fname)
{
    FILE *fp, *fpo;
    char drive[_MAX_DRIVE];
    char dir[_MAX_DIR];
    char file[_MAX_FNAME];
    char ext[_MAX_EXT];
    char new_fname[_MAX_PATH];
    char *cp, *cp1;
    enum { MLS_PREFIX, BEGIN, FIND_PREFIX, BODY, IGNORE, SAVE_MESSAGE, DONE } state;
    struct node *n, *n1;
    int printable_stuff, i, valid_prefix;
    unsigned long nmessages;
    struct message *cm1;
    struct iw_message_info mi;
    
    error_occurred = 0;
    _splitpath(fname,drive,dir,file,ext);
    if (strcmp(ext, ".MLS") == 0) {
    	fprintf(stderr, "Can't compile files with extension .MLS: %s\n", fname);
	return(-2);
    }
    fp = fopen(fname, "r");
    if (fp == 0) {
    	fprintf(stderr, "Can't open %s\n", fname);
	return(-1);
    }
    _makepath(new_fname,drive,dir,file,".MLS");
    fpo = fopen(new_fname, "wb");
    if (fpo == 0) {
	fclose(fp);
    	fprintf(stderr, "Can't create %s\n", new_fname);
	return(-1);
    }
    state = MLS_PREFIX;
    line_number = 0;
    cp = ibuff;
    ibuff[0] = 0;
    cm = 0; cm1 = 0;
    while (state != DONE) {
	if (*cp == 0) {
	    if (fgets(ibuff, IBUFFLEN, fp) == 0) {
	        state = SAVE_MESSAGE;
	    } else {
		line_number++;
		cp = ibuff;
		if (*cp == '#') {
		    *cp = 0;
		    continue;
		}
	    }
	}
	if (state == MLS_PREFIX) {
	    while (isspace(*cp)) cp++;
	    if (*cp == 0) continue;
	    if (strnicmp(cp, "mls_prefix", strlen("mls_prefix")) == 0) {
	    	cp += strlen("mls_prefix");
		while (isspace(*cp)) cp++;
		cp1 = mls_prefix;
		while (isprint(*cp) && !isspace(*cp)) *cp1++ = *cp++;
		*cp1 = 0;
		state = BEGIN;
	    } else {
	    	strcpy(mls_prefix, "IWMLS");
		state = BEGIN;
	    }
	}
	if (state == BEGIN) {
	    while (isspace(*cp)) cp++;
	    if (*cp == 0) continue;
	    state = FIND_PREFIX;
	}
	while (state == FIND_PREFIX && *cp) {
	    while (isspace(*cp)) cp++;
	    if (*cp == 0) continue;
	    if (strnicmp(cp, mls_prefix, strlen(mls_prefix)) == 0) {
	        cp += strlen(mls_prefix);
		while (isspace(*cp)) cp++;
		mls_number = atoi(cp);
		if (mls_number == 0) {
		    if (isdigit(*cp)) {
			valid_prefix = 1;
			while (isdigit(*cp)) {
			    if (*cp != '0') valid_prefix = 0;
			    cp++;
			}
		    } else {
			valid_prefix = 0;
		    }
		} else {
		    while (isdigit(*cp)) cp++;
		    valid_prefix = 1;
		}
		if (valid_prefix == 0) {
		    error_printf("Invalid prefix\n");
		    while (!isspace(*cp)) cp++;
		    state = IGNORE;
		} else {
		    for (n=message_list.head; n != 0; n=n->next) {
			cm1 = n->data;
			if (cm1->number == mls_number) {
			    error_printf("Duplicate prefix\n");
			    break;
			}
		    }
		    if (n == 0) {
			cm = malloc(sizeof(struct message));
			if (cm == 0) {
			    fprintf(stderr, "Out of memory\n");
			    exit(-3);
			}
			cm1 = cm;
			cm->number = mls_number;
			cm->len = 0;
			cm->next = 0;
			cm->message = 0;
			state = BODY;
			while (isspace(*cp)) cp++;
			if (*cp == 0) continue;
		    } else {
			state = IGNORE;
		    }
		}
	    } else {
		while (!isspace(*cp)) cp++;
	    	error_printf("Expecting prefix: %s\n", mls_prefix);
		state = IGNORE;
	    }
	}
	if (state == BODY) {
	    if (*cp == 0) continue;
	    cp1 = cp;
	    printable_stuff = 0;
	    while (*cp1) {
		while (isspace(*cp1)) cp1++;
		if (strnicmp(cp1, mls_prefix, strlen(mls_prefix)) == 0) {
		    state = SAVE_MESSAGE;
		    break;
		} else if (*cp1) {
		    printable_stuff = 1;
		    while (!isspace(*cp1)) cp1++;
		}
	    }
	    if (printable_stuff || state == BODY) {
		cm1->message = strncopy(cp, cp1-cp);
		cm1->len = cp1-cp;
		cm1->next = malloc(sizeof(struct message));
		if (cm1->next == 0) {
		    fprintf(stderr, "Out of memory\n");
		    exit(-3);
		}
		cm1 = cm1->next;
		cm1->number = mls_number;
		cm1->len = 0;
		cm1->next = 0;
		cm1->message = 0;
	    }
	    cp = cp1;
	}
	if (state == SAVE_MESSAGE) {
	    if (cm) {
	        /* remove trailing blank lines and cairrage returns */
		while (strip(cm)) ;
		// blah - need to do insertion sort
		AddNode(&message_list, CreateNode(cm), NULL, APPEND);
		cm = 0;
		cm1 = 0;
	    }
	    if (feof(fp)) {
	        state = DONE;
	    } else {
		state = FIND_PREFIX;
	    }
	}
	while (state == IGNORE && *cp) {
	    while (isspace(*cp)) cp++;
	    if (*cp == 0) continue;
	    if (strnicmp(cp, mls_prefix, strlen(mls_prefix)) == 0) {
		state = FIND_PREFIX;
	    } else {
		while (!isspace(*cp)) cp++;
	    }
	}
    }
    if (error_occurred) return(-1);
    for (nmessages=0, n=message_list.head; n != 0; n=n->next, nmessages++) ;
    if (nmessages == 1) {
	printf("There was one message in %s\n", fname);
    } else {
	printf("There were %ld messages in %s\n", nmessages, fname);
    }
    if (fwrite(&nmessages, sizeof(long), 1, fpo) != 1) {
	bad_output:
    	fprintf(stderr, "Error writing output file %s\n", new_fname);
	fclose(fp);
	fclose(fpo);
	return(-8);
    }
    mpos = sizeof(long) + (nmessages * sizeof(struct iw_message_info));
    for (n=message_list.head; n != 0; n=n->next) {
	cm = n->data;
    	mi.message_id = cm->number;
	mi.size = 0;
	cm1 = cm;
	while (cm1) {
	    mi.size += cm1->len;
	    cm1 = cm1->next;
	}
	mi.message_offset = mpos;
	mpos += mi.size;
    	if (fwrite(&mi, sizeof(mi), 1, fpo) != 1) goto bad_output;
    }
    for (n=message_list.head; n != 0; n=n->next) {
	cm = n->data;
	while (cm) {
	    if (cm->len) {
		if (fwrite(cm->message, cm->len, 1, fpo) != 1) goto bad_output;
	    }
	    cm = cm->next;
	}
    }
    // clean up
    fclose(fp);
    fclose(fpo);
    for (n=message_list.head; n != 0; n=n1) {
    	n1 = n->next;
	cm = n->data;
	while (cm) {
	    cm1 = cm->next;
	    if (cm->message) free(cm->message);
	    free(cm);
	    cm = cm1;
	}
	free(n);
    }
    InitList(&message_list);
    return(0);
}

void multiple_compile(char *files)
{
    struct find_t fileinfo;
    char drive[_MAX_DRIVE];
    char dir[_MAX_DIR];
    char file[_MAX_FNAME];
    char ext[_MAX_EXT];
    char path[_MAX_PATH];
    char fpath[_MAX_PATH];
    int ret;

    _splitpath(files,drive,dir,file,ext);
    _makepath(path,drive,dir,NULL,NULL);
    ret = _dos_findfirst(files, _A_NORMAL, &fileinfo);
    if (ret != 0) {
	fprintf(stderr, "Can't find file %s\n", files);
    }
    while (ret == 0) {
	strcpy(fpath, path);
	strcat(fpath, fileinfo.name);
	compile(fpath);
	ret = _dos_findnext(&fileinfo);
    }
}

void help(void)
{
    fprintf(stderr, "USAGE: mlscomp [-(?|H)] filename or wildcard\n");
    exit(-1);
}

int main(int argc, char *argv[])
{
    int ac;

    if (argc <= 1) {
    	help();
    }
    for (ac=1; ac < argc; ac++) {
	if (argv[ac][0] == '-' || argv[ac][0] == '/' || argv[ac][0] == '+') {
	    strupr(argv[ac]);
	    switch (argv[ac][1]) {
		case '?':
		case 'H':
		    help();
		    break;
		default:
		    fprintf(stderr, "unknown option %s\n", argv[ac]);
		    help();
		    break;
	    }
	} else {
	    multiple_compile(argv[ac]);
	}
    }
    return(0);
}
