/*
 * @author Henning Prömpers <henning.proempers@uni-jena.de>
 * @date 25. October 2009
 */

#include <glibtop.h>
#include <glibtop/proclist.h>
#include <glibtop/procargs.h>


/** Ping the client and returns the client's state (on or off).
 * @param client_no The number of the client which should be looked for.
 * @return The detected state, this may be on or off.
 */
guint ping_client(guint client_no){
	gint *exit_status = g_malloc(sizeof(gint));
	gchar **argv;
	g_shell_parse_argv(
				g_strdup_printf("ping -q -c 1 %s", get_hostname_of_client(client_no)),
				NULL, &argv, NULL);
	g_spawn_sync(NULL, argv, NULL, G_SPAWN_SEARCH_PATH + 
	            G_SPAWN_STDOUT_TO_DEV_NULL + G_SPAWN_STDERR_TO_DEV_NULL, 
                   NULL, NULL, NULL, NULL, exit_status, NULL);
	if (*exit_status==0){ return state_on;}
	else{ return state_off;}
	g_strfreev(argv);
	g_free(exit_status);
}


/** Try to find an open zleaf-tcp-connection.
 * The Language is set to POSIX before, because otherwise netstat returns 
 * ie "VERBUNDEN" instead of "ESTABLISHED".
 * @param client_no The number of the client which should be looked for.
 * @return The detected state, this may be on or ztree_running.
*/
guint netstat_client(guint client_no){
	gint *exit_status = g_malloc(sizeof(gint));
	gchar **argv, **envp;
	g_shell_parse_argv("LANG=POSIX", NULL, &envp, NULL);
	g_shell_parse_argv(
//				g_strdup_printf("LANG=POSIX; test -n \"$(netstat -t | grep afs3-fileserver |  grep ESTABLISHED | grep %s)\"",
//				g_strdup_printf("netstat -t",
				g_strdup_printf("/opt/labcontrol/scripts/netstat_client.sh %s",
				get_hostname_of_client(client_no)), NULL, &argv, NULL);
	g_spawn_sync(NULL, argv, envp, G_SPAWN_SEARCH_PATH, 
                NULL, NULL, NULL, NULL, exit_status, NULL);
//	g_message("Client %i: %i", client_no, *exit_status);
	if (*exit_status==0) {
//   	g_message("found connection to zleaf %i", client_no);
		return state_zleaf_running;}
	else{ return state_on;}
	g_strfreev(argv);
	g_strfreev(envp);
	g_free(exit_status);
}


/** Looks if a connection from remote zleaf to local ztree exists. 
 * @param client_no The number of the client which should be looked for.
 * @return The detected state, this may be on or ztree_running.
 */
guint ssh_find_zleaf_on_client(guint client_no){
	gint *exit_status = g_malloc(sizeof(gint));
	gchar **argv;
	g_shell_parse_argv(
				g_strdup_printf("/opt/labcontrol/scripts/ssh_find_zleaf.sh %s %s %s",
				get_publickey_path_user(),
				get_user_name_on_clients(),
				get_hostname_of_client(client_no)), NULL, &argv, NULL);
	g_spawn_sync(NULL, argv, NULL, G_SPAWN_SEARCH_PATH + 
	            G_SPAWN_STDOUT_TO_DEV_NULL + G_SPAWN_STDERR_TO_DEV_NULL, 
                NULL, NULL, NULL, NULL, exit_status, NULL);
//	g_message("Client %i: %i", client_no, *exit_status);
	if (*exit_status==0) {
//	   	g_message("found zleaf process at client %i", client_no);
		return state_zleaf_running;}
	else{ return state_on;}
	g_strfreev(argv);
	g_free(exit_status);
}


/** Gets the current state of the client by calling ping_client, netcat_client and ssh_find_zleaf_on_client.
 * @param client_no The number of the client which state should be updated.
 * @param only_zleaf_running if only_zleaf_running is false, then also ping the client. If a 
 *        zleaf should be started on the client, it is only required to check whether a 
 *        zleaf is already running. A ping would need too much time.
 * @raturn The current clients state, this may be on, off or zleaf_running.
 */
