/* Copyright (C) 2004, 2005 Morten K. Poulsen <morten at afdelingp.dk>
 *
 * $Id$
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to
 * deal in the Software without restriction, including without limitation the
 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
 * sell copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
 * IN THE SOFTWARE.
 */

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

#include "mlmmj.h"
#include "send_digest.h"
#include "log_error.h"
#include "strgen.h"
#include "xmalloc.h"
#include "wrappers.h"
#include "prepstdreply.h"
#include "gethdrline.h"
#include "statctrl.h"
#include "unistr.h"
#include "utils.h"


struct mail {
	int idx;
	char *from;
};

struct thread {
	char *subject;
	int num_mails;
	struct mail *mails;
};


struct thread_list_state;
typedef struct thread_list_state thread_list_state;
struct thread_list_state {
	const char *listdir;
	int firstindex;
	int lastindex;
	int num_threads;
	struct thread *threads;
	int cur_thread;
	int cur_mail;
};


static thread_list_state *init_thread_list(
		const char *listdir, int firstindex, int lastindex)
{
	/* We use a static variable rather than dynamic allocation as
	 * there will never be two lists in use simultaneously */
	static thread_list_state s;
	s.listdir = listdir;
	s.firstindex = firstindex;
	s.lastindex = lastindex;
	s.num_threads = 0;
	s.threads = NULL;
	s.cur_thread = -1;
	return &s;
}


static void rewind_thread_list(void * state)
{
	thread_list_state *s = (thread_list_state *)state;
	FILE *archivef;
	int i, j, thread_idx;
	char *line = NULL, *tmp, *subj, *from, *tmpfrom;
	size_t linecap = 0;
	char *archivename;
	int num_threads = 0;
	struct thread *threads = NULL;
	char buf[45];

	if (s->cur_thread != -1) {
		/* We have gathered the data already; just rewind */
		s->cur_thread = 0;
		s->cur_mail = -1;
		return;
	}

	for (i=s->firstindex; i<=s->lastindex; i++) {
		xasprintf(&archivename, "%s/archive/%d", s->listdir, i);
		archivef = fopen(archivename, "r");
		free(archivename);

		if (archivef == NULL)
			continue;

		subj = NULL;
		from = NULL;

		while (getline(&line, &linecap, archivef) > 0) {
			if (strncasecmp(line, "Subject:", 8) == 0) {
				free(subj);
				subj = unistr_header_to_utf8(line + 8);
			}
			if (strncasecmp(line, "From:", 5) == 0) {
				free(from);
				from = unistr_header_to_utf8(line + 5);
			}
		}

		if (!subj) {
			subj = xstrdup("no subject");
		}

		if (!from) {
			from = xstrdup("anonymous");
		}

		tmp = subj;
		for (;;) {
			if (isspace(*tmp)) {
				tmp++;
				continue;
			}
			if (strncasecmp(tmp, "Re:", 3) == 0) {
				tmp += 3;
				continue;
			}
			break;
		}
		/* tmp is now the clean subject */

		thread_idx = -1;
		for (j=0; j<num_threads; j++) {
			if (strcmp(subj, threads[j].subject) == 0) {
				thread_idx = j;
				break;
			}
		}
		if (thread_idx == -1) {
			num_threads++;
			threads = xrealloc(threads,
					num_threads*sizeof(struct thread));
			xasprintf(&(threads[num_threads-1].subject), "%s\n", tmp);
			threads[num_threads-1].num_mails = 0;
			threads[num_threads-1].mails = NULL;
			thread_idx = num_threads-1;
		}

		tmp = from;
		for (;;) {
			if (isspace(*tmp)) {
				tmp++;
				continue;
			}
			break;
		}
		/* tmp is now left-trimmed from */

		threads[thread_idx].num_mails++;
		threads[thread_idx].mails = xrealloc(threads[thread_idx].mails,
				threads[thread_idx].num_mails*sizeof(struct mail));
		threads[thread_idx].mails[threads[thread_idx].num_mails-1].idx = i;
		xasprintf(&tmpfrom, "      %s - %s\n", buf, tmp);
		threads[thread_idx].mails[threads[thread_idx].num_mails-1].from = tmpfrom;

		free(subj);
		free(from);

		fclose(archivef);
	}
	free(line);

	s->num_threads = num_threads;
	s->threads = threads;
	s->cur_thread = 0;
	s->cur_mail = -1;
}


