/* dnd.c
 * Drag and Drop
 *
 * $Id: drag_and_drop.c 15056 2005-07-25 18:02:32Z ulfl $
 *
 * Ethereal - Network traffic analyzer
 * By Gerald Combs <gerald@ethereal.com>
 * Copyright 1998 Gerald Combs
 *
 * 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.
 */

#ifdef HAVE_CONFIG_H
# include "config.h"
#endif

#ifdef HAVE_IO_H
#include <io.h> /* open/close on win32 */
#endif

#ifdef HAVE_LIBPCAP
#include <pcap.h>
#endif

#include <gtk/gtk.h>

#include "globals.h"

#include "gtkglobals.h"
#include "util.h"
#include "file_dlg.h"
#include "../menu.h"
#include "compat_macros.h"
#include "file.h"
#include "simple_dialog.h"
#include "main.h"
#ifdef HAVE_LIBPCAP
#include "capture.h"
#endif
#include <epan/prefs.h>

#include <string.h>
#include <stdio.h>


enum { DND_TARGET_STRING, DND_TARGET_ROOTWIN, DND_TARGET_URL };

/* convert drag and drop URI to a local filename */
static gchar *
dnd_uri2filename(gchar *cf_name)
{
    gchar     *src, *dest;
    gint      ret;
    guint     i;
    gchar     esc[3];


    /* Remove URI header.
     * we have to remove the prefix to get a valid filename. */
#ifdef _WIN32
    /*
     * On win32 (at least WinXP), this prefix looks like (UNC):
     * file:////servername/sharename/dir1/dir2/capture-file.cap
     * or (local filename):
     * file:///d:/dir1/dir2/capture-file.cap
     */
    if (strncmp("file:////", cf_name, 9) == 0) {
        /* win32 UNC: now becoming: //servername/sharename/dir1/dir2/capture-file.cap */
        cf_name += 7;
    } else if (strncmp("file:///", cf_name, 8) == 0) {
        /* win32 local: now becoming: d:/dir1/dir2/capture-file.cap */
        cf_name += 8;
    }
#else
    /*
     * On UNIX (at least KDE 3.0 Konqueror), this prefix looks like:
     * file:/dir1/dir2/capture-file.cap
     *
     * On UNIX (at least GNOME Nautilus 2.8.2), this prefix looks like:
     * file:///dir1/dir2/capture-file.cap
     */ 
    if (strncmp("file:", cf_name, 5) == 0) {
        /* now becoming: /dir1/dir2/capture-file.cap or ///dir1/dir2/capture-file.cap */
        cf_name += 5;
        /* shorten //////thing to /thing */
        for(; cf_name[1] == '/'; ++cf_name);
    }
#endif

    /* 
     * unescape the escaped URI characters (spaces, ...)
     *
     * we have to replace escaped chars to their equivalents, 
     * e.g. %20 (always a two digit hexstring) -> ' '
     * the percent character '%' is escaped be a double one "%%"
     *
     * we do this conversation "in place" as the result is always 
     * equal or smaller in size.
     */
    src = cf_name;
    dest = cf_name;
    while (*src) {
        if (*src == '%') {
            src++;
            if (*src == '%') {
                /* this is an escaped '%' char (was: "%%") */
                *dest = *src;
                src++;
                dest++;
            } else {
                /* convert escaped hexnumber to unscaped character */
                esc[0] = src[0];
                esc[1] = src[1];
                esc[2] = '\0';
                ret = sscanf(esc, "%x", &i);
                if (ret == 1) {
                    src+=2;
                    *dest = (gchar) i;
                    dest++;
                } else {
                    /* somethings wrong, just jump over that char
                     * this will result in a wrong string, but we might get
                     * user feedback and can fix it later ;-) */
                    src++;
                }
            }
        } else {
            *dest = *src;
            src++;
            dest++;
        }
    }
    *dest = '\0';

    return cf_name;
}

