/* $Id: malias.c,v 1.4 2015/09/29 13:17:42 onoe Exp $ */

/*-
 * Copyright (c) 2003 Atsushi Onoe
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. The name of the author may not be used to endorse or promote products
 *    derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
#ifdef MALIAS

#include <sys/types.h>

#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "cue.h"

struct malias_list {
	char *str;
	struct malias_list *next;
};

struct malias_keys {
	char *str;
	struct malias_keys *next;	/* next key */
	struct malias_list *data;	/* sentinel for data against the key. */
};

extern struct state statebuf;

static void
malias_free_list(struct malias_list *m)
{
	struct malias_list *n;

	while (m != NULL) {
		n = m->next;
		free(m->str);
		free(m);
		m = n;
	}
}

static void
malias_free_keys(struct malias_keys *m)
{
	struct malias_keys *n;

	while (m != NULL) {
		n = m->next;
		free(m->str);
		malias_free_list(m->data);
		free(m);
		m = n;
	}
}

/*
 * parse the right side value in the alias file.
 * '*head' can be NULL.
 */
static int
malias_disassemble_data(char *str, struct malias_list **head)
{
	struct malias_list **ml = head;
	char *data, *p;
	int finish = 0;

	*head = NULL;

	/* get values */
	for (p = str; ; p++) {
		/* skip unnecessary char */
		while (*p == ' ' || *p == '\t')
			p++;
		/* parse the value */
		data = p;
		while (!(*p == ',' || *p == '\n' || *p == '\0'))
			p++;
		if (*p == '\n' || *p == '\0')
			finish++;
		*p = '\0';
		if (strlen(data) != 0) {
			if (((*ml) = malloc(sizeof(**ml))) == NULL) {
				malias_free_list(*head);
				return -1;
			}
			(*ml)->str = strdup(data);
			(*ml)->next = NULL;
			ml = &(*ml)->next;
		}
		if (finish)
			break;
	}

	return 0;
}

/*
 * read the alias file and parse the left side value in it.
 */
static struct malias_keys *
malias_read(void)
{
	char tname[MAXPATH];
	char buf[BUFSIZ], *key, *p;
	struct malias_keys *base = NULL, **ak = &base;
	FILE *fp;

	/* get the alias mapping */
	if (statebuf.config.addrbook == NULL)
		return NULL;
	if (statebuf.config.addrbook[0] == '/' || (p = getenv("HOME")) == NULL)
		strlcpy(tname, statebuf.config.addrbook, sizeof(tname));
	else
		snprintf(tname, sizeof(tname), "%s/%s", p,
		    statebuf.config.addrbook);
	if ((fp = fopen(tname, "r")) == NULL)
		return NULL;
	while ((key = fgets(buf, sizeof(buf), fp)) != NULL) {
		p = key;
		/* comment or blank line */
		if (*key == '#' || *key == '\n') {
			continue;
		}
		/* skip unnecessary char */
		while (*p == ' ' || *p == '\t')
			p++;
		/* get a key */
		/* it is also valid when the line has only key */
		while (!(*p == ':' || *p == '\n' || *p == '\0'))
			p++;
		*p++ = '\0';
		/* if the key size is zero, it is not suitable */
		if (strlen(key) == 0) {
			continue;
		}
		if (((*ak) = malloc(sizeof(**ak))) == NULL) {
			malias_free_keys(base);
			fclose(fp);
			return NULL;
		}
		(*ak)->str = strdup(key);
		(*ak)->data = NULL;
		(*ak)->next = NULL;
		if (malias_disassemble_data(p, &(*ak)->data)) {
			malias_free_keys(base);
			fclose(fp);
			return NULL;
		}
		ak = &(*ak)->next;
	}
	fclose(fp);

	return base;
}

/* remove duplicate key.  very slow */
static struct malias_list *
malias_completion_list(struct malias_keys *base)
{
	struct malias_keys *a;
	struct malias_list *list = NULL, **ml = &list, *mx, *b;

	for (a = base; a != NULL; a = a->next) {
		for (mx = list; mx != NULL; mx = mx->next) {
			if (strcmp(a->str, mx->str) == 0)
				goto next;	/* duplicated */
		}
		if (((*ml) = malloc(sizeof(**ml))) == NULL) {
			malias_free_list(list);
			return NULL;
		}
		(*ml)->str = strdup(a->str);
		(*ml)->next = NULL;
		ml = &(*ml)->next;

		/* check each data of the key */
		for (b = a->data; b != NULL; b = b->next) {
			for (mx = list; mx != NULL; mx = mx->next) {
				if (strcmp(b->str, mx->str) == 0)
					goto next;	/* duplicated */
			}
			if (((*ml) = malloc(sizeof(**ml))) == NULL) {
				malias_free_list(list);
				return NULL;
			}
			(*ml)->str = strdup(b->str);
			(*ml)->next = NULL;
			ml = &(*ml)->next;
		}
	    next: ;
	}

	return list;
}

