/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
 *
 *  Authors: Michael Zucchi <notzed@ximian.com>
 *
 *  Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
 *
 *  This program is free software; you can redistribute it and/or
 *  modify it under the terms of version 2 of the GNU General Public
 *  License as published by the Free Software Foundation.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 *
 */

/* This is prototype code only, this may, or may not, use undocumented
 * unstable or private internal function calls. */

#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <string.h>
#include <stdio.h>

#include <gtk/gtk.h>
#include <glib/gi18n.h>
#include <glib/gstdio.h>

#include <camel/camel-folder.h>
#include <camel/camel-exception.h>
#include <camel/camel-mime-message.h>
#include <camel/camel-multipart.h>
#include <camel/camel-utf8.h>

#include "e-util/e-error.h"

#include "mail/em-menu.h"
#include "mail/em-utils.h"

/* these are sort of mail-internal */
#include "mail/mail-mt.h"
#include "mail/mail-ops.h"

void org_gnome_save_attachments_save(EPlugin *ep, EMMenuTargetSelect *target);

struct _save_data {
	CamelFolder *folder;
	char *uid;
	CamelMimeMessage *msg;

	char *path;
	char *base;

	GtkWidget *entry;
	GtkWidget *tree;
	GtkTreeStore *model;
};

static void
free_data(struct _save_data *data)
{
	if (data->msg)
		camel_object_unref(data->msg);
	g_free(data->base);
	g_free(data->path);
	g_free(data->uid);
	camel_object_unref(data->folder);
	if (data->model)
		g_object_unref(data->model);
	g_free(data);
}

static char *
clean_name(const char *s)
{
	GString *out = g_string_new("");
	int c;
	char *r;

	while ( (c = camel_utf8_getc((const unsigned char **)&s)) ) {
		if (!g_unichar_isprint(c) || ( c < 0x7f && strchr(" /'\"`&();|<>$%{}!", c )))
			c = '_';
		g_string_append_u(out, c);
	}

	r = g_strdup(out->str);
	g_string_free(out, TRUE);

	return r;
}

static void
fill_model_rec(CamelMimeMessage *msg, CamelMimePart *part, GtkTreeStore *model, GtkTreeIter *parent, GString *name)
{
	CamelDataWrapper *containee;
	int parts, i;
	char *type;
	GtkTreeIter iter;
	int len = name->len;
	CamelContentType *mime;

	containee = camel_medium_get_content_object((CamelMedium *)part);
	if (containee == NULL)
		return;

	mime = ((CamelDataWrapper *)containee)->mime_type;
	type = camel_content_type_simple(mime);

	if (CAMEL_IS_MULTIPART(containee)) {
		gtk_tree_store_append(model, &iter, parent);
		g_string_append_printf(name, ".multipart");
		gtk_tree_store_set(model, &iter, 0, FALSE, 1, type, 2, name->str, 3, name->str, 4, part, -1);

		parts = camel_multipart_get_number((CamelMultipart *)containee);
		for (i = 0; i < parts; i++) {
			CamelMimePart *mpart = camel_multipart_get_part((CamelMultipart *)containee, i);

			g_string_truncate(name, len);
			g_string_append_printf(name, ".%d", i);
			fill_model_rec(msg, mpart, model, &iter, name);
		}
	} else if (CAMEL_IS_MIME_MESSAGE(containee)) {
		gtk_tree_store_append(model, &iter, parent);
		g_string_append_printf(name, ".msg");
		gtk_tree_store_set(model, &iter, 0, FALSE, 1, type, 2, name->str, 3, name->str, 4, part, -1);
		fill_model_rec(msg, (CamelMimePart *)containee, model, &iter, name);
	} else {
		char *filename = NULL;
		const char *ext = NULL, *tmp;
		int save = FALSE;

		gtk_tree_store_append(model, &iter, parent);
		tmp = camel_mime_part_get_filename(part);
		if (tmp) {
			filename = clean_name(tmp);
			ext = strrchr(filename, '.');
		}
		tmp = camel_mime_part_get_disposition(part);
		if (tmp && !strcmp(tmp, "attachment"))
			save = TRUE;

		if (camel_content_type_is(mime, "text", "*")) {
			if (ext == NULL) {
				if ((ext = mime->subtype) == NULL || !strcmp(ext, "plain"))
					ext = "text";
			}
		} else if (camel_content_type_is(mime, "image", "*")) {
			if (ext == NULL) {
				if ((ext = mime->subtype) == NULL)
					ext = "image";
			}
			save = TRUE;
		}

		g_string_append_printf(name, ".%s", ext);
		gtk_tree_store_set(model, &iter, 0, save, 1, type, 2, filename?filename:name->str, 3, filename?NULL:name->str, 4, part, -1);
		g_free(filename);
	}
	g_free(type);

	g_string_truncate(name, len);
}