static void
dnd_merge_files(int in_file_count, char **in_filenames)
{
    char *tmpname;
    cf_status_t merge_status;
    int err;

    /* merge the files in chonological order */
    tmpname = NULL;
    merge_status = cf_merge_files(&tmpname, in_file_count, in_filenames,
                              WTAP_FILE_PCAP, FALSE);

    if (merge_status != CF_OK) {
        /* merge failed */
        g_free(tmpname);
	return;
    }

    cf_close(&cfile);

    /* Try to open the merged capture file. */
    if (cf_open(&cfile, tmpname, TRUE /* temporary file */, &err) != CF_OK) {
	/* We couldn't open it; don't dismiss the open dialog box,
	   just leave it around so that the user can, after they
	   dismiss the alert box popped up for the open error,
	   try again. */
	g_free(tmpname);
	return;
    }
    g_free(tmpname);

    switch (cf_read(&cfile)) {

    case CF_READ_OK:
    case CF_READ_ERROR:
	/* Just because we got an error, that doesn't mean we were unable
	   to read any of the file; we handle what we could get from the
	   file. */
	break;

    case CF_READ_ABORTED:
	/* The user bailed out of re-reading the capture file; the
	   capture file has been closed - just free the capture file name
	   string and return (without changing the last containing
	   directory). */
	return;
    }

    gtk_widget_grab_focus(packet_list);
}

/* open/merge the dnd file */
void
dnd_open_file_cmd(gchar *cf_names_freeme)
{
    int       err;
    gchar     *cf_name;
    int       in_files;
    gpointer  dialog;
    GString   *dialog_text;
    int       files_work;
    char      **in_filenames;

    
    /* DND_TARGET_URL on Win32:
     * The cf_name_freeme is a single string, containing one or more URI's,
     * seperated by CR/NL chars. The length of the whole field can be found 
     * in the selection_data->length field. If it contains one file, simply open it,
     * If it contains more than one file, ask to merge these files. */

    /* count the number of input files */
    cf_name = cf_names_freeme;
    for(in_files = 0; (cf_name = strstr(cf_name, "\r\n")) != NULL; ) {
        cf_name += 2;
        in_files++;
    }

    in_filenames = g_malloc(sizeof(char*) * in_files);

    /* store the starts of the file entries in a gchar array */
    cf_name = cf_names_freeme;
    in_filenames[0] = cf_name;
    for(files_work = 1; (cf_name = strstr(cf_name, "\r\n")) != NULL && files_work < in_files; ) {
        cf_name += 2;
        in_filenames[files_work] = cf_name;
        files_work++;
    }

    /* replace trailing CR NL simply with zeroes (in place), so we get valid terminated strings */
    cf_name = cf_names_freeme;
    g_strdelimit(cf_name, "\r\n", '\0');

    /* convert all filenames from URI to local filename (in place) */
    for(files_work = 0; files_work < in_files; files_work++) {
        in_filenames[files_work] = dnd_uri2filename(in_filenames[files_work]);
    }

    switch(in_files) {
    case(0):
        /* shouldn't happen */
        break;
    case(1):
        /* open and read the capture file (this will close an existing file) */
        if (cf_open(&cfile, in_filenames[0], FALSE, &err) == CF_OK) {
          /* XXX - add this to the menu if the read fails? */
          cf_read(&cfile);
          add_menu_recent_capture_file(in_filenames[0]);
	} else {
          /* the capture file couldn't be read (doesn't exist, file format unknown, ...) */
	}
        break;
    default:
        /* build and show the info dialog */
        dialog_text = g_string_sized_new(200);
        g_string_append(dialog_text, PRIMARY_TEXT_START 
            "Merging the following files:" PRIMARY_TEXT_END "\n\n");
        for(files_work = 0; files_work < in_files; files_work++) {
            g_string_append(dialog_text, in_filenames[files_work]);
            g_string_append(dialog_text, "\n");
        }
        g_string_append(dialog_text, "\nThe packets in these files will be merged chronologically into a new temporary file.");
        dialog = simple_dialog(ESD_TYPE_CONFIRMATION,
                    ESD_BTN_OK,
                    dialog_text->str);
        g_string_free(dialog_text, TRUE);

        /* actually merge the files now */
        dnd_merge_files(in_files, in_filenames);
    }

    g_free(in_filenames);
    g_free(cf_names_freeme);
}

