/* Bluefish HTML Editor
 * bluefish.c - main source file
 * contains main function and some general often used functions
 *
 * Copyright (C) 1998 Olivier Sessink and Chris Mazuc
 *
 * 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 "default_include.h"

#include <unistd.h> /* getuid */
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/types.h>  /* msgsnd() */

#ifdef HAVE_SYS_IPC_H  /* on win 32 we don't have it */
#include <sys/ipc.h>  /* msgsnd() */
#include <sys/msg.h>  /* msgsnd() */
#endif

#include <errno.h> 	/* errno */

#if HAVE_LIBGDK_IMLIB
#include <gdk_imlib.h>
#endif

#include "bluefish.h"
#include "bf_lib.h"
#include "init.h"				/* parse commandline */
#include "gtk_easy.h"			/* window_destroy */
#include "interface.h"
#include "document.h" 			/* file_close_all_cb() */
#include "project.h" 			/* project_close() */
#include "callbacks.h"
#include "stringlist.h" 	/* duplicate_stringarray() */

Tmain *main_v;

/****************************************************************************/

void cya_later(GtkWidget * widget, gpointer data);
int main(gint argc, gchar * argv[]);
void window_close(GtkWidget * button, gpointer windowname);
void flush_queue(void);

/****************************************************************************/

void cleanup(void) {

#ifdef HAVE_SYS_IPC_H
#ifdef WITH_MSG_QUEUE
	msgctl(main_v->msgid, IPC_RMID, 0);
#endif /* WITH_MSG_QUEUE */
#endif

}

void cya_later(GtkWidget * widget, gpointer data)
{
	DEBUG_MSG("cya_later, calling project_close_cb(NULL, NULL);\n");
	project_close_cb(NULL, NULL);
	DEBUG_MSG("cya_later, calling file_close_all_cb(NULL, NULL);\n");
	file_close_all_cb(NULL, NULL);
	if ((g_list_length(main_v->documentlist) == 1)
		&& (main_v->current_document->modified == 0)
		&& (main_v->current_document->filename == NULL)
		&& (gtk_text_get_length(GTK_TEXT(main_v->current_document->textbox)) == 0)) {
		DEBUG_MSG("cya_later, doesn't matter, 1 empty not modified file without filename\n");
	} else {
		DEBUG_MSG("cya_later, not all documents are closed, NO EXIT\n");
		return;
	}
	gtk_main_quit();
}

#ifdef WITH_MSG_QUEUE

/* 
the message queue system is quite easy:

if there is no queue --> open it and start listening
on a MSG_QUEUE_OPENFILE, open the file, on a MSG_QUEUE_ASK_ALIVE return
the same data with type MSG_QUEUE_SEND_ALIVE (which we are not
listening for)

if there is a queue and there are files loaded on the commandline --> 
check the server (send a keepalive containing the pid) and send the 
files to the queue, after sending the files check if a 
MSG_QUEUE_SEND_ALIVE is received contaning the pid. If so, there is
a server and we can quit. If not, we continue starting and load them

if there is a queue and we do not have files loaded on the commandline
we just start, but we don't listen to the queue!
*/

gint msg_queue_open(void) {

#ifdef HAVE_SYS_IPC_H
	DEBUG_MSG("msg_queue_open, started\n");
	main_v->msgid = msgget((key_t) BLUEFISH_MSG_QUEUE + getuid(), 0666 | IPC_CREAT | IPC_EXCL);
#ifdef DEBUG
		if (main_v->msgid == -1) {
			DEBUG_MSG("msg_queue_open, errno=%d, error=%s\n", errno, g_strerror(errno));
		}
#endif
	/* if main_v->msgid == -1 the message queue was already opened by another process */
	DEBUG_MSG("msg_queue_open, main_v->msgid=%d\n", main_v->msgid);
	if (main_v->msgid == -1) {
		main_v->msgid = msgget((key_t) BLUEFISH_MSG_QUEUE+ getuid(), 0666);
		DEBUG_MSG("msg_queue_open, connected to existing message queue, id=%d\n", main_v->msgid);
#ifdef DEBUG
		if (main_v->msgid == -1) {
			DEBUG_MSG("msg_queue_open, errno=%d, error=%s\n", errno, g_strerror(errno));
		}
#endif
		return 1;
	}
#endif  
		return 0;
}

