static char rcsid[] = "$Id: cm_parse.c,v 1.2 1991/12/17 08:11:22 putz Exp $";

#include <stdio.h>
#include <string.h>
#include <time.h>
#include <ctype.h>

/* Here is an example version 2 callog "add" entry:

(add "Thu Dec 19 09:00:00 1991"
	key: 17
	what: "repeater\n"
	details: ""
	duration: 10800
	period: biweekly
	ntimes: 26
	exceptions: (4 1 )
	mailto: "putz@localhost"
	author: "putz@localhost"
	attributes: (("ml","7200")("op","300")("fl","300")("bp","300")) )
	)

/* Here is an example version 3 callog "add" entry:

(add "Mon Dec 16 09:00:00 1991"
	key: 107
	what: "example \"calendar\"\nentry\n"
	details: ""
	duration: 3600
	period: biweekly
	ntimes: 26
	exceptions: (4 2 )
	author: "putz@grimm"
	attributes: (("ml","7200","putz@grimm")("op","300","")("fl","300","")("bp","300",""))
	tags: ((appointment , 1))
	apptstat: active
	privacy: public
	)


A time of 3:41am is a flag that no start time or duration is defined.

There are also "remove" entries like:

(remove "Thu Dec 19 09:00:00 19911" key: 17)

*/

#define TRUE		1
#define FALSE		0

#define MAX_TAG		100
#define MAX_KEYS	1000
#define MAX_CHARS	2000
#define MAX_EXCEPTIONS	1000
#define NO_START_TIME	-1
#define PER_NONE	0

#define OZ_NEWLINE	'`'
#define OZ_BACKQUOTE	'\''
#define OZ_BACKSLASH	'/'

typedef struct {
    int 	key;
    struct tm  	tm;
    char 	*datestr;
    char 	*what;
    char 	*details;
    int 	duration;
    int 	period;
    int 	ntimes;
    char 	*exceptions;
    char 	*mailto;
    char 	*author;
    char 	*attributes;
    char 	*tags;
    char 	*apptstat;
    char 	*privacy;
} cm_type;


static int verbose = FALSE;

#define SECS_PER_DAY	(60*60*24)

static char *period_names[] = { "single", "daily", "weekly",
	"biweekly", "monthly", "yearly", NULL};
static int period_days[] =	{ 0, 1, 7, 14, 0, 0};
static int period_months[] =	{ 0, 0, 0,  0, 1, 0};
static int period_years[] =	{ 0, 0, 0,  0, 0, 1};

static char charbuf[MAX_CHARS];
static short exceptbuf[MAX_EXCEPTIONS];

int
syntax_error(fmt, a, b, c, d, e, f, g, h, i, j)
    char *fmt, *a, *b, *c, *d, *e, *f, *g, *h, *i, *j;
{
    fprintf(stderr, fmt, a, b, c, d, e, f, g, h, i, j);
    putc('\n', stderr);
    return 0;
}

int
parse_string(fp, strp)
    FILE *fp;
    char **strp;
{
    char *s = charbuf;
    int ch, backslash = FALSE;

    do {
	ch = getc(fp);
    } while (isspace(ch));
    if (ch != '"') return syntax_error("parse_string: expected '\"' not '%c'");
    do {
	ch = getc(fp);
	if (ch == EOF) return syntax_error("parse_string: unexpected EOF");
	if (backslash) {
	    if (ch == 'n') ch = OZ_NEWLINE;
	    else if (ch == '\\') ch = OZ_BACKSLASH;
	    else if (ch == '`') ch = OZ_BACKQUOTE;
	    *s++ = ch;
	    backslash = FALSE;
	    ch = '.';		/* fool test */
	    continue;
	}
	switch (ch) {
	case '"':
	    break;
	case '\\':
	    backslash = TRUE;
	    break;
	default:
	    *s++ = ch;
	    break;
	}
    } while (ch != '"');
    if (s > charbuf && *(s-1) == OZ_NEWLINE) s--;
    *s = '\0';
    *strp = strdup(charbuf);
    return 1;
}

int
parse_list(fp, strp)
    FILE *fp;
    char **strp;
{
    char *s = charbuf;
    int ch, pcount = 0, backslash = FALSE;

    for (;;) {
	ch = getc(fp);
	if (ch == EOF) return syntax_error("parse_list: unexpected EOF");
	if (backslash) {
	    *s++ = ch;
	    backslash = FALSE;
	    continue;
	}
	*s++ = ch;
	if (ch == '\\') backslash = TRUE;
	else if (ch == '(') pcount++;
	else if (ch == ')') pcount--;
	if (pcount <= 0) break;
    }
    *s = '\0';
    *strp = strdup(charbuf);
    return 1;
}

int
parse_date(fp, ent)
    FILE *fp;
    cm_type *ent;
{
    if (!parse_string(fp, &ent->datestr)) return 0;

    /* e.g. "Tue Oct 29 13:00:00 1991" */
    strptime(ent->datestr, "%a %B %e %T %Y", &ent->tm);
    return 1;
}

