/*
 *      win32.c - this file is part of Geany, a fast and lightweight IDE
 *
 *      Copyright 2005-2007 Enrico Tröger <enrico.troeger@uvena.de>
 *      Copyright 2006-2007 Nick Treleaven <nick.treleaven@btinternet.com>
 *
 *      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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 *
 *  $Id$
 */

/*
 * Special functions for the win32 platform, to provide native dialogs.
 */

#include "geany.h"

#ifdef G_OS_WIN32

#define VC_EXTRALEAN
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <commdlg.h>
#include <shlobj.h>

#include <string.h>
#include <ctype.h>
#include <math.h>
#include <stdlib.h>

#include <gdk/gdkwin32.h>

#include "win32.h"

#include "document.h"
#include "support.h"
#include "utils.h"
#include "ui_utils.h"
#include "sciwrappers.h"
#include "dialogs.h"
#include "filetypes.h"



static gchar *win32_get_file_filters()
{
	gchar *string;
	gint i, j, len;

	GString *str = g_string_sized_new(100);
	GString *all_patterns = g_string_sized_new(100);
	gchar *tmp;

	for (i = 0; i < GEANY_MAX_FILE_TYPES; i++)
	{
		tmp = g_strjoinv(";", filetypes[i]->pattern);
		g_string_append_printf(str, "%s\t%s\t", filetypes[i]->title, tmp);
		g_free(tmp);
	}
	// create meta file filter "All Source"
	for (i = 0; i < GEANY_MAX_FILE_TYPES; i++)
	{
		for (j = 0; filetypes[i]->pattern[j] != NULL; j++)
		{
			g_string_append(all_patterns, filetypes[i]->pattern[j]);
			g_string_append_c(all_patterns, ';');
		}
	}
	g_string_append_printf(str, "%s\t%s\t", _("All Source"), all_patterns->str);
	g_string_free(all_patterns, TRUE);

	g_string_append_c(str, '\t'); // the final \0 byte to mark the end of the string
	string = str->str;
	g_string_free(str, FALSE);

	// replace all "\t"s by \0
	len = strlen(string);
	for(i = 0; i < len; i++)
	{
		if (string[i] == '\t') string[i] = '\0';
	}
	return string;
}


static gchar *win32_get_filters(gboolean exe)
{
	gchar *string;
	gint i, len;

	if (exe)
	{
		string = g_strconcat(_("Executables"), "\t", "*.exe;*.bat;*.cmd", "\t",
			filetypes[GEANY_FILETYPES_ALL]->title, "\t",
			filetypes[GEANY_FILETYPES_ALL]->pattern[0], "\t", NULL);
	}
	else
	{
		string = g_strconcat(_("Geany project files"), "\t", "*." GEANY_PROJECT_EXT, "\t",
			filetypes[GEANY_FILETYPES_ALL]->title, "\t",
			filetypes[GEANY_FILETYPES_ALL]->pattern[0], "\t", NULL);
	}

	// replace all "\t"s by \0
	len = strlen(string);
	for(i = 0; i < len; i++)
	{
		if (string[i] == '\t') string[i] = '\0';
	}
	return string;
}


/* Returns the directory part of the given filename. */
static gchar *get_dir(const gchar *filename)
{
	if (! g_file_test(filename, G_FILE_TEST_IS_DIR))
		return g_path_get_dirname(filename);
	else
		return g_strdup(filename);
}


/* Callback function for setting the initial directory of the folder open dialog. This could also
 * be done with BROWSEINFO.pidlRoot and SHParseDisplayName but SHParseDisplayName is not available
 * on systems below Windows XP. So, we go the hard way by creating a callback which will set up the
 * folder when the dialog is initialised. Yeah, I like Windows. */
INT CALLBACK BrowseCallbackProc(HWND hwnd, UINT uMsg, LPARAM lp, LPARAM pData)
{
	switch(uMsg)
	{
		case BFFM_INITIALIZED:
			SendMessage(hwnd, BFFM_SETSELECTION, TRUE, (LPARAM) pData);
			break;

		case BFFM_SELCHANGED:
		{
			// set the status window to the currently selected path.
			static TCHAR szDir[MAX_PATH];
			if (SHGetPathFromIDList((LPITEMIDLIST) lp, szDir))
			{
				SendMessage(hwnd, BFFM_SETSTATUSTEXT, 0, (LPARAM) szDir);
			}
			break;
		}
	}
	return 0;
}


/* Shows a folder selection dialog.
 * The selected folder name is returned. */