gint msg_queue_check(gpointer data) {
#ifdef HAVE_SYS_IPC_H
	struct msgbuf {
		long mtype;
		char mtext[MSQ_QUEUE_SIZE];
	} msgp;
	gint retval;

	if (main_v->msgid == -1) {
		return FALSE;
	}
/*	DEBUG_MSG("msg_queue_check, checking\n"); */

	retval = msgrcv(main_v->msgid, &msgp, MSQ_QUEUE_SIZE * sizeof(char), -MSG_QUEUE_OPENFILE, IPC_NOWAIT);
	if (retval != -1) {
		DEBUG_MSG("msg_queue_check, found type %ld\n", msgp.mtype);
		if (msgp.mtype == MSG_QUEUE_ASK_ALIVE) {
			struct small_msgbuf {
				long mtype;
				char mtext[MSQ_QUEUE_SMALL_SIZE];
			} small_msgp;
			DEBUG_MSG("msg_queue_check, a keepalive is asked from %s, sending!\n",msgp.mtext);
			small_msgp.mtype = MSG_QUEUE_SEND_ALIVE;
			strncpy(small_msgp.mtext,msgp.mtext, MSQ_QUEUE_SMALL_SIZE-1);	
			msgsnd(main_v->msgid, (void *)&small_msgp, MSQ_QUEUE_SMALL_SIZE * sizeof(char), IPC_NOWAIT);
		} else if (msgp.mtype == MSG_QUEUE_OPENFILE) {
			DEBUG_MSG("msg_queue_check, a filename %s is received\n", msgp.mtext);
			doc_new_with_file(msgp.mtext);
			msg_queue_check(NULL); /* call it again, there may have been multiple files */
		}
	} else {
		if (errno == 22) {
			DEBUG_MSG("msg_queue_check, re-opening message queue ?!?!?\n");
			/* the msg_queue was removed !?!?! */
			msg_queue_open();
		}
	}
#endif
	return TRUE;
}

gint msg_queue_check_alive(gchar *pid_string) {

#ifdef HAVE_SYS_IPC_H
	struct small_msgbuf {
		long mtype;
		char mtext[MSQ_QUEUE_SMALL_SIZE];
	} small_msgp;
	while (msgrcv(main_v->msgid, &small_msgp, MSQ_QUEUE_SMALL_SIZE*sizeof(char), MSG_QUEUE_SEND_ALIVE, IPC_NOWAIT) != -1) {
		DEBUG_MSG("msg_queue_check, received a keepalive, mtext=%s!\n", small_msgp.mtext);
		if (strncmp(pid_string, small_msgp.mtext, MSQ_QUEUE_SMALL_SIZE-1) ==0) {
			DEBUG_MSG("msg_queue_check, keepalive matches request!\n");
			/* we did receive a keepalive on our request !!*/
			return 1;
		} else {
			DEBUG_MSG("msg_queue_check, keepalive does NOT match request %s\n", pid_string);
		}
	}
	DEBUG_MSG("msg_queue_check, errno=%d, error=%s\n", errno, g_strerror(errno));
#endif
	return 0;
}

#endif /* WITH_MSG_QUEUE */