int
parse_int(fp, intp)
    FILE *fp;
    int *intp;
{
    if (fscanf(fp, " %d", intp) < 1) return 0;
    else return 1;
}

int
parse_token(fp, strp)
    FILE *fp;
    char **strp;
{
    if (fscanf(fp, " %s", charbuf) < 1) return 0;
    *strp = strdup(charbuf);
}

int
parse_exceptions(fp, ent)
    FILE *fp;
    cm_type *ent;
{
    return parse_list(fp, &ent->exceptions);
}

int
parse_attributes(fp, ent)
    FILE *fp;
    cm_type *ent;
{
    return parse_list(fp, &ent->attributes);
}

int
parse_tags(fp, ent)
    FILE *fp;
    cm_type *ent;
{
    return parse_list(fp, &ent->tags);
}

int
parse_period(fp, intp)
    FILE *fp;
    int *intp;
{
    int i;

    if (fscanf(fp, " %s", charbuf) < 1) return 0;
    for (i = 0; period_names[i]; i++) {
	if (!strcasecmp(charbuf, period_names[i])) {
	    *intp = i;
	    return 1;
	}
    }
    syntax_error("unknown period: %s", charbuf);
    *intp = 0;
    return 1;
}



int
parse_add_field(fp, ent)
    FILE *fp;
    cm_type *ent;
{
    if (fscanf(fp, " %s ", charbuf) < 1) return 0;
    if (!strcmp(charbuf, ")")) return 0;
    if (!strcmp(charbuf, "key:")) return parse_int(fp, &ent->key);
    if (!strcmp(charbuf, "what:")) return parse_string(fp, &ent->what);
    if (!strcmp(charbuf, "details:")) return parse_string(fp, &ent->details);
    if (!strcmp(charbuf, "duration:")) return parse_int(fp, &ent->duration);
    if (!strcmp(charbuf, "period:")) return parse_period(fp, &ent->period);
    if (!strcmp(charbuf, "ntimes:")) return parse_int(fp, &ent->ntimes);
    if (!strcmp(charbuf, "exceptions:")) return parse_exceptions(fp, ent);
    if (!strcmp(charbuf, "mailto:")) return parse_string(fp, &ent->mailto);
    if (!strcmp(charbuf, "author:")) return parse_string(fp, &ent->author);
    if (!strcmp(charbuf, "attributes:")) return parse_attributes(fp, ent);
    if (!strcmp(charbuf, "tags:")) return parse_tags(fp, ent);
    if (!strcmp(charbuf, "apptstat:")) return parse_token(fp, &ent->apptstat);
    if (!strcmp(charbuf, "privacy:")) return parse_token(fp, &ent->privacy);
    syntax_error("unexpected field: %s", charbuf);
    return 0;
}

cm_type *
parse_add(fp, ent)
    FILE *fp;
    cm_type *ent;
{
    parse_date(fp, ent);
    while (parse_add_field(fp, ent)) { }
    if (ent->tm.tm_hour == 3 && ent->tm.tm_min == 41) 
	ent->duration = NO_START_TIME;
    return ent;
}

cm_type *
parse_remove(fp, ent)
    FILE *fp;
    cm_type *ent;
{
    parse_date(fp, ent);
    if (fscanf(fp, " key: %d)", &ent->key) < 1) return NULL;
    return ent;
}

void
print_ent(fp, ent)
    FILE *fp;
    cm_type *ent;
{
    fprintf(fp, "key: %d\n", ent->key);
    fprintf(fp, "date: %s", asctime(&ent->tm));
    if (ent->what) fprintf(fp, "what: %s\n", ent->what);
    if (ent->details) fprintf(fp, "details: %s\n", ent->details);
    if (ent->duration) fprintf(fp, "duration: %d\n", ent->duration);
    if (ent->period) fprintf(fp, "period: %s\n", period_names[ent->period]);
    if (ent->ntimes) fprintf(fp, "ntimes: %d\n", ent->ntimes);
    if (ent->exceptions) fprintf(fp, "exceptions: %s\n", ent->exceptions);
    if (ent->mailto) fprintf(fp, "mailto: %s\n", ent->mailto);
    if (ent->author) fprintf(fp, "author: %s\n", ent->author);
    if (ent->attributes) fprintf(fp, "attributes: %s\n", ent->attributes);
    if (ent->tags) fprintf(fp, "tags: %s\n", ent->tags);
    if (ent->apptstat) fprintf(fp, "apptstat: %s\n", ent->apptstat);
    if (ent->privacy) fprintf(fp, "privacy: %s\n", ent->privacy);
}