gchar *win32_show_project_folder_dialog(const gchar *title, const gchar *initial_dir)
{
	BROWSEINFO bi;
	LPCITEMIDLIST pidl;
	gchar *fname = g_malloc(MAX_PATH);
	gchar *dir = get_dir(initial_dir);

	memset(&bi, 0, sizeof bi);
	bi.hwndOwner = GDK_WINDOW_HWND(app->window->window);
	bi.pidlRoot = NULL;
	bi.lpszTitle = title;
	bi.lpfn = BrowseCallbackProc;
	bi.lParam = (LPARAM) dir;
	bi.ulFlags = BIF_DONTGOBELOWDOMAIN | BIF_RETURNONLYFSDIRS | BIF_STATUSTEXT;

	pidl = SHBrowseForFolder(&bi);
	g_free(dir);

	// convert the strange Windows folder list item something into an usual path string ;-)
	if (pidl != NULL && SHGetPathFromIDList(pidl, fname))
		return fname;
	else
	{
		g_free(fname);
		return NULL;
	}
}


/* Shows a file open dialog.
 * If allow_new_file is set, the file to be opened doesn't have to exist.
 * The selected file name is returned. */
gchar *win32_show_project_open_dialog(const gchar *title, const gchar *initial_dir, gboolean allow_new_file)
{
	OPENFILENAME of;
	gint retval;
	gchar *fname = g_malloc(2048);
	gchar *filters = win32_get_filters(FALSE);
	gchar *dir = get_dir(initial_dir);

	fname[0] = '\0';

	/* initialise file dialog info struct */
	memset(&of, 0, sizeof of);
#ifdef OPENFILENAME_SIZE_VERSION_400
	of.lStructSize = OPENFILENAME_SIZE_VERSION_400;
#else
	of.lStructSize = sizeof of;
#endif
	of.hwndOwner = GDK_WINDOW_HWND(app->window->window);
	of.lpstrFilter = filters;

	of.lpstrCustomFilter = NULL;
	of.nFilterIndex = 0;
	of.lpstrFile = fname;
	of.lpstrInitialDir = dir;
	of.nMaxFile = 2048;
	of.lpstrFileTitle = NULL;
	of.lpstrTitle = title;
	of.lpstrDefExt = "";
	of.Flags = OFN_PATHMUSTEXIST | OFN_EXPLORER | OFN_HIDEREADONLY;
	if (! allow_new_file)
		of.Flags |= OFN_FILEMUSTEXIST;

	retval = GetOpenFileName(&of);

	g_free(dir);
	g_free(filters);

	if (! retval)
	{
		if (CommDlgExtendedError())
		{
			gchar *error;
			error = g_strdup_printf("File dialog box error (%x)", (int)CommDlgExtendedError());
			win32_message_dialog(NULL, GTK_MESSAGE_ERROR, error);
			g_free(error);
		}
		g_free(fname);
		return NULL;
	}

	return fname;
}


// return TRUE if the dialog was not cancelled.
gboolean win32_show_file_dialog(gboolean file_open)
{
	OPENFILENAME of;
	gint retval;
	gchar *fname = g_malloc(2048);
	gchar *current_dir = utils_get_current_file_dir();
	gchar *filters = win32_get_file_filters();

	fname[0] = '\0';

	/* initialize file dialog info struct */
	memset(&of, 0, sizeof of);
#ifdef OPENFILENAME_SIZE_VERSION_400
	of.lStructSize = OPENFILENAME_SIZE_VERSION_400;
#else
	of.lStructSize = sizeof of;
#endif
	of.hwndOwner = GDK_WINDOW_HWND(app->window->window);
	of.lpstrFilter = filters;

	of.lpstrCustomFilter = NULL;
	of.nFilterIndex = GEANY_FILETYPES_ALL + 1;
	of.lpstrFile = fname;
	of.lpstrInitialDir  = current_dir;
	of.nMaxFile = 2048;
	of.lpstrFileTitle = NULL;
	of.lpstrTitle = NULL;
	of.lpstrDefExt = "";
	if (file_open)
	{
		of.Flags = OFN_ALLOWMULTISELECT | OFN_FILEMUSTEXIST | OFN_EXPLORER;
		retval = GetOpenFileName(&of);
	}
	else
	{
		of.Flags = OFN_OVERWRITEPROMPT | OFN_PATHMUSTEXIST;
		retval = GetSaveFileName(&of);
	}

	g_free(current_dir);
	g_free(filters);

	if (!retval)
	{
		if (CommDlgExtendedError())
		{
			gchar error[100];
			snprintf(error, sizeof error, "File dialog box error (%x)", (int)CommDlgExtendedError());
			win32_message_dialog(NULL, GTK_MESSAGE_ERROR, error);
		}
		g_free(fname);
		return FALSE;
	}

	if (file_open)
	{
		gchar file_name[255];
		gint x;

		x = of.nFileOffset - 1;
		if (x != strlen(fname))
		{	// open a single file
			document_open_file(fname, of.Flags & OFN_READONLY, NULL, NULL);
		}
		else
		{	// open multiple files
			document_delay_colourise();

			for (; ;)
			{
				if (! fname[x])
				{
					if (! fname[x+1]) break;

					g_snprintf(file_name, 254, "%s\\%s", fname, fname + x + 1);
					document_open_file(file_name, of.Flags & OFN_READONLY, NULL, NULL);
				}
				x++;
			}
			document_colourise_new();
		}
	}
	else
	{
		gint idx = document_get_cur_idx();
		doc_list[idx].file_name = g_strdup(fname);
		document_save_file(idx, TRUE);
	}
	g_free(fname);
	return (retval != 0);
}