/*
 * it uses all keywords in the alias file to complete the candidate.
 */
int
malias_completion(char *bufline, void (*callback)(void *arg, const char *entry), void *arg)
{
	char *candidate;
	int nmatch = 0;
	int matchlen, len, nlen;
	int i, l;
	char *fname, *ep;
	char dname[MAXPATH], mname[MAXPATH];
	char *p;
	struct malias_keys *base;
	struct malias_list *list, *ma;

	/* pick the last string */
	candidate = bufline;
	len = strlen(bufline);
	for (p = bufline + len; p >= bufline; p--) {
		if (*p == ',') {
			for (p++; *p == ' ' || *p == '\t'; p++)
				;
			candidate = p;
			break;
		}
	}

	len = strlen(candidate);
	if ((base = malias_read()) == NULL)
		return 0;	/* error */
	list = malias_completion_list(base);
	malias_free_keys(base);
	if (list ==NULL)
		return 0;

	fname = dname;
	ep = dname + sizeof(dname);
	len = strlen(candidate);
	matchlen = 0;
	mname[0] = '\0';
	l = 0;
	for (ma = list; ma != NULL; ma = ma->next) {
		strlcpy(fname, ma->str, ep - fname);
		nlen = strlen(fname);
		if (nlen >= len &&
		    memcmp(fname, candidate, len) == 0) {
			if (nmatch && mname[0]) {
				for (i = len; i < matchlen; i++) {
					if (i >= nlen
					||  fname[i] != mname[i]) {
						matchlen = i;
						break;
					}
				}
			} else {
				matchlen = nlen;
			}
			nmatch++;
			if (callback)
				callback(arg, fname);
			strlcpy(mname, fname, sizeof(mname));
		}
	}
	if (nmatch && matchlen > len) {
		memcpy(candidate + len, mname + len, matchlen - len);
		candidate[matchlen] = '\0';
	}
	malias_free_list(list);
	return nmatch;
}

/*
 * it regards a string as an alias if there is no '@' mark in the string.
 * so it will be able to expand a string recursively.
 * XXX not sure it is correct processing.
 */
static struct malias_list *
malias_expand_addr(char *src, struct malias_keys *base)
{
	struct malias_keys *mk;
	struct malias_list *list = NULL, **ml = &list, *a;

	if (strchr(src, '@') != NULL) {
		/* substansial */
		if ((list = malloc(sizeof(*list))) == NULL)
			return NULL;
		list->str = strdup(src);
		list->next = NULL;
		return list;
	}

	for (mk = base; mk != NULL; mk = mk->next) {
		if (strcmp(src, mk->str) == 0)
			break;
	}
	if (mk == NULL)
		return NULL;

	for (a = mk->data; a != NULL; a = a->next) {
		if (((*ml) = malias_expand_addr(a->str, base)) == NULL) {
			malias_free_list(list);
			return NULL;
		}
		ml = &(*ml)->next;
	}

	return list;
}

/*
 * it expands a string to mail addresses.
 */
int
malias_expand(char *dst, int dstlen)
{
	char *src;
	struct malias_keys *base;
	struct malias_list *srclist, *s;
	struct malias_list *list = NULL, **ml = &list;
	int tlen;

	if ((src = strdup(dst)) == NULL)
		return 0;	/* error */

	if ((base = malias_read()) == NULL) {
		free(src);
		return 0;	/* error */
	}

	if (malias_disassemble_data(src, &srclist)) {
		free(src);
		malias_free_keys(base);
		return 0;
	}

	/* get the addresses expanded */
	for (s = srclist; s != NULL; s = s->next) {
		if (((*ml) = malias_expand_addr(s->str, base)) == NULL) {
			malias_free_list(list);
			malias_free_keys(base);
			return 0;
		}
		ml = &(*ml)->next;
	}
	malias_free_list(srclist);
	malias_free_keys(base);

	/* make the mail addresses */
	dst[0] = '\0';
	tlen = 0;
	for (s = list; s != NULL; s = s->next) {
		tlen = snprintf(dst + tlen, dstlen - tlen, "%s%s",
		    s->str, s->next != NULL ? ", " : "");
		if (dstlen <= tlen)
			break;
	}
	malias_free_list(list);

	return 0;
}
#endif /* MALIAS */