guint get_current_client_state(guint client_no, gboolean only_zleaf_running){
	guint newstate;
	if (!only_zleaf_running){
		newstate = ping_client(client_no);
		g_message("pinged client %i with result %i", client_no, newstate);
	}
	if (only_zleaf_running || ((newstate == state_on) & (get_client_state_from_gconf(client_no) != state_shutdowning))){
		newstate = netstat_client (client_no);
		g_message("netstat client %i with result %i", client_no, newstate);
		if (newstate == state_on){
			newstate = ssh_find_zleaf_on_client(client_no);
			g_message("ssh client %i with result %i", client_no, newstate);
		}
	}
//	g_message("Detected state %i for client %i", newstate, client_no);

	// in einer Sekunde nochmal probieren, wenn beim herunterfahren noch online (durch checken automatische Verzögerung von ca. 3 Sekunden)
	if ((get_client_state_from_gconf(client_no) == state_shutdowning) & (newstate == state_on)){
		client_update_wait[client_no-1] = 1;
		g_message("Client %i has not finished shutdowning yet, trying again", client_no);
		return state_shutdowning;
	}
	// nochmal probieren, wenn beim hochfahren noch offline (durch checken automatische Verzögerung von ca. 3 Sekunden)
	if ((get_client_state_from_gconf(client_no) == state_booting) & (newstate == state_off)){
		client_update_wait[client_no-1] = 0;
		g_message("Client %i has not finished booting yet, trying again", client_no);
		return state_booting;
	}

//	if (!client_update_lock[client_no-1]){// Abbrechen, wenn Status grade nicht verändert werden darf
		if (newstate != get_client_state_from_gconf(client_no)){
			set_client_state_to_gconf(client_no, newstate);			
		}
		client_update_wait[client_no-1] = 10; // per default 10 sekunden bis zum nächsten aktualisieren warten
		return newstate;
//	}
//	else {return state_undefined;}
}


/** The client's state is updated in an infinity loop.
 * This method is called by main() for each client in an own thread. If it would
 * be called in the main thread, the GUI would not respond while updating.
 * The array client_update_wait contains the remaining seconds until the next update.
 * @param client_no_typeless The client number is given as a pointer here, because a thread function can only get user data as pointer.
 */
void client_state_watchdog (gpointer client_no_typeless)
{
	guint client_no = GPOINTER_TO_UINT(client_no_typeless);
	while (TRUE){
//		g_message("Status von Client %i wird eingelesen", client_no);
//		client_update_lock[client_no-1] = FALSE;
		get_current_client_state(client_no, FALSE);
		
		// client_update_wait sekunden warten: jede sekunde um 1 verringern, damit auf zwischenzeitliche Änderungen aus anderen Funktionen reagiert werden kann
		while(client_update_wait[client_no-1] > 0){
//			client_update_lock[client_no-1] = FALSE; // evtl. entsperren
			g_usleep(1000000);
			client_update_wait[client_no-1]--;
//			g_message("Noch %i sec bis client %i aktualisiert wird", client_update_wait[client_no-1], client_no);
		}
	}
}



/** Look if a process called ztree.exe is running. This is ie required for the request, if the user really want to start another zleaf.
 * 
 * @return TRUE, if ztree is running on the local server
 */
gboolean ztree_running(){
	// alle Prozesse des Users auflisten
	gint *pid_list;
	glibtop_proclist proclist;
	pid_list = glibtop_get_proclist(&proclist, GLIBTOP_KERN_PROC_UID, getuid());
	// Zu jeder PID den Befehl auslesen und prüfen, ob dieser zu ztree führt
	guint i;
	gchar *command;
	glibtop_proc_args buf;
	for (i=0; i<proclist.number; i++){ // proclist.number bezeichnet die Anzahl der Einträge der Liste
		command = glibtop_get_proc_args(&buf, pid_list[i], 0);
		//Prüfen, ob command mit /ztree.exe endet
		if (g_str_has_suffix(command, "/ztree.exe")){
			g_message("Found a running process named ztree.exe with PID %i.", pid_list[i]);
			g_free(command); // Inhalt von command wieder löschen
			return TRUE;
		}
		g_free(command); // Inhalt von command wieder löschen
	}
	g_free(pid_list); // Inhalt von pid_Liste wieder löschen
	
	// Es wurde in den PIDs kein zTree-Prozess gefunden, false ausgeben
	g_message("Found no running process named ztree.exe.");
	return FALSE;
}