void win32_show_font_dialog(void)
{
	CHOOSEFONT cf;
	gint retval;
	static LOGFONT lf;        // logical font structure

	memset(&cf, 0, sizeof cf);
	cf.lStructSize = sizeof cf;
	cf.hwndOwner = GDK_WINDOW_HWND(app->window->window);
	cf.lpLogFont = &lf;
	cf.Flags = CF_APPLY | CF_NOSCRIPTSEL | CF_FORCEFONTEXIST | CF_INITTOLOGFONTSTRUCT | CF_SCREENFONTS;

	retval = ChooseFont(&cf);

	if (!retval)
	{
		if (CommDlgExtendedError())
		{
			//gchar error[100];
			//snprintf(error, 255, _("Font dialog box error (%x)"), (int)CommDlgExtendedError());
			//MessageBox(NULL, "Font not availab", _("Error"), MB_OK | MB_ICONERROR);
		}
		return;
	}
	else
	{
			gchar *editorfont = g_strdup_printf("%s %d", lf.lfFaceName,
				(cf.iPointSize / 10));
			ui_set_editor_font(editorfont);
			g_free(editorfont);
	}
}


void win32_show_color_dialog(const gchar *colour)
{
	CHOOSECOLOR cc;
	static COLORREF acr_cust_clr[16];
	static DWORD rgb_current;
	gchar *hex = g_malloc0(12);
	gint idx = document_get_cur_idx();

	// Initialize CHOOSECOLOR
	memset(&cc, 0, sizeof cc);
	cc.lStructSize = sizeof(cc);
	cc.hwndOwner = GDK_WINDOW_HWND(app->window->window);
	cc.lpCustColors = (LPDWORD) acr_cust_clr;
	cc.rgbResult = (colour != NULL) ? utils_strtod(colour, NULL, colour[0] == '#') : 0;
	cc.Flags = CC_FULLOPEN | CC_RGBINIT;

	if (ChooseColor(&cc))
	{
		rgb_current = cc.rgbResult;
		g_snprintf(hex, 11, "#%02X%02X%02X",
	      (guint) (utils_scale_round(GetRValue(rgb_current), 255)),
	      (guint) (utils_scale_round(GetGValue(rgb_current), 255)),
	      (guint) (utils_scale_round(GetBValue(rgb_current), 255)));

		document_insert_colour(idx, hex);
	}
	g_free(hex);
}