/* ask the user to save current unsaved file, before opening the dnd file */
static void 
dnd_save_file_answered_cb(gpointer dialog _U_, gint btn, gpointer data)
{
    switch(btn) {
    case(ESD_BTN_SAVE):
        /* save file first */
        file_save_as_cmd(after_save_open_dnd_file, data);
        break;
    case(ESD_BTN_DONT_SAVE):
        cf_close(&cfile);
        dnd_open_file_cmd(data);
        break;
    case(ESD_BTN_CANCEL):
        break;
    default:
        g_assert_not_reached();
    }
}


/* we have received some drag and drop data */
/* (as we only registered to "text/uri-list", we will only get a file list here) */
static void 
dnd_data_received(GtkWidget *widget _U_, GdkDragContext *dc _U_, gint x _U_, gint y _U_, 
GtkSelectionData *selection_data, guint info, guint t _U_, gpointer data _U_)
{
    gpointer  dialog;
	gchar *cf_names_freeme;

    if (info == DND_TARGET_URL) {
        /* Usually we block incoming events by disabling the corresponding menu/toolbar items. 
         * This is the only place where an incoming event won't be blocked in such a way, 
         * so we have to take care of NOT loading a new file while a different process 
         * (e.g. capture/load/...) is still in progress. */

#ifdef HAVE_LIBPCAP
        /* if a capture is running, do nothing but warn the user */
        if((capture_opts->state != CAPTURE_STOPPED)) {
            dialog = simple_dialog(ESD_TYPE_CONFIRMATION,
                        ESD_BTN_OK,
                        PRIMARY_TEXT_START "Drag and Drop currently not possible!" PRIMARY_TEXT_END "\n\n"
                        "Dropping a file isn't possible while a capture is in progress.");
            return;
        }
#endif

        /* if another file read is still in progress, do nothing but warn the user */
        if((cfile.state == FILE_READ_IN_PROGRESS)) {
            dialog = simple_dialog(ESD_TYPE_CONFIRMATION,
                        ESD_BTN_OK,
                        PRIMARY_TEXT_START "Drag and Drop currently not possible!" PRIMARY_TEXT_END "\n\n"
                        "Dropping a file isn't possible while loading another capture file.");
            return;
        }

		/* the selection_data will soon be gone, make a copy first */
		/* the data string is not zero terminated -> make a zero terminated "copy" of it */
		cf_names_freeme = g_malloc(selection_data->length + 1);
		memcpy(cf_names_freeme, selection_data->data, selection_data->length);
		cf_names_freeme[selection_data->length] = '\0';

        /* ask the user to save it's current capture file first */
        if((cfile.state != FILE_CLOSED) && !cfile.user_saved && prefs.gui_ask_unsaved) {
            /* user didn't saved his current file, ask him */
            dialog = simple_dialog(ESD_TYPE_CONFIRMATION,
                        ESD_BTNS_SAVE_DONTSAVE_CANCEL,
                        PRIMARY_TEXT_START "Save capture file before opening a new one?" PRIMARY_TEXT_END "\n\n"
                        "If you open a new capture file without saving, your current capture data will be discarded.");
            simple_dialog_set_cb(dialog, dnd_save_file_answered_cb, cf_names_freeme );
        } else {
            /* unchanged file */
            dnd_open_file_cmd( cf_names_freeme );
        }
    }
}

/* init the drag and drop functionality */
void 
dnd_init(GtkWidget *w)
{
    /* we are only interested in the URI list containing filenames */
    static GtkTargetEntry target_entry[] = {
         /*{"STRING", 0, DND_TARGET_STRING},*/
         /*{"text/plain", 0, DND_TARGET_STRING},*/
         {"text/uri-list", 0, DND_TARGET_URL}
    };

    /* set this window as a dnd destination */
    gtk_drag_dest_set(
         w, GTK_DEST_DEFAULT_ALL, target_entry,
         sizeof(target_entry) / sizeof(GtkTargetEntry),
         (GdkDragAction)(GDK_ACTION_MOVE | GDK_ACTION_COPY) );

    /* get notified, if some dnd coming in */
    gtk_signal_connect(GTK_OBJECT(w), "drag_data_received",
        GTK_SIGNAL_FUNC(dnd_data_received), NULL);
}


