/* to compile:
 * gcc -Wall -pedantic -o ob3-et ob3-et.c \ 
 * 	`xml2-config --cflags --libs` \
 * 	`pkg-config --cflags --libs glib-2.0`
 *
 * the default config file is ~/.config/ob3-et; to change it,
 * simply change CONFIG_FILE (down there).
 *
 * you may pass an argument to the program, if you do it will
 * attempt to open that file instead of the one specified by
 * CONFIG_FILE.
 *
 * the config file should contain one server address per line;
 * the '#' character means comment.
 *
 * comments are handled almost exactly the same way perl 
 * or bash would handle them.
 *
 */

#define CONFIG_FILE ".config/ob3-et"
#define _GNU_SOURCE

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <linux/string.h>
#include <libxml/parser.h>
#include <libxml/tree.h>
#include <glib.h>

/* max qstat output size, in bytes */
#define MAX_OUTPUT_SIZE 200000

/* commands */
#define ET "et"

GSList * list;

struct server {
	char * status;
	
	char * gametype;
	char * hostname;
	char * ip;
	char * name;
	char * map;
	
	int numplayers;
	int maxplayers;
	int ping;

	GSList * players;
};

void parse_server (xmlNode * node) {
	xmlNode * current_node = NULL;
	xmlNode * child_node = NULL;
	
	xmlNode * player_node = NULL;
	xmlNode * player_name_node = NULL;
	
	current_node = node;
	if (current_node->type == XML_ELEMENT_NODE) {
		struct server * server = calloc (1, sizeof (struct server));
		server->ip = strdup ((char *) current_node->properties->next->children->content);
		server->status = strdup ((char *) current_node->properties->next->next->children->content);
		if (strncmp (server->status, "UP", 2) == 0) {
			child_node = current_node->children;
			while (child_node) {
				if (strncmp ("hostname", (char *) child_node->name, 8) == 0) {
					if (child_node->children && child_node->children->type == XML_TEXT_NODE) {
						server->hostname = strdup ((char *) child_node->children->content);
					}
				}
				else if (strncmp ("name", (char *) child_node->name, 4) == 0) {
					if (child_node->children && child_node->children->type == XML_TEXT_NODE) {
						server->name = strdup ((char *) child_node->children->content);
					}
				}
				else if (strncmp ("gametype", (char *) child_node->name, 8) == 0) {
					if (child_node->children && child_node->children->type == XML_TEXT_NODE) {
						server->gametype = strdup ((char *) child_node->children->content);
					}
				}
				else if (strncmp ("map", (char *) child_node->name, 3) == 0) {
					if (child_node->children && child_node->children->type == XML_TEXT_NODE) {
						server->map = strdup ((char *) child_node->children->content);
					}
				}
				else if (strncmp ("numplayers", (char *) child_node->name, 10) == 0) {
					if (child_node->children && child_node->children->type == XML_TEXT_NODE) {
						server->numplayers = atoi ((char *) child_node->children->content);
					}
				}
				else if (strncmp ("maxplayers", (char *) child_node->name, 10) == 0) {
					if (child_node->children && child_node->children->type == XML_TEXT_NODE) {
						server->maxplayers = atoi ((char *) child_node->children->content);
					}
				}
				else if (strncmp ("ping", (char *) child_node->name, 4) == 0) {
					if (child_node->children && child_node->children->type == XML_TEXT_NODE) {
						server->ping = atoi ((char *) child_node->children->content);
					}
				}
				else if (strncmp ("players", (char *) child_node->name, 7) == 0) {
					player_node = child_node->children;
					while (player_node) {
						if (player_node->type == XML_ELEMENT_NODE) {
							player_name_node = player_node->children;
							while (player_name_node) {
								if (player_name_node->type == XML_ELEMENT_NODE && !strncmp ("name", (char *) player_name_node->name, 4)) {
									if (player_name_node->children->type == XML_TEXT_NODE) {
										server->players = g_slist_append (server->players, strdup ((char *) player_name_node->children->content));
									}
								}
								player_name_node = player_name_node->next;
							}
						}
						player_node = player_node->next;
					}
				}
				child_node = child_node->next;
			}
		}

		list = g_slist_append (list, server);
	}
}

void parse_main (xmlNode * node) {
	xmlNode * current_node = NULL;
	
	current_node = node->children;
	while (current_node) {
		if (current_node->type == XML_ELEMENT_NODE) {
			if (!strncmp ("server", (char *) current_node->name, 4)) {
				parse_server (current_node);
			}
		}
		current_node = current_node->next;
	}
}

void player_list_print (char * player) {
	char * player_name = g_markup_escape_text (player, strlen (player));
	printf ("\t\t<item label=\"%s\" />\n", player_name);
	free (player_name);
}