void win32_show_pref_file_dialog(GtkEntry *item)
{
	OPENFILENAME of;
	gint retval;
	gchar *fname = g_malloc(512);
	gchar **field, *filename, *tmp;
	gchar *filters = win32_get_filters(TRUE);

	fname[0] = '\0';

	// cut the options from the command line
	field = g_strsplit(gtk_entry_get_text(GTK_ENTRY(item)), " ", 2);
	if (field[0] && g_file_test(field[0], G_FILE_TEST_EXISTS))
	{
		filename = g_find_program_in_path(field[0]);
		strcpy(fname, filename);
		g_free(filename);
	}

	/* initialize file dialog info struct */
	memset(&of, 0, sizeof of);
#ifdef OPENFILENAME_SIZE_VERSION_400
	of.lStructSize = OPENFILENAME_SIZE_VERSION_400;
#else
	of.lStructSize = sizeof of;
#endif
	of.hwndOwner = GDK_WINDOW_HWND(app->window->window);

	of.lpstrFilter = filters;
	of.lpstrCustomFilter = NULL;
	of.nFilterIndex = 1;

	of.lpstrFile = fname;
	of.nMaxFile = 512;
	of.lpstrFileTitle = NULL;
	//of.lpstrInitialDir = g_get_home_dir();
	of.lpstrInitialDir = NULL;
	of.lpstrTitle = NULL;
	of.lpstrDefExt = "exe";
	of.Flags = OFN_ALLOWMULTISELECT | OFN_HIDEREADONLY | OFN_FILEMUSTEXIST | OFN_EXPLORER;
	retval = GetOpenFileName(&of);

	g_free(filters);

	if (!retval)
	{
		if (CommDlgExtendedError())
		{
			gchar error[100];
			snprintf(error, sizeof error, "File dialog box error (%x)", (int)CommDlgExtendedError());
			win32_message_dialog(NULL, GTK_MESSAGE_ERROR, error);
		}
		g_strfreev(field);
		g_free(fname);
		return;
	}

	if ((of.nFileOffset - 1) != strlen(fname))
	{
		tmp = g_strdup(fname);
		if (g_strv_length(field) > 1)
			// haha, pfad- und dateinamen mit leerzeichen??
			filename = g_strconcat(tmp, " ", field[1], NULL);
		else
		{
			filename = tmp;
			tmp = NULL;
		}
		gtk_entry_set_text(GTK_ENTRY(item), filename);
		g_free(filename);
		g_free(tmp);
	}
	g_strfreev(field);
	g_free(fname);
}


/* Creates a native Windows message box of the given type and returns always TRUE
 * or FALSE representing th pressed Yes or No button.
 * If type is not GTK_MESSAGE_QUESTION, it returns always TRUE. */
gboolean win32_message_dialog(GtkWidget *parent, GtkMessageType type, const gchar *msg)
{
	gboolean ret = TRUE;
	gint rc;
	guint t;
	const gchar *title;
	static wchar_t w_msg[512];
	static wchar_t w_title[512];

	switch (type)
	{
		case GTK_MESSAGE_ERROR:
		{
			t = MB_OK | MB_ICONERROR;
			title = _("Error");
			break;
		}
		case GTK_MESSAGE_QUESTION:
		{
			t = MB_YESNO | MB_ICONQUESTION;
			title = _("Question");
			break;
		}
		case GTK_MESSAGE_WARNING:
		{
			t = MB_OK | MB_ICONWARNING;
			title = _("Warning");
			break;
		}
		default:
		{
			t = MB_OK | MB_ICONINFORMATION;
			title = _("Information");
			break;
		}
	}

	// convert the Unicode chars to wide chars
	/// TODO test if LANG == C then possibly skip conversion => g_win32_getlocale()
	MultiByteToWideChar(CP_UTF8, 0, msg, -1, w_msg, G_N_ELEMENTS(w_msg));
	MultiByteToWideChar(CP_UTF8, 0, title, -1, w_title, G_N_ELEMENTS(w_title));

	// display the message box
	if (parent == NULL)
		parent = app->window;

	rc = MessageBoxW(GDK_WINDOW_HWND(parent->window), w_msg, w_title, t);

	if (type == GTK_MESSAGE_QUESTION && rc != IDYES)
		ret = FALSE;

	return ret;
}


/* Special dialog to ask for an action when closing an unsaved file */
gint win32_message_dialog_unsaved(const gchar *msg)
{
	static wchar_t w_msg[512];
	static wchar_t w_title[512];
	gint ret;

	// convert the Unicode chars to wide chars
	MultiByteToWideChar(CP_UTF8, 0, msg, -1, w_msg, G_N_ELEMENTS(w_msg));
	MultiByteToWideChar(CP_UTF8, 0, _("Question"), -1, w_title, G_N_ELEMENTS(w_title));

	ret = MessageBoxW(GDK_WINDOW_HWND(app->window->window), w_msg, w_title, MB_YESNOCANCEL | MB_ICONQUESTION);
	switch(ret)
	{
		case IDYES: ret = GTK_RESPONSE_YES; break;
		case IDNO: ret = GTK_RESPONSE_NO; break;
		case IDCANCEL: ret = GTK_RESPONSE_CANCEL; break;
	}

	return ret;
}

/* Just a simple wrapper function to open a browser window */
void win32_open_browser(const gchar *uri)
{
	ShellExecute(NULL, "open", uri, NULL, NULL, SW_SHOWNORMAL);
}

#endif