void
print_wizard_single(fp, ent)
    FILE *fp;
    cm_type *ent;
{
    int min, setalarm;
    time_t etime;

    etime = timelocal(&ent->tm);
    setalarm = ent->attributes != NULL && etime > time(NULL);

    fprintf(fp, "%c0%04d%02d%02d%",
	(setalarm ? '4' : '0'),
        ent->tm.tm_year + 1900,
	ent->tm.tm_mon + 1,
	ent->tm.tm_mday);
    if (ent->duration == NO_START_TIME) {
	fprintf(fp, "    ");
    } else {
	fprintf(fp, "%02d%02d", ent->tm.tm_hour, ent->tm.tm_min);
    }
    if (ent->duration == NO_START_TIME || ent->duration <= 60) {
	fprintf(fp, "    ");
    } else {
	min = ent->tm.tm_hour*60 + ent->tm.tm_min + ent->duration/60;
	fprintf(fp, "%02d%02d", min / 60, min % 60);
    }
    if (ent->duration == NO_START_TIME) {
	fprintf(fp, "    ");
    } else {
	/* set alarm 5 minutes ahead of time (should get from attributes) */
	min = ent->tm.tm_hour*60 + ent->tm.tm_min  - 5;
	fprintf(fp, "%02d%02d", min / 60, min % 60);
    }
    fprintf(fp, "\\%s\\\n", ent->what);
}


int
print_wizard_ent(fp, ent, mintime, maxtime)
    FILE *fp;
    cm_type *ent;
    time_t mintime, maxtime;
{
    cm_type pent;
    int cycle, pdays, cnt;
    time_t basetime, ptime;
    short *except;
    char *esp;

    basetime = timelocal(&ent->tm);

    if (ent->period == PER_NONE) {
	if (basetime < mintime || basetime > maxtime) return 0;
	print_wizard_single(fp, ent);
	return 1;
    }

    except = exceptbuf;
    if (ent->exceptions) {
	esp = &ent->exceptions[1];
	while (esp != NULL) {
	    while (isspace(*esp)) esp++;
	    cycle = strtol(esp, &esp, 10);
	    if (cycle <= 0) break;
	    *++except = cycle - 1;
	}
    }
    bcopy(ent, &pent, sizeof pent);
    
    cnt = 0;
    for (cycle = 0; cycle < ent->ntimes; cycle++) {
	ptime = timelocal(&pent.tm);
	bcopy(localtime(&ptime), &pent.tm, sizeof pent.tm);
	if (ptime > maxtime) break;
	if (except > exceptbuf && *except == cycle) {
	    except--;
	} else if (ptime >= mintime) {
	    print_wizard_single(fp, &pent);
	    cnt++;
	}
	pent.tm.tm_mday += period_days[ent->period];
	pent.tm.tm_mon  += period_months[ent->period];
	pent.tm.tm_year += period_years[ent->period];
    }
    return cnt;
}


main(argc, argv)
    int argc;
    char **argv;
{
    cm_type *entries[MAX_KEYS];
    int key, maxkey = 0;
    int inserted = 0, removed = 0, wizcount = 0;
    cm_type *ent;
    char c, etag[MAX_TAG];
    time_t mintime, maxtime;

    argv++;
    if (*argv != NULL && !strcasecmp(*argv, "-v")) verbose = TRUE;

    bzero(entries, sizeof entries);
    ent = (cm_type *) malloc(sizeof *ent);

    while (!feof(stdin)) {
	if (fscanf(stdin, " (%s", etag) < 1) {
	    fscanf(stdin, "%[^\n]", charbuf);
	    fprintf(stderr, "skipping '%s'\n", charbuf);
	    continue;
	}
	bzero(ent, sizeof *ent);
	if (!strcmp(etag, "add") && parse_add(stdin, ent)) {
	    entries[ent->key] = ent;
	    inserted++;
	    if (ent->key > maxkey) maxkey = ent->key;
	    if (verbose) print_ent(stderr, ent);
	    ent = (cm_type *) malloc(sizeof *ent);
	} else if (!strcmp(etag, "remove") && parse_remove(stdin, ent)) {
	    if (verbose) fprintf(stderr, "remove %d\n", ent->key);
	    entries[ent->key] = NULL;
	    removed++;
	} else if (!strcmp(etag, "access")) {
	    syntax_error("ignoring entry type: %s", etag);
	    fscanf(stdin, "%*[^\n]");
	} else {
	    syntax_error("unknown entry type: %s", etag);
	    fscanf(stdin, "%*[^\n]");
	}
    }

    mintime = time(0) - SECS_PER_DAY * 30;
    maxtime = time(0) + SECS_PER_DAY * 365;

    for (key = 0; key <= maxkey; key++) {
	if (entries[key] == NULL) continue;
	wizcount += print_wizard_ent(stdout, entries[key], mintime, maxtime);
    }
    fprintf(stderr, "%d/%d calendar entries, %d wizard entries\n",
	inserted-removed, inserted, wizcount);
    exit(0);
}
