/* This file is part of GQ
 *
 * AUTHORS
 *     Sven Herzberg
 *
 * Copyright (C) 1998-2003 Bert Vermeulen
 * Copyright (C) 2002-2003 Peter Stamfest
 * Copyright (C) 2006  Sven Herzberg
 *
 * This program is released under the Gnu General Public License with
 * the additional exemption that compiling, linking, and/or using
 * OpenSSL is allowed.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 2 of the
 * License, or (at your option) any later version.
 *
 * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307
 * USA
 */

#include "gq-export-dialog.h"

#include <glib.h>
#include <glib/gi18n.h>
#include <gtk/gtk.h>
#include <string.h>
#include <errno.h>		/* errno */
#include <stdio.h>		/* FILE */
#include <stdlib.h>		/* free - MUST get rid of malloc/free */

#ifdef HAVE_CONFIG_H
# include  <config.h>
#endif /* HAVE_CONFIG_H */

#include "browse-dnd.h"		/* copy_entry et al */
#include "common.h"
#include "configfile.h"		/* config */
#include "errorchain.h"
#include "encode.h"
#include "gq-browser-node-dn.h"
#include "gq-browser-node-reference.h"
#include "gq-utilities.h"
#include "ldif.h"
#include "search.h"		/* fill_out_search */
#include "template.h"		/* struct gq_template */

typedef struct {
	GtkFileChooserDialog base_instance;
	GList    * to_export;
	GtkWidget* filetype_box;
	GtkWidget* filetype_label;
	GtkWidget* filetype_combo;
} GqExport;
typedef GtkFileChooserDialogClass GqExportClass;

/* GType */
GType gq_export_get_type(void);
#define GQ_TYPE_EXPORT         (gq_export_get_type())
#define GQ_EXPORT(i)           (G_TYPE_CHECK_INSTANCE_CAST((i), GQ_TYPE_EXPORT, GqExport))
G_DEFINE_TYPE(GqExport, gq_export, GTK_TYPE_FILE_CHOOSER_DIALOG);

static gchar const* type_names[] = {
	N_("ldifformat|UMich/OpenLDAP style (no comments/version)"),
	N_("ldifformat|LDIF Version 1 (RFC2849)")
};

static void
gq_export_init(GqExport* self)
{
	GtkListStore* store = gtk_list_store_new(2, G_TYPE_INT, G_TYPE_STRING);
	GqLdifType type;
	// TRANSLATORS: there is a unicode symbol for the ellipsis (...)
	gtk_window_set_title(GTK_WINDOW(self), _("Export Entries..."));
	gtk_dialog_add_buttons(GTK_DIALOG(self),
			       GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
			       GTK_STOCK_SAVE,   GTK_RESPONSE_ACCEPT,
			       NULL);

#warning "FIXME: add filter on text/x-ldif and *.ldif"

	self->filetype_box   = gtk_hbox_new(FALSE, 6);
	self->filetype_label = gtk_label_new(_("LDIF Format:"));
#warning "FIXME: set mnemonic and target widget"
	gtk_box_pack_start(GTK_BOX(self->filetype_box), self->filetype_label,
			   FALSE, FALSE, 0);
	self->filetype_combo = gtk_combo_box_new_with_model(GTK_TREE_MODEL(store));
	gtk_box_pack_start_defaults(GTK_BOX(self->filetype_box), self->filetype_combo);
	g_object_unref(store);

	{
		GtkCellRenderer* renderer = gtk_cell_renderer_text_new();
		GtkCellLayout* layout = GTK_CELL_LAYOUT(self->filetype_combo);

		gtk_cell_layout_pack_start(layout, renderer, TRUE);
		gtk_cell_layout_set_attributes(layout, renderer,
					       "text", 1,
					       NULL);
	}

	for(type = 0; type < G_N_ELEMENTS(type_names); type++) {
		GtkTreeIter iter;
		gtk_list_store_append(store, &iter);
		gtk_list_store_set(store, &iter,
				   0, type,
				   1, Q_(type_names[type]),
				   -1);

		if(type == DEFAULT_LDIFFORMAT) {
			gtk_combo_box_set_active_iter(GTK_COMBO_BOX(self->filetype_combo), &iter);
		}
	}
#warning "FIXME: add help button to explain the different formats"
}