static void
fill_model(CamelMimeMessage *msg, GtkTreeStore *model)
{
	GString *name = g_string_new("");
	GtkTreeIter iter;

	gtk_tree_store_append(model, &iter, NULL);
	gtk_tree_store_set(model, &iter, 0, FALSE, 1, "message/rfc822", 2, ".msg", 3, ".msg", 4, msg, -1);
	fill_model_rec(msg, (CamelMimePart *)msg, model, &iter, name);
	g_string_free(name, TRUE);
}

static gboolean
save_part(GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, void *d)
{
	struct _save_data *data = d;
	char *filename, *ext, *save;
	CamelMimePart *part;
	gboolean doit;

	/* TODO: check for existing file */

	gtk_tree_model_get(model, iter, 0, &doit, -1);
	if (!doit)
		return FALSE;

	gtk_tree_model_get(model, iter, 2, &filename, 3, &ext, 4, &part, -1);
	if (ext == NULL)
		save = g_build_filename(data->path, filename, NULL);
	else
		save = g_strdup_printf("%s%s", data->base, ext);

	/* FIXME: if part == data->msg then we need to save this
	 * differently, not using the envelope MimePart */

	/*
	 * The underlying em_utils_save_part_to_file ain't using gnome-vfs. Therefor
	 * the POSIX access-call should suffice for checking the file existence.
	 */

	if (g_access(save, F_OK) == 0)
		doit = e_error_run(NULL, E_ERROR_ASK_FILE_EXISTS_OVERWRITE, save, NULL) == GTK_RESPONSE_OK;

	if (doit)
		em_utils_save_part_to_file(NULL, save, part);

	g_free(ext);
	g_free(filename);

	return FALSE;
}

static void
save_response(GtkWidget *d, int id, struct _save_data *data)
{
	if (id == GTK_RESPONSE_OK) {
		char *tmp;

		data->base = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (data->entry));
		data->path = g_strdup(data->base);
		tmp = strrchr(data->path, '/');
		if (tmp)
			*tmp = 0;
		gtk_tree_model_foreach((GtkTreeModel *)data->model, save_part, data);
	}

	gtk_widget_destroy(d);
	free_data(data);
}

static gboolean
entry_changed_update(GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, void *d)
{
	const char *name = d;
	char *filename, *ext;

	gtk_tree_model_get(model, iter, 3, &ext, -1);
	if (ext) {
		filename = g_strdup_printf("%s%s", name, ext);
		gtk_tree_store_set((GtkTreeStore *)model, iter, 2, filename, -1);
		g_free(filename);
		g_free(ext);
	}

	return FALSE;
}

static void
entry_changed(GtkWidget *entry, struct _save_data *data)
{
	char *path;
	char *basename = NULL;
	const char *file;

	path = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (data->entry));
	if (path == NULL
	    || G_IS_DIR_SEPARATOR (path[strlen(path)-1])
	    || (basename = g_path_get_basename(path)) == NULL
	    || (basename[0] == '.' && basename[1] == '\0')
	    || (g_file_test(path, G_FILE_TEST_IS_DIR)))
		file = "attachment";
	else
		file = basename;

	gtk_tree_model_foreach((GtkTreeModel *)data->model, entry_changed_update, (void *)file);
	g_free(path);
	g_free(basename);
}

static void
toggle_changed(GtkWidget *entry, const char *spath, struct _save_data *data)
{
        GtkTreePath *path;
        GtkTreeIter iter;

        path = gtk_tree_path_new_from_string(spath);
        if (gtk_tree_model_get_iter((GtkTreeModel *)data->model, &iter, path)) {
		gboolean on;

                gtk_tree_model_get((GtkTreeModel *)data->model, &iter, 0, &on, -1);
		gtk_tree_store_set(data->model, &iter, 0, !on, -1);
        }

        gtk_tree_path_free (path);
}