/** Look if there are processes called zleaf.exe running. Then look for the first unused number, that the local client can have.
 * 
 * @return unique number, which can be used to start a local zleaf client
 */
guint free_local_zleaf_number(){
	// alle Prozesse des Users auflisten
	gint *pid_list;
	glibtop_proclist proclist;
	pid_list = glibtop_get_proclist(&proclist, GLIBTOP_KERN_PROC_UID, getuid());
	// Zu jeder PID den Befehl auslesen und prüfen, ob dieser zu ztree führt
	guint i, j;
	gchar **args; // Vektor aus Strings, der alle Argumente des Befehls enthält
	glibtop_proc_args buf;
	guint unique_number = 1;
	gboolean number_changed = TRUE;
	while (number_changed & (unique_number <= proclist.number)){
		g_message("Trying number %i", unique_number);
		number_changed = FALSE;
		for (i=0; i<proclist.number; i++){ // proclist.number bezeichnet die Anzahl der Einträge der Liste
			args = glibtop_get_proc_argv(&buf, pid_list[i], 0);
			j = 0;
			// testen, ob Befehl mit zleaf.exe endet und als 2. Parameter local___ übergeben wurde (1. Parameter ist immer /name) 
			if ( (args != NULL) & (args[0] != NULL) & (args[1] != NULL) & (args[2] != NULL)){
				if (g_str_has_suffix(args[0], "zleaf.exe") & g_str_has_suffix(args[2], g_strdup_printf("local%i", unique_number))){
					g_message("Found a running process of zleaf.exe with name %i (PID %i).", unique_number, pid_list[i]);
					unique_number++;
					number_changed = TRUE;
				}
			}
			g_strfreev(args);
		}
	}
	g_free(pid_list); // Inhalt von pid_Liste wieder löschen	
	return unique_number;
}

/** Check wheter all clients are online.
 * @param zLeafOK If zLeafOK is TRUE, then a running zleaf is ignored. If FALSE, a running zleaf is returned like an offline client. This may be useful in furter versions, when updates should be performed.
 * @return TRUE if all clients are online, FALSE if they are not.
 */
gboolean all_clients_running(gboolean zLeafOK){
	int i;
	for(i=1; i<=num_clients; i++){
		if (!(gconf_client_get_int(gconf_client, g_strdup_printf("/apps/labcontrol/temp/status/client%i", i), NULL) == state_on)
		    ||((gconf_client_get_int(gconf_client, g_strdup_printf("/apps/labcontrol/temp/status/client%i", i), NULL)==state_zleaf_running)
		       && zLeafOK)) return FALSE; //wenn ein client nicht an ist, false zurückgeben
	}
	return TRUE; // wenn das hier erreicht wird, sind alle cleints an
}


/** Initialize the symbol update system.
 * Initialize the update of the symbols.
 * To do this, for every client a watchdog-Thread is started wich monitors the client's state periodically and update the state in gconf.
 * Also the gconfdirectory is monitored, and on changes the symbols are updated.
 */
void init_get_state(){

//	g_timeout_add_seconds_full()

	
	// Für jeden Client einen Watchdog-Thread zur Statusüberwachung starten
	if (!g_thread_supported ()) g_thread_init (NULL);   // sicherstellen, dass GThread-System initialisiert ist
	GThread *watchdog_client[num_clients];
	{int i; for (i=1; i<=num_clients; i++) {
		watchdog_client[i-1] = g_thread_create((GThreadFunc)client_state_watchdog, GUINT_TO_POINTER(i), FALSE, NULL); // Thread anlegen
		//g_thread_set_priority(watchdog_client[i-1], G_THREAD_PRIORITY_LOW); // hat bei Linux laut reference keine Wirkung
	}}

	// die GConf-Werte überwachen die den Status der Clients repräsentieren (Bei Änderung Symbol_aktualisieren ausführen)
	gconf_client_notify_add(gconf_client, "/apps/labcontrol/temp/status", update_symbol, NULL, NULL, NULL);
}