static const char *get_thread_list_line(void * state)
{
	thread_list_state *s = (thread_list_state *)state;

	if (s->cur_thread >= s->num_threads) return NULL;

	if (s->cur_mail == -1) {
		s->cur_mail = 0;
		return s->threads[s->cur_thread].subject;
	}

	if (s->cur_mail >= s->threads[s->cur_thread].num_mails) {
		s->cur_thread++;
		s->cur_mail = -1;
		return "\n";
	}

	return s->threads[s->cur_thread].mails[s->cur_mail++].from;
}


static void finish_thread_list(thread_list_state * s)
{
	int i, j;
	if (s->threads == NULL) return;
	for (i=0; i<s->num_threads; i++) {
		free(s->threads[i].subject);
		for (j=0; j<s->threads[i].num_mails; j++) {
			free(s->threads[i].mails[j].from);
		}
		free(s->threads[i].mails);
	}
	free(s->threads);
	s->threads = NULL;
}


int send_digest(struct ml *ml, int firstindex, int lastindex,
		int issue, const char *addr, const char *mlmmjsend)
{
	int i, fd, archivefd, hdrfd;
	size_t len;
	text * txt;
	char buf[100];
	char *tmp, *queuename = NULL, *archivename, *subject = NULL, *line = NULL;
	char *boundary;
	thread_list_state * tls;

	if (addr) {
		errno = 0;
		log_error(LOG_ARGS, "send_digest() does not support sending "
				"digest mails to only one recipient yet");
		return -1;
	}

	if (firstindex > lastindex)
		return -1;
	
	do {
		tmp = random_str();
		free(queuename);
		xasprintf(&queuename, "%s/queue/%s", ml->dir, tmp);
		free(tmp);
		fd = open(queuename, O_RDWR|O_CREAT|O_EXCL, S_IRUSR|S_IWUSR);
	} while ((fd < 0) && (errno == EEXIST));

	if (fd < 0) {
		log_error(LOG_ARGS, "Could not open digest queue file '%s'",
				queuename);
		free(queuename);
		return -1;
	}

	hdrfd = openat(ml->ctrlfd, "customheaders", O_RDONLY);

	boundary = random_str();

	txt = open_text_file(ml->fd, "digest");
	if (txt == NULL) {
		log_error(LOG_ARGS, "Could not open listtext 'digest'");
		goto fallback_subject;
	}
	register_default_unformatted(txt, ml);

	snprintf(buf, sizeof(buf), "%d", firstindex);
	register_unformatted(txt, "digestfirst", buf);

	snprintf(buf, sizeof(buf), "%d", lastindex);
	register_unformatted(txt, "digestlast", buf);

	if (lastindex == firstindex) {
		snprintf(buf, sizeof(buf), "%d", firstindex);
	} else {
		snprintf(buf, sizeof(buf), "%d-%d", firstindex, lastindex);
	}
	register_unformatted(txt, "digestinterval", buf);

	snprintf(buf, sizeof(buf), "%d", issue);
	register_unformatted(txt, "digestissue", buf);

	tls = init_thread_list(ml->dir, firstindex, lastindex);
	register_formatted(txt, "digestthreads", rewind_thread_list,
			get_thread_list_line, tls);

	line = get_processed_text_line(txt, true, ml);

	if (line == NULL) {
		log_error(LOG_ARGS, "No content in digest listtext");
		goto fallback_subject;
	}

	tmp = line;
	len = 0;
	while (*tmp && *tmp != ':') {
		tmp++;
		len++;
	}
	if (!*tmp) {
		log_error(LOG_ARGS, "No subject or invalid "
				"subject in digest listtext");
		goto fallback_subject;
	}
	tmp++;
	len++;
	if (strncasecmp(line, "Subject:", len) == 0) {
		tmp = unistr_utf8_to_header(tmp);
		xasprintf(&subject, "Subject: %s", tmp);
		free(tmp);

		/* Skip the empty line after the subject */
		line = get_processed_text_line(txt, true, ml);
		if (line == NULL || *line != '\0') {
			log_error(LOG_ARGS, "Too many headers "
					"in digest listtext");
			goto fallback_subject;
		}

		free(line);
		line = NULL;
	} else {
		log_error(LOG_ARGS, "No subject or invalid "
				"subject in digest listtext");
		goto fallback_subject;
	}

fallback_subject:
	if (subject == NULL) {
		if (lastindex == firstindex) {
			snprintf(buf, sizeof(buf), "%d", firstindex);
		} else {
			snprintf(buf, sizeof(buf), "%d-%d", firstindex, lastindex);
		}
		tmp = xstrdup(buf);
		snprintf(buf, sizeof(buf), "Digest of %s issue %d (%s)",
				ml->addr, issue, tmp);
		subject = unistr_utf8_to_header(buf);
		free(tmp);
	}

	if (dprintf(fd, "From: %s%shelp@%s"
	    "\nMIME-Version: 1.0"
	    "\nContent-Type: multipart/" DIGESTMIMETYPE "; "
	    "boundary=%s"
	    "\nSubject: %s\n",
	    ml->name, ml->delim, ml->fqdn, boundary, subject) < 0) {
		free(subject);
		goto errdighdrs;
	}
	free(subject);

	if(hdrfd >= 0 && dumpfd2fd(hdrfd, fd) < 0) {
		goto errdighdrs;
	}

	close(hdrfd);
	hdrfd = -1;

	if (dprintf(fd, "\n") < 0) {
errdighdrs:
		log_error(LOG_ARGS, "Could not write digest headers to '%s'",
				queuename);
		close(fd);
		unlink(queuename);
		free(boundary);
		free(queuename);
		if (txt != NULL) {
			close_text(txt);
			free(line);
		}
		if (hdrfd > 0) {
			close(hdrfd);
		}
		return -1;
	}

	if ((txt != NULL) && !statctrl(ml->ctrlfd, "nodigesttext")) {

		if (dprintf(fd, "\n--%s"
		    "\nContent-Type: text/plain; charset=UTF-8"
		    "\nContent-Transfer-Encoding: 8bit"
		    "\n\n", boundary) < 0) {
			log_error(LOG_ARGS, "Could not write digest text/plain"
					" part headers to '%s'", queuename);
			close(fd);
			unlink(queuename);
			free(boundary);
			free(queuename);
			if (txt != NULL) {
				close_text(txt);
				free(line);
			}
			return -1;
		}

		for (;;) {
			line = get_processed_text_line(txt, false, ml);
			if (line == NULL) break;
			if(dprintf(fd, "%s\n", line) < 0) {
				log_error(LOG_ARGS, "Could not write"
						" std mail");
				break;
			}
			free(line);
			line = NULL;
		}

		finish_thread_list(tls);
		close_text(txt);
	} else if (txt != NULL) {
		finish_thread_list(tls);
		close_text(txt);
	}

	if (line != NULL) free(line);

	for (i=firstindex; i<=lastindex; i++) {
		xasprintf(&archivename, "archive/%d", i);
		archivefd = openat(ml->fd, archivename, O_RDONLY);
		free(archivename);

		if (archivefd < 0)
			continue;

		if (dprintf(fd, "\n--%s"
		    "\nContent-Type: message/rfc822"
		    "\nContent-Disposition: inline; filename=\"%s_%s.eml\"\n\n",
		    boundary, ml->name, buf) < 0) {
			log_error(LOG_ARGS, "Could not write digest part "
					"headers for archive index %d to "
					"'%s'", i, queuename);
			close(fd);
			close(archivefd);
			unlink(queuename);
			free(boundary);
			free(queuename);
			return -1;
		}

		if (dumpfd2fd(archivefd, fd) < 0) {
			log_error(LOG_ARGS, "Could not write digest part %d "
					"to '%s'", i,
					queuename);
			close(fd);
			close(archivefd);
			unlink(queuename);
			free(boundary);
			free(queuename);
			return -1;
		}
		close(archivefd);
	}

	if (dprintf(fd, "\n--%s--\n", boundary) < 0) {
		log_error(LOG_ARGS, "Could not write digest end to '%s'",
				queuename);
		close(fd);
		unlink(queuename);
		free(boundary);
		free(queuename);
		return -1;
	}

	close(fd);
	free(boundary);

	exec_and_wait(mlmmjsend, "-l", "7", "-L", ml->dir, "-m", queuename,
	    NULL);
	free(queuename);

	return 0;
}