static void
save_got_message(CamelFolder *folder, const char *uid, CamelMimeMessage *msg, void *d)
{
	struct _save_data *data = d;
	GtkDialog *dialog;
	GtkWidget *w, *tree;
	GtkTreeStore *model;
	GtkCellRenderer *renderer;

	/* not found, the mailer will show an error box for this */
	if (msg == NULL) {
		free_data(data);
		return;
	}

	data->msg = msg;
	camel_object_ref(msg);

	dialog = (GtkDialog *)gtk_dialog_new_with_buttons(_("Save attachments"),
							  NULL, /* target->parent? */
							  0,
							  GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
							  GTK_STOCK_SAVE, GTK_RESPONSE_OK,
							  NULL);
	w = gtk_file_chooser_button_new (_("Select save base name"), GTK_FILE_CHOOSER_ACTION_OPEN);
	data->entry = w;
	g_object_set(w, "filechooser_action", GTK_FILE_CHOOSER_ACTION_SAVE, NULL);
	gtk_widget_show(w);
	gtk_box_pack_start((GtkBox *)dialog->vbox, w, FALSE, TRUE, 6);

	g_signal_connect(GTK_FILE_CHOOSER_BUTTON (w), "selection-changed", G_CALLBACK(entry_changed), data);

	model = gtk_tree_store_new(5, G_TYPE_BOOLEAN, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_POINTER);
	data->model = model;
	fill_model(msg, model);

	tree = gtk_tree_view_new_with_model((GtkTreeModel *)model);
	data->tree = tree;
	gtk_widget_show(tree);
	gtk_tree_view_expand_all((GtkTreeView *)tree);

	renderer = gtk_cell_renderer_text_new();
	gtk_tree_view_insert_column_with_attributes((GtkTreeView *)tree, -1,
						    _("MIME Type"), renderer, "text", 1, NULL);
	gtk_tree_view_set_expander_column((GtkTreeView *)tree, gtk_tree_view_get_column((GtkTreeView *)tree, 0));

	renderer = gtk_cell_renderer_toggle_new();
	g_object_set(renderer, "activatable", TRUE, NULL);
	g_signal_connect(renderer, "toggled", G_CALLBACK(toggle_changed), data);

	gtk_tree_view_insert_column_with_attributes((GtkTreeView *)tree, -1,
						    _("Save"), renderer, "active", 0, NULL);
	renderer = gtk_cell_renderer_text_new();
	gtk_tree_view_insert_column_with_attributes((GtkTreeView *)tree, -1,
						    _("Name"), renderer, "text", 2, NULL);

	w = g_object_new(gtk_frame_get_type(),
			 "shadow_type", GTK_SHADOW_NONE,
			 "label_widget", g_object_new(gtk_label_get_type(),
						      "label", "<span weight=\"bold\">Attachments</span>",
						      "use_markup", TRUE,
						      "xalign", 0.0, NULL),
			 "child", g_object_new(gtk_alignment_get_type(),
					       "left_padding", 12,
					       "top_padding", 6,
					       "child", g_object_new(gtk_scrolled_window_get_type(),
								     "hscrollbar_policy", GTK_POLICY_AUTOMATIC,
								     "vscrollbar_policy", GTK_POLICY_AUTOMATIC,
								     "shadow_type", GTK_SHADOW_IN,
								     "child", tree,
								     NULL),
					       NULL),
			 NULL);
	gtk_widget_show_all(w);

	gtk_box_pack_start((GtkBox *)dialog->vbox, w, TRUE, TRUE, 0);
	g_signal_connect(dialog, "response", G_CALLBACK(save_response), data);
	gtk_window_set_default_size((GtkWindow *)dialog, 500, 500);
	gtk_widget_show((GtkWidget *)dialog);
}

void
org_gnome_save_attachments_save(EPlugin *ep, EMMenuTargetSelect *target)
{
	struct _save_data *data;

	if (target->uids->len != 1)
		return;

	data = g_malloc0(sizeof(*data));
	data->folder = target->folder;
	camel_object_ref(data->folder);
	data->uid = g_strdup(target->uids->pdata[0]);

	mail_get_message(data->folder, data->uid, save_got_message, data, mail_msg_unordered_push);
}