int main(gint argc, gchar * argv[])
{
#ifdef WITH_MSG_QUEUE
	gint other_bluefish=0, received_keepalive=0;
	gchar *pid_string=NULL;
#endif /* WITH_MSG_QUEUE */

#ifdef ENABLE_NLS
	/* init locale before any *_init */
	setlocale(LC_ALL, "");
	bindtextdomain(PACKAGE, LOCALEDIR);
	textdomain(PACKAGE);

	/* for XIM support we need to call gtk_set_locale() before gdk_init() */
	gtk_set_locale();
#endif
	gdk_init(&argc, &argv);
	gdk_rgb_init (); /* initialise GdkRgb */

#if HAVE_LIBGDK_IMLIB
	/* Immediately after initialising GDK, Initialise Imlib */
	gdk_imlib_init();
#endif

	gtk_init(&argc, &argv);

	parse_commandline(argc, argv);

	/* see if we're running as root, if so, exit with an error unless overriden */
#ifndef WIN32
	if (root_override != 1) {
		if (geteuid() == 0) {
			g_print(_("Error: cannot run as root (for security reasons)\n"));
			g_print(_("use -s to override\n"));
			/* effective uid == 0 and the real uid != 0 means it's suid root */
			/* give an additional message if this is the case */
			if (getuid() != 0) {
				g_print(_("Bluefish is SUID root, this is a security risk!\n"));
			}
			exit(EXIT_FAILURE);
		}
	}
#endif

	DEBUG_MSG("main, init main_v\n");
	/*initialise the main struct */
	main_v = g_malloc0(sizeof(Tmain));

	DEBUG_MSG("main, check files and parse config files\n");
	/* parse config files */
	check_files_links_directories();
	parse_bluefish_rcfile();

#ifdef HAVE_SYS_IPC_H
#ifdef WITH_MSG_QUEUE
	DEBUG_MSG("main (msg queue), open message queue\n");
	other_bluefish = msg_queue_open();
	if (other_bluefish) {
		struct small_msgbuf {
			long mtype;
			char mtext[MSQ_QUEUE_SMALL_SIZE];
		} small_msgp;
		gint ask_other_alive;
		/* if the message queue is still open and the process listening is killed
			we should be the server process --> we have to check this */
		pid_string = g_strdup_printf("%d", (int)getpid());
		DEBUG_MSG("main, asking for keepalive, string %s\n", pid_string);
		small_msgp.mtype = MSG_QUEUE_ASK_ALIVE;
		strncpy(small_msgp.mtext, pid_string, MSQ_QUEUE_SMALL_SIZE-1);
		ask_other_alive = msgsnd(main_v->msgid, (void *)&small_msgp, MSQ_QUEUE_SMALL_SIZE*sizeof(char), IPC_NOWAIT);
#ifdef DEBUG
		if (ask_other_alive == -1) {
			DEBUG_MSG("main (msg queue), errno=%d, error=%s\n", errno, g_strerror(errno));
		}
#endif
		if (!load_projectname && load_filenames && main_v->props.open_in_running_bluefish) {
			if (main_v->msgid != -1) {
				struct msgbuf {
					long mtype;
					char mtext[MSQ_QUEUE_SIZE];
				} msgp;
				gint success=1, check_keepalive_cnt=0, send_failure_cnt=0;
				GList *tmplist;
	
				/* we have a message queue now, opened by another bluefish process */
				msgp.mtype = MSG_QUEUE_OPENFILE;
				tmplist = g_list_first(load_filenames);
				while(tmplist && success) {
					gint retval;
					gint len = strlen((gchar *)tmplist->data);
					if (len < MSQ_QUEUE_SIZE-1) {
						strncpy(msgp.mtext, (gchar *)tmplist->data, MSQ_QUEUE_SIZE-1);
						retval = msgsnd(main_v->msgid, (void *)&msgp, MSQ_QUEUE_SIZE * sizeof(char), IPC_NOWAIT);
						if (retval == -1) {
							DEBUG_MSG("main (msg queue), failed sending, errno=%d, error=%s\n", errno, g_strerror(errno));
							usleep(MSG_QUEUE_PER_DOCUMENT_TIMEOUT * 1000);
							if (received_keepalive) {
								send_failure_cnt++;
							} else {
								if (msg_queue_check_alive(pid_string)) {
									received_keepalive = 1;
									DEBUG_MSG("main (msg queue), received keepalive\n");
								} else {
									check_keepalive_cnt++;
									DEBUG_MSG("main (msg queue), no keepalive (try %d)\n", check_keepalive_cnt);
								}
							}
						} else {
							if (!received_keepalive) {
								/* if we fill the message queue with loads of data, the server 
								process doesn't even get a chance of reply-ing. So as long as we 
								don't know a thing about it, we give it some time and check for
								a reply often */
								usleep(MSQ_QUEUE_CHECK_TIME * 1000);
								if (msg_queue_check_alive(pid_string)) {
									received_keepalive = 1;
									DEBUG_MSG("main (msg queue), received keepalive\n");
								} else {
									check_keepalive_cnt++;
									DEBUG_MSG("main (msg queue), no keepalive (try %d)\n", check_keepalive_cnt);
								}
							}
							DEBUG_MSG("main (msg queue), sending %s succeeded\n", (gchar *)tmplist->data);
							send_failure_cnt = 0;
							tmplist = g_list_next(tmplist);
						}
					} else {
						DEBUG_MSG("main (msg queue), failed sending, length increased message size\n");
						success = 0;
					}
					if ((check_keepalive_cnt > 5) || (send_failure_cnt > 30)) {
						DEBUG_MSG("main (msg queue), to many tries, check_keepalive_cnt=%d, send_failure_cnt=%d\n", check_keepalive_cnt, send_failure_cnt);
						success = 0;
					}
				}
				if (success) {
					DEBUG_MSG("main (msg queue), sending filenames complete and successfull, received_keepalive=%d\n",received_keepalive);
					/* all filenames send to other process, test if it is alive */
					if (received_keepalive) {
						exit(0);
					} else {
						if (ask_other_alive != -1) {
							/* the other process should have enough time to check the queue */
							/* the macro is in milliseconds, usleep is microseconds */
							usleep( 1000 * MSQ_QUEUE_CHECK_TIME );
							if (msg_queue_check_alive(pid_string)) {
								/* we did receive a keep alive message! */
								exit(0);
							}
						}
					}
				}
			}
		} else { /* if (!load_projectname && load_filenames && main_v->props.open_in_running_bluefish) */
			usleep( 1000 * MSQ_QUEUE_CHECK_TIME);
			if (msg_queue_check_alive(pid_string)) {
				received_keepalive = 1;
				DEBUG_MSG("main (msg queue), received keepalive, we should not be the server!\n");
			}
		}
		
	} /* if (other_bluefish) */
	
#endif /* WITH_MSG_QUEUE */
#endif
	/* display a splash screen with progress bar
	 * also creates the main window, reads the config, 
	 * checks directories etc... 
	 */
	DEBUG_MSG("main, calling startup()\n");
	startup();

	DEBUG_MSG("main, startup() finished\n");
	if (load_filenames) {
		docs_new_from_files(load_filenames);
		/* the strings don't need to be freed, they're needed in the project anyway */
		g_list_free(load_filenames);
	}
	DEBUG_MSG("main, load file from commandline finished\n");
	if ((load_projectname) && (file_exists_and_readable(load_projectname))) {
		main_v->current_project.filename = load_projectname;
		/* no need to free anymore */
		project_from_file();
	}

#ifdef WITH_MSG_QUEUE
	/* there was a message queue, lets check if there is a server process reading it */
	if (!other_bluefish || (other_bluefish && !received_keepalive)) {
		DEBUG_MSG("main (msg queue), no other bluefish, or we didn't get a keepalive, we will be server!\n");
		gtk_timeout_add(MSQ_QUEUE_CHECK_TIME, msg_queue_check, NULL);
	}
	if (pid_string) {
		g_free(pid_string);
	}
#endif /* WITH_MSG_QUEUE */
	
	DEBUG_MSG("main, going into gtk_main()\n");
	gtk_main();
	cleanup();
	gtk_exit(0);
	return (0);
}

