/* Copyright (c) 2002-2009 Dovecot Sieve authors, see the included COPYING file
 */

#include "lib.h"
#include "ostream.h"
#include "array.h"
#include "mail-namespace.h"
#include "mail-storage.h"
#include "env-util.h"

#include "sieve.h"
#include "sieve-binary.h"

#include "mail-raw.h"
#include "sieve-tool.h"

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <pwd.h>

/* 
 * Configuration
 */

#define DEFAULT_SENDMAIL_PATH "/usr/lib/sendmail"
#define DEFAULT_ENVELOPE_SENDER "MAILER-DAEMON"

/*
 * Print help
 */

static void print_help(void)
{
#ifdef SIEVE_RUNTIME_TRACE
#  define SVTRACE " [-t]"
#else
#  define SVTRACE
#endif
	printf(
"Usage: sieve-test [-r <recipient address>] [-f <envelope sender>]\n"
"                  [-m <mailbox>] [-d <dump filename>] [-x <extensions>]\n"
"                  [-s <scriptfile>] [-c]"SVTRACE"\n"
"                  <scriptfile> <mailfile>\n"
	);
}

/*
 * Dummy SMTP session
 */

static void *sieve_smtp_open(const char *destination,
	const char *return_path, FILE **file_r)
{
	i_info("sending message from <%s> to <%s>:",
		return_path == NULL || *return_path == '\0' ? "" : return_path, 
		destination);
	printf("\nSTART MESSAGE:\n");
	
	*file_r = stdout;
	
	return NULL;	
}

static bool sieve_smtp_close(void *handle ATTR_UNUSED)
{
	printf("END MESSAGE\n\n");
	return TRUE;
}

/*
 * Dummy duplicate check implementation
 */

static int duplicate_check(const void *id ATTR_UNUSED, size_t id_size ATTR_UNUSED, 
	const char *user)
{
	i_info("checked duplicate for user %s.\n", user);
	return 0;
}

static void duplicate_mark
(const void *id ATTR_UNUSED, size_t id_size ATTR_UNUSED, const char *user, 
	time_t time ATTR_UNUSED)
{
	i_info("marked duplicate for user %s.\n", user);
}

/*
 * Tool implementation
 */