static GObject*
export_constructor(GType                  type,
		   guint                  n_props,
		   GObjectConstructParam* props)
{
	GObject* object = G_OBJECT_CLASS(gq_export_parent_class)->constructor(type, n_props, props);
	gtk_file_chooser_set_action(GTK_FILE_CHOOSER(object),
				    GTK_FILE_CHOOSER_ACTION_SAVE);
	gtk_file_chooser_set_extra_widget(GTK_FILE_CHOOSER(object),
					  GQ_EXPORT(object)->filetype_box);
	gtk_widget_show_all(GQ_EXPORT(object)->filetype_box);
	return object;
}

static void
export_finalize(GObject* object)
{
	g_list_foreach(GQ_EXPORT(object)->to_export, (GFunc)g_object_unref, NULL);
	g_list_free(GQ_EXPORT(object)->to_export);
	GQ_EXPORT(object)->to_export = NULL;

	G_OBJECT_CLASS(gq_export_parent_class)->finalize(object);
}

static void
gq_export_class_init(GqExportClass* self_class)
{
	GObjectClass* object_class = G_OBJECT_CLASS(self_class);

	object_class->constructor = export_constructor;
	object_class->finalize    = export_finalize;
}

static void
dump_subtree_ok_callback(GqExport* ex)
{
	GtkTreeIter iter;
	GqLdifType  type;
     LDAPMessage *res = NULL, *e;
     LDAP *ld = NULL;
     GList *I;
     int num_entries;
     const char *filename;
     FILE *outfile = NULL;
     GString *out = NULL;
     GString *gmessage = NULL;
     size_t written;
     int ctx;
     GqServer *last = NULL;

     out = g_string_sized_new(2048);

     ctx = error_new_context(_("Dump subtree"), GTK_WIDGET(gtk_window_get_transient_for(GTK_WINDOW(ex))));

     if(g_list_length(ex->to_export) == 0) {
	  error_push(ctx, _("Nothing to dump!"));
#warning "FIXME: isn't this some kind of late?"
	  goto fail;
     }

     set_busycursor();

	/* obtain filename and open file for reading */
	filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(ex));

	gtk_combo_box_get_active_iter(GTK_COMBO_BOX(GQ_EXPORT(ex)->filetype_combo),
				      &iter);
	gtk_tree_model_get(gtk_combo_box_get_model(GTK_COMBO_BOX(GQ_EXPORT(ex)->filetype_combo)),
			   &iter,
			   0, &type,
			   -1);

     if( (outfile = fopen(filename, "w")) == NULL) {
	  error_push(ctx, _("Could not open output file '%1$s': %2$s"),
		     filename, strerror(errno));

	  goto fail;
     } else {
	  /* AFAIK, the UMich LDIF format doesn't take comments or a
             version string */
	  if (type != LDIF_UMICH) {
	       g_string_truncate(out, 0);

	       prepend_ldif_header(out, ex->to_export);

	       written = fwrite(out->str, 1, out->len, outfile);
	       if(written != (size_t) out->len) {
		    error_push(ctx,
			       _("Save to '%3$s' failed: Only %1$d of %2$d bytes written"),
			       written, out->len, filename);
		    goto fail; /* sometimes goto is useful */
	       }
	  }

	  num_entries = 0;
	  gmessage = g_string_sized_new(256);
	  for (I = g_list_first(ex->to_export) ; I ; I = g_list_next(I)) {
	       GqServerDn *dos = I->data;

	       LDAPControl ct;
	       LDAPControl *ctrls[2] = { NULL, NULL } ;
	       char *attrs[] = {
		    LDAP_ALL_USER_ATTRIBUTES,
		    "ref",
		    NULL
	       };
	       int rc;

	       ct.ldctl_oid		= LDAP_CONTROL_MANAGEDSAIT;
	       ct.ldctl_value.bv_val	= NULL;
	       ct.ldctl_value.bv_len	= 0;
	       ct.ldctl_iscritical	= 1;

	       ctrls[0] = &ct;

	       statusbar_msg(_("Search on %s"), gq_server_dn_get_dn(dos));

	       if (last != gq_server_dn_get_server(dos)) {
		    if (last) {
			 close_connection(last, FALSE);
			 last = NULL;
		    }

		    if( (ld = open_connection(ctx, gq_server_dn_get_server(dos))) == NULL) {
			 /* no extra error, open_connection does error
			    reporting itself... */
			 goto fail;
		    }

		    last = gq_server_dn_get_server(dos);
	       }

	       rc = ldap_search_ext_s(ld, gq_server_dn_get_dn(dos),
				      dos->flags == LDAP_SCOPE_SUBTREE ? LDAP_SCOPE_SUBTREE : LDAP_SCOPE_BASE,
				      "(objectClass=*)",
				      attrs,
				      0,
				      ctrls,		/* serverctrls */
				      NULL,		/* clientctrls */
				      NULL,		/* timeout */
				      LDAP_NO_LIMIT,	/* sizelimit */
				      &res);

	       if(rc == LDAP_NOT_SUPPORTED) {
		    rc = ldap_search_s(ld, gq_server_dn_get_dn(dos),
				       dos->flags == LDAP_SCOPE_SUBTREE ? LDAP_SCOPE_SUBTREE : LDAP_SCOPE_BASE,
				       "(objectClass=*)",
				       attrs, 0, &res);
	       }

	       if (rc == LDAP_SUCCESS) {
		    for(e = ldap_first_entry(ld, res); e; e = ldap_next_entry(ld, e)) {
			 g_string_truncate(out, 0);
			 ldif_entry_out(out, ld, e, ctx);
			 num_entries++;

			 written = fwrite(out->str, 1, out->len, outfile);
			 if(written != (size_t) out->len) {
			      g_string_sprintf(gmessage,
					       _("%1$d of %2$d bytes written"),
					       written, out->len);
			      error_popup(_("Save failed"), gmessage->str,
					  GTK_WIDGET(gtk_window_get_transient_for(GTK_WINDOW(ex))));

			      ldap_msgfree(res);
			      goto fail;
			 }

		    }
		    ldap_msgfree(res);
	       } else if (rc == LDAP_SERVER_DOWN) {
		    gq_server_dn_get_server(dos)->server_down++;

		    error_push(ctx,
			       _("Server '%s' down. Export may be incomplete!"),
			       gq_server_get_name(gq_server_dn_get_server(dos)));
		    push_ldap_addl_error(ld, ctx);
		    goto fail;
	       } else {
		    /* report error */
		    error_push(ctx,
			       _("LDAP error while searching below '%s'."
				 " Export may be incomplete!"),
			       gq_server_dn_get_dn(dos));
		    push_ldap_addl_error(ld, ctx);
		    goto fail;
	       }
	  }

	  statusbar_msg(ngettext("%1$d entry exported to %2$s",
				 "%1$d entries exported to %2$s", num_entries),
			num_entries, filename);

#warning "FIXME: store the last used type in the config file"
#warning "FIXME: store the last used location in the config file"
     }

 fail:		/* labels are only good for cleaning up, really */
     if (outfile) fclose(outfile);

     set_normalcursor();
     if (out) g_string_free(out, TRUE);
     if (gmessage) g_string_free(gmessage, TRUE);
     if (ld && last) close_connection(last, FALSE);

     error_flush(ctx);
}

void
gq_export_server_dns(int        error_context G_GNUC_UNUSED,
		     GtkWindow* parent,
		     GList    * to_export)
{
#warning "FIXME: provide a good default file name"
	GqExport* ex = g_object_new(GQ_TYPE_EXPORT, NULL);
	gboolean finished = FALSE;
	gtk_window_set_transient_for(GTK_WINDOW(ex), parent);

	ex->to_export = to_export;

	g_signal_connect_swapped(ex, "key-press-event",
				 G_CALLBACK(close_on_esc), ex);

	do {
		switch(gtk_dialog_run(GTK_DIALOG(ex))) {
		case GTK_RESPONSE_CANCEL:
		case GTK_RESPONSE_DELETE_EVENT:
			finished = TRUE;
			break;
		case GTK_RESPONSE_ACCEPT:
			dump_subtree_ok_callback(ex);
			finished = TRUE;
			break;
		default:
			g_assert_not_reached();
		}
	} while(!finished);

	gtk_widget_destroy(GTK_WIDGET(ex));
}

