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

#include "lib.h"
#include "lib-signals.h"
#include "ioloop.h"
#include "env-util.h"
#include "str.h"
#include "ostream.h"
#include "array.h"
#include "mail-namespace.h"
#include "mail-storage.h"
#include "master-service.h"
#include "master-service-settings.h"
#include "mail-storage-service.h"

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

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

#include "sieve-ext-debug.h"

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


/* 
 * Configuration
 */

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

/*
 * Print help
 */

static void print_help(void)
{
	printf(
"Usage: sieve-test [-c] [-d <dump-filename>] [-e] [-f <envelope-sender>]\n"
"                  [-l <mail-location>] [-m <default-mailbox>]\n" 
"                  [-r <recipient-address>] [-s <script-file>]\n"
"                  [-t] [-x <extensions>] <script-file> <mail-file>\n"
	);
}

/*
 * Dummy SMTP session
 */

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

static bool sieve_smtp_close
(void *script_ctx ATTR_UNUSED, 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) 
{
	enum mail_storage_service_flags service_flags = 0;
	struct master_service *master_service;
	int c;
	ARRAY_DEFINE(scriptfiles, const char *);
	const char *scriptfile, *recipient, *sender, *mailbox, *dumpfile, *mailfile, 
		*mailloc, *extensions; 
	const char *user, *home;
	struct mail_raw *mailr;
	struct mail_namespace_settings ns_set;
	struct mail_namespace *ns = 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;

	master_service = master_service_init("sieve-test",
        MASTER_SERVICE_FLAG_STANDALONE,
        &argc, &argv, "r:f:m:d:l:x:s:ect");

	sieve_tool_init(NULL, FALSE);

	t_array_init(&scriptfiles, 16);

	user = getenv("USER");
	
	/* Parse arguments */
	scriptfile = recipient = sender = mailbox = dumpfile = mailfile = mailloc = 
		extensions = NULL;
	while ((c = master_getopt(master_service)) > 0) {
		switch (c) {
		case 'r':
			/* destination address */
			recipient = optarg;
			break;
		case 'f':
			/* envelope sender address */
			sender = optarg;
			break;
		case 'm':
			/* default mailbox (keep box) */
			mailbox = optarg;
			break;
		case 'd':
			/* dump file */
			dumpfile = optarg;
			break;
		case 'l':
			/* mail location */
			mailloc = optarg;
			break;
		case 'x':
			/* mail location */
			extensions = optarg;
			break;
		case 's': 
			/* scriptfile executed before main script */
			{
				const char *file;			

				file = t_strdup(optarg);
				array_append(&scriptfiles, &file, 1);
			}
			break;
		case 'e':
			execute = TRUE;
			break;
		case 'c':
			force_compile = TRUE;
			break;
		case 't':
			trace = TRUE;
			break;
		default:
			print_help();
			i_fatal_status(EX_USAGE, "Unknown argument: %c", c);
			break;
		}
	}

	if ( optind < argc ) {
		scriptfile = t_strdup(argv[optind++]);
	} else { 
		print_help();
		i_fatal_status(EX_USAGE, "Missing <script-file> argument");
	}
	
	if ( optind < argc ) {
		mailfile = t_strdup(argv[optind++]);
	} else { 
		print_help();
		i_fatal_status(EX_USAGE, "Missing <mail-file> argument");
	}
	
	if (optind != argc) {
		print_help();
		i_fatal_status(EX_USAGE, "Unknown argument: %s", argv[optind]);
	}

	if ( extensions != NULL ) {
		sieve_set_extensions(sieve_instance, extensions);
	}

	/* Register tool-specific extensions */
	(void) sieve_extension_register(sieve_instance, &debug_extension, TRUE);
	
	/* Create error handler */
	ehandler = sieve_stderr_ehandler_create(0);
	sieve_system_ehandler_set(ehandler);
	sieve_error_handler_accept_infolog(ehandler, TRUE);

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

	if ( main_sbin != NULL ) {
		struct mail_user *mail_user_dovecot = NULL;
		struct mail_user *mail_user = NULL;
		struct mail_storage_service_input input;

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

		/* Initialize mail storages */
		//env_remove("HOME");
		env_put("DOVECONF_ENV=1");
		env_put(t_strdup_printf("MAIL=maildir:/tmp/dovecot-test-%s", user));

		master_service_init_finish(master_service);

		memset(&input, 0, sizeof(input));
		input.username = user;
		mail_user_dovecot = 
			mail_storage_service_init_user(master_service, &input,
                NULL, service_flags);
	
		/* Obtain mail namespaces from -l argument */
		if ( mailloc != NULL ) {
			const char *errstr;

			mail_user = mail_user_alloc(user, mail_user_dovecot->unexpanded_set);
			mail_user_set_home(mail_user, home);

			if ( mail_user_init(mail_user, &errstr) < 0 )
        		i_fatal("Test user initialization failed: %s", errstr);

			memset(&ns_set, 0, sizeof(ns_set));
			ns_set.location = mailloc;

			ns = mail_namespaces_init_empty(mail_user);
			ns->flags |= NAMESPACE_FLAG_NOQUOTA | NAMESPACE_FLAG_NOACL;
			ns->set = &ns_set;

			if ( mail_storage_create(ns, NULL, 0, &errstr) < 0 )
		        i_fatal("Test storage creation failed: %s", errstr);
		}

		if ( master_service_set(master_service, "mail_full_filesystem_access=yes") < 0 )
			i_unreached(); 

		/* Initialize raw mail object */
		mail_raw_init(master_service, user, mail_user_dovecot);
		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;
	
		/* 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, NULL);
			else
				ret = sieve_test
					(sbin, &msgdata, &scriptenv, ehandler, teststream, NULL);				
		} 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
					(sieve_instance, &msgdata, &scriptenv);
			else
				mscript = sieve_multiscript_start_test
					(sieve_instance, &msgdata, &scriptenv, 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]);
					if ( sbin != NULL )
						(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, ehandler, 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, ehandler, TRUE);
			}
			
			result = sieve_multiscript_finish(&mscript, ehandler, NULL);
			
			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 */
		if ( sbin != NULL )
			sieve_close(&sbin);
		if ( main_sbin != NULL ) 
			sieve_close(&main_sbin);
		
		/* De-initialize raw mail object */
		mail_raw_close(mailr);
		mail_raw_deinit();

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

		if ( mail_user_dovecot != NULL )
			mail_user_unref(&mail_user_dovecot);
	
		mail_storage_service_deinit_user();
	}

	/* Cleanup error handler */
	sieve_error_handler_unref(&ehandler);
	sieve_system_ehandler_reset();

	sieve_tool_deinit();

	master_service_deinit(&master_service);
	
	return 0;
}