static gchar** load_specific_filter(gchar *property, gchar **def_val) {
	gchar **retval;
	if (property && strlen(property)) {
		retval = g_strsplit(property, ":", 0);
	} else {
		retval = duplicate_stringarray(def_val);
	}
	return retval;
} 

void load_identify_filters() {
	gchar * images[] = {".jpg", ".gif", ".png", ".jpeg", NULL};
	gchar * html[] = {".htm", ".html", ".shtml", ".sht", NULL};
	gchar * php[] = {".phtml", ".php", ".php4", ".php3", ".phps", NULL};
	gchar * jscript[] = {".js", NULL};
	gchar * css[] = {".css", NULL};
	gchar * other[] = {".htaccess", ".asp", ".pl", ".cgi", ".jsp", NULL};
#ifdef WIN32
	gchar * dir[] = {"\\", NULL};
#else
	gchar * dir[] = {"/", NULL};
#endif
	gint count;

	for (count = 0 ; count < TYPES_COUNT ; count++) {
		if(main_v->identify_filters[count]) {
			g_strfreev(main_v->identify_filters[count]);
			main_v->identify_filters[count] = NULL;
		}
	}

	main_v->identify_filters[TYPE_UNKNOWN] = NULL;
	main_v->identify_filters[TYPE_IMAGE] = load_specific_filter(main_v->props.filebrowser_filter_image, images);
	main_v->identify_filters[TYPE_PHP] = load_specific_filter(main_v->props.filebrowser_filter_php, php);
	main_v->identify_filters[TYPE_HTML] = load_specific_filter(main_v->props.filebrowser_filter_html, html);
	main_v->identify_filters[TYPE_DIR] = load_specific_filter(NULL, dir);
	main_v->identify_filters[TYPE_JSCRIPT] = load_specific_filter(main_v->props.filebrowser_filter_jscript, jscript);
	main_v->identify_filters[TYPE_CSS] = load_specific_filter(main_v->props.filebrowser_filter_css, css);
	main_v->identify_filters[TYPE_OTHER] = load_specific_filter(main_v->props.filebrowser_filter_other, other);
}

static gint identify_test_this_type(gchar **point, gchar *filename) {
	while (*point) {
		if (strncmp(&filename[strlen(filename)-strlen(*point)], *point, strlen(*point)) == 0 ) {
			return 1;
		}
		point++;
	}
	return 0;
}

gint identify_by_filename(gchar *filename) {
	if (filename) {
		gint count;

		DEBUG_MSG("identify_by_filename, started on %s\n", filename);	
		for (count = 1 ; count < TYPES_COUNT; count++) {
			if (identify_test_this_type(main_v->identify_filters[count], filename)) {
				return count;
			}
		}
	}
	DEBUG_MSG("identify_by_filename, type unknown or no filename\n");
	return TYPE_UNKNOWN;
}