int main(int argc, char **argv) 
{
	ARRAY_DEFINE(scriptfiles, const char *);
	const char *scriptfile, *recipient, *sender, *mailbox, *dumpfile, *mailfile, 
		*mailloc, *extensions; 
	const char *user, *home;
	int i;
	struct mail_raw *mailr;
	struct mail_namespace *ns = NULL;
	struct mail_user *mail_user = NULL;
	struct sieve_binary *main_sbin, *sbin = NULL;
	struct sieve_message_data msgdata;
	struct sieve_script_env scriptenv;
	struct sieve_exec_status estatus;
	struct sieve_error_handler *ehandler;
	struct ostream *teststream = NULL;
	bool force_compile = FALSE, execute = FALSE;
	bool trace = FALSE;
	int ret;

	sieve_tool_init();
	
	t_array_init(&scriptfiles, 16);

	/* Parse arguments */
	scriptfile = recipient = sender = mailbox = dumpfile = mailfile = mailloc = 
		extensions = NULL;
	for (i = 1; i < argc; i++) {
		if (strcmp(argv[i], "-r") == 0) {
			/* recipient address */
			i++;
			if (i == argc)
				i_fatal("Missing -r argument");
			recipient = argv[i];
		} else if (strcmp(argv[i], "-f") == 0) {
			/* envelope sender */
			i++;
			if (i == argc)
				i_fatal("Missing -f argument");
			sender = argv[i];
		} else if (strcmp(argv[i], "-m") == 0) {
			/* default mailbox (keep box) */
			i++;
			if (i == argc) 
				i_fatal("Missing -m argument");
			mailbox = argv[i];
		} else if (strcmp(argv[i], "-d") == 0) {
			/* dump file */
			i++;
			if (i == argc)
				i_fatal("Missing -d argument");
			dumpfile = argv[i];
		} else if (strcmp(argv[i], "-l") == 0) {
			/* mail location */
			i++;
			if (i == argc)
				i_fatal("Missing -l argument");
			mailloc = argv[i];
		} else if (strcmp(argv[i], "-x") == 0) {
			/* extensions */
			i++;
			if (i == argc)
				i_fatal("Missing -x argument");
			extensions = argv[i];
		} else if (strcmp(argv[i], "-s") == 0) {
			const char *file;
			
			/* scriptfile executed before main script */
			i++;
			if (i == argc)
				i_fatal("Missing -s argument");
				
			file = t_strdup(argv[i]);
			array_append(&scriptfiles, &file, 1);
		} else if (strcmp(argv[i], "-c") == 0) {
			/* force compile */
			force_compile = TRUE;
		} else if (strcmp(argv[i], "-e") == 0) {
			/* execute */
			execute = TRUE;
#ifdef SIEVE_RUNTIME_TRACE
		} else if (strcmp(argv[i], "-t") == 0) {
			/* runtime trace */
			trace = TRUE;
#endif
		} else if ( scriptfile == NULL ) {
			scriptfile = argv[i];
		} else if ( mailfile == NULL ) {
			mailfile = argv[i];
		} else {
			print_help();
			i_fatal("Unknown argument: %s", argv[i]);
		}
	}
	
	if ( scriptfile == NULL ) {
		print_help();
		i_fatal("Missing <scriptfile> argument");
	}
	
	if ( mailfile == NULL ) {
		print_help();
		i_fatal("Missing <mailfile> argument");
	}

	if ( extensions != NULL ) {
		sieve_set_extensions(extensions);
	}
	
	/* Compile main sieve script */
	if ( force_compile ) {
		main_sbin = sieve_tool_script_compile(scriptfile, NULL);
		(void) sieve_save(main_sbin, NULL);
	} else {
		main_sbin = sieve_tool_script_open(scriptfile);
	}

	if ( main_sbin != NULL ) {
		/* Dump script */
		sieve_tool_dump_binary_to(main_sbin, dumpfile);
	
		user = sieve_tool_get_user();
		home = getenv("HOME");

		/* Initialize mail storages */
		mail_users_init(getenv("AUTH_SOCKET_PATH"), getenv("DEBUG") != NULL);
		mail_storage_init();
		mail_storage_register_all();
		mailbox_list_register_all();
	
		/* Obtain mail namespaces from -l argument */
		if ( mailloc != NULL ) {
			env_put(t_strdup_printf("NAMESPACE_1=%s", mailloc));
			env_put("NAMESPACE_1_INBOX=1");
			env_put("NAMESPACE_1_LIST=1");
			env_put("NAMESPACE_1_SEP=.");
			env_put("NAMESPACE_1_SUBSCRIPTIONS=1");

			mail_user = mail_user_init(user);
			mail_user_set_home(mail_user, home);
			if (mail_namespaces_init(mail_user) < 0)
				i_fatal("Namespace initialization failed");	

			ns = mail_user->namespaces;
		}

		/* Initialize raw mail object */
		mail_raw_init(user);
		mailr = mail_raw_open_file(mailfile);

		sieve_tool_get_envelope_data(mailr->mail, &recipient, &sender);

		if ( mailbox == NULL )
			mailbox = "INBOX";

		/* Collect necessary message data */
		memset(&msgdata, 0, sizeof(msgdata));
		msgdata.mail = mailr->mail;
		msgdata.return_path = sender;
		msgdata.to_address = recipient;
		msgdata.auth_user = user;
		(void)mail_get_first_header(mailr->mail, "Message-ID", &msgdata.id);

		/* Create stream for test and trace output */
		if ( !execute || trace )
			teststream = o_stream_create_fd(1, 0, FALSE);	
		
		/* Compose script environment */
		memset(&scriptenv, 0, sizeof(scriptenv));
		scriptenv.default_mailbox = "INBOX";
		scriptenv.namespaces = ns;
		scriptenv.username = user;
		scriptenv.hostname = "host.example.com";
		scriptenv.postmaster_address = "postmaster@example.com";
		scriptenv.smtp_open = sieve_smtp_open;
		scriptenv.smtp_close = sieve_smtp_close;
		scriptenv.duplicate_mark = duplicate_mark;
		scriptenv.duplicate_check = duplicate_check;
		scriptenv.trace_stream = ( trace ? teststream : NULL );
		scriptenv.exec_status = &estatus;
	
		/* Create error handler */
		ehandler = sieve_stderr_ehandler_create(0);	
		sieve_error_handler_accept_infolog(ehandler, TRUE);

		/* Run the test */
		ret = 1;
		if ( array_count(&scriptfiles) == 0 ) {
			/* Single script */
			sbin = main_sbin;
			main_sbin = NULL;
	
			/* Execute/Test script */
			if ( execute )
				ret = sieve_execute(sbin, &msgdata, &scriptenv, ehandler);
			else
				ret = sieve_test(sbin, &msgdata, &scriptenv, ehandler, teststream);				
		} else {
			/* Multiple scripts */
			const char *const *sfiles;
			unsigned int i, count;
			struct sieve_multiscript *mscript;
			bool more = TRUE;
			int result;

			if ( execute )
				mscript = sieve_multiscript_start_execute
					(&msgdata, &scriptenv, ehandler);
			else
				mscript = sieve_multiscript_start_test
					(&msgdata, &scriptenv, ehandler, teststream);
		
			/* Execute scripts sequentially */
			sfiles = array_get(&scriptfiles, &count); 
			for ( i = 0; i < count && more; i++ ) {
				if ( teststream != NULL ) 
					o_stream_send_str(teststream, 
						t_strdup_printf("\n## Executing script: %s\n", sfiles[i]));
			
				/* Close previous script */
				if ( sbin != NULL )						
					sieve_close(&sbin);
		
				/* Compile sieve script */
				if ( force_compile ) {
					sbin = sieve_tool_script_compile(sfiles[i], sfiles[i]);
					(void) sieve_save(sbin, NULL);
				} else {
					sbin = sieve_tool_script_open(sfiles[i]);
				}
			
				if ( sbin == NULL ) {
					ret = SIEVE_EXEC_FAILURE;
					break;
				}
			
				/* Execute/Test script */
				more = sieve_multiscript_run(mscript, sbin, FALSE);
			}
		
			/* Execute/Test main script */
			if ( more && ret > 0 ) {
				if ( teststream != NULL ) 
					o_stream_send_str(teststream, 
						t_strdup_printf("## Executing script: %s\n", scriptfile));
				
				/* Close previous script */
				if ( sbin != NULL )						
					sieve_close(&sbin);	
				
				sbin = main_sbin;
				main_sbin = NULL;
			
				sieve_multiscript_run(mscript, sbin, TRUE);
			}
			
			result = sieve_multiscript_finish(&mscript);
			
			ret = ret > 0 ? result : ret;
		}
	
		/* Run */
		switch ( ret ) {
		case SIEVE_EXEC_OK:
			i_info("final result: success");
			break;
		case SIEVE_EXEC_BIN_CORRUPT:
			i_info("corrupt binary deleted.");
			(void) unlink(sieve_binary_path(sbin));
		case SIEVE_EXEC_FAILURE:
			i_info("final result: failed; resolved with successful implicit keep");
			break;
		case SIEVE_EXEC_KEEP_FAILED:
			i_info("final result: utter failure");
			break;
		default:
			i_info("final result: unrecognized return value?!");	
		}

		if ( teststream != NULL )
			o_stream_destroy(&teststream);

		/* Cleanup remaining binaries */
		sieve_close(&sbin);
		if ( main_sbin != NULL ) sieve_close(&main_sbin);
		
		/* Cleanup error handler */
		sieve_error_handler_unref(&ehandler);

		/* De-initialize raw mail object */
		mail_raw_close(mailr);
		mail_raw_deinit();

		/* De-initialize mail user object */
		if ( mail_user != NULL )
			mail_user_unref(&mail_user);

		/* De-initialize mail storages */
		mail_storage_deinit();
		mail_users_deinit();
	}

	sieve_tool_deinit();
	
	return 0;
}