void server_list_print (struct server * server) {
	char * server_name;
	
	if (strncmp(server->status, "UP", 4) != 0) {
		printf ("<item label=\"%s - %s\" />\n", server->ip, server->status);
		return;
	}
	
	server_name = g_markup_escape_text (server->name, strlen (server->name));
	
	printf ("<menu id=\"%s\" label=\"%s\">\n", server->hostname, server_name);
	printf ("\t<item label=\"ip: %s\" />\n", server->hostname);
	printf ("\t<item label=\"ping: %i\" />\n", server->ping);
	printf ("\t<item label=\"gametype: %s\" />\n", server->gametype);
	printf ("\t<item label=\"map: %s\" />\n", server->map);
	/*printf ("\tplayers: %i/%i\n", server->numplayers, server->maxplayers);*/

	if (server->numplayers > 0) {
		if (g_slist_length (server->players) > 0) {
			printf ("\t<menu id=\"players-%s\" label=\"players: %i/%i\">\n", server->hostname, server->numplayers, server->maxplayers);
			g_slist_foreach (server->players, (GFunc) player_list_print, NULL);
			printf ("\t</menu>\n");
		}
		else {
			printf ("\t<item label=\"players: %i/%i\" />\n", server->numplayers, server->maxplayers);
		}
	}
	else {
		printf ("\t<item label=\"empty\" />\n");
	}
	
	printf ("\t<separator />\n");
	printf ("\t<item label=\"connect\"><action name=\"execute\"><execute>%s +connect %s</execute></action></item>\n", ET, server->hostname);
	
	printf ("</menu>");
	printf ("\n");

	free (server_name);
}

void server_list_free (struct server * server) {
	g_slist_foreach (server->players, (GFunc) free, NULL);
	g_slist_free (server->players);

	if (server->status != NULL) {
		free (server->status);
	}
	if (server->gametype != NULL) {
		free (server->gametype);
	}
	if (server->hostname != NULL) {
		free (server->hostname);
	}
	if (server->ip != NULL) {
		free (server->ip);
	}
	if (server->name != NULL) {
		free (server->name);
	}
	if (server->map != NULL) {
		free (server->map);
	}
	
	free (server);
}

char * read_server_file (char * path) {
	FILE * file = fopen (path, "r");
	char string[MAX_OUTPUT_SIZE];
	char * return_str;
	char c;
	int count = 0;
	
	if (file == NULL) {
		printf ("<openbox_pipe_menu>\n");
		printf ("\t<item label=\"The server file could not be opened.\" />\n");
		printf ("\t<item label=\"Perhaps you forgot to make it?\" />\n");
		printf ("\t<item label=\"The file that I tried to open was\" />\n");
		printf ("\t<item label=\"%s\" />\n", path);
		printf ("</openbox_pipe_menu>\n");
		
		exit (1);
	}
	
	while (count < MAX_OUTPUT_SIZE) {
		c = fgetc(file);
		
		if (c == EOF) {
			string[count] = '\0';
			break;
		}
		if (c == '\n') {
			string[count] = ' ';	
		}
		else if (c == '#') {
			/* this is a comment
			 * ignore it */
			while ((c = fgetc (file)) != '\n') {}
			fseek (file, -1, SEEK_CUR);
		}
		else {
			string[count] = c;
		}
		
		count++;
	}
	
	fclose (file);
	
	return_str = strndup (string, strlen (string));
	
	return return_str;
}

int main (int argc, char ** argv) {
	xmlDoc * doc;
	xmlNode * root_node;

	char * server_file;
	char * qstat_output;
	char * qstat_command = malloc (strlen ("qstat -P -xml -default rws ") + MAX_OUTPUT_SIZE);
	char * servers;
	
	/* check for command line argument file pass */
	if ( argc == 1 ) {
		server_file = malloc (strlen (g_get_home_dir()) + strlen (CONFIG_FILE) + 2);
		sprintf (server_file, "%s/%s", g_get_home_dir (), CONFIG_FILE);
	} else if ( argc == 2 ) {
		server_file = strdup( argv[1] );
	} else {
		/* print error to stderr so to not confuse openbox 
		 * read_server_file will give appropriate error to openbox */
		fprintf(stderr, "Invalid number of argument specified.  Usage is %s [serverfile]\n", argv[0]);
		server_file = strdup( argv[1] );
	}
	
	servers = read_server_file (server_file);
	
	free (server_file);
	
	sprintf (qstat_command, "qstat -P -xml -default rws %s", servers);

	fprintf (stderr, "command: %s\n", qstat_command);
	
	/*qstat_output = read_output_from_command (qstat_command);*/
	g_spawn_command_line_sync (qstat_command, &qstat_output, NULL, NULL, NULL);

	/* parse */
	
	doc = xmlReadMemory (qstat_output, strlen (qstat_output), "qstat.xml", NULL, 0);
	if (doc == NULL) {
		fprintf(stderr, "Failed to parse.\n");
		return 1;
	}

	root_node = xmlDocGetRootElement (doc);
	parse_main (root_node);
	
	/* end parse; begin print */

	printf ("<openbox_pipe_menu>\n");
	g_slist_foreach (list, (GFunc) server_list_print, NULL);
	printf ("</openbox_pipe_menu>\n");
	
	/* we're done, let's clean up */
	
	g_slist_foreach (list, (GFunc) server_list_free, NULL);
	g_slist_free (list);
	free (qstat_output);
	
	return 0;
}
