/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 4; tab-width: 4 -*- */
/*
 * appcast.c
 * Copyright (C) John Stebbins 2008 <stebbins@stebbins>
 * 
 * appcast.c is free software.
 * 
 * You may 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.
 */

#include <stdio.h>
#include <string.h>
#include <glib.h>
#include <glib/gstdio.h>
#include "icon_tools.h"
#include "plist.h"
#include "values.h"

enum
{
	A_NONE = 0,
	A_DESCRIPTION,
	A_ENCLOSURE,
	A_ITEM,
};

typedef struct
{
	gchar *tag;
	gint id;
} tag_map_t;

static tag_map_t tag_map[] =
{
	{"description", A_DESCRIPTION},
	{"enclosure", A_ENCLOSURE},
	{"item", A_ITEM},
};
#define TAG_MAP_SZ	(sizeof(tag_map)/sizeof(tag_map_t))

typedef struct
{
	gchar *key;
	gchar *value;
	GQueue *stack;
	GQueue *tag_stack;
	GString *description;
	gchar *build;
	gchar *version;
	gboolean item;
} parse_data_t;

static const gchar*
lookup_attr_value(
	const gchar *name, 
	const gchar **attr_names, 
	const gchar **attr_values)
{
	gint ii;

	if (attr_names == NULL) return NULL;
	for (ii = 0; attr_names[ii] != NULL; ii++)
	{
		if (strcmp(name, attr_names[ii]) == 0)
			return attr_values[ii];
	}
	return NULL;
}
 
static void
start_element(
	GMarkupParseContext *ctx, 
	const gchar *tag, 
	const gchar **attr_names,
	const gchar **attr_values,
	gpointer ud,
	GError **error)
{
	parse_data_t *pd = (parse_data_t*)ud;
	union 
	{
		gint id;
		gpointer pid;
	} id;
	gint ii;

	for (ii = 0; ii < TAG_MAP_SZ; ii++)
	{
		if (strcmp(tag, tag_map[ii].tag) == 0)
		{
			id.id = tag_map[ii].id;
			break;
		}
	}
	if (ii == TAG_MAP_SZ)
	{
		g_debug("Unrecognized start tag (%s)", tag);
		id.id = A_NONE;
	}
	g_queue_push_head(pd->tag_stack, id.pid);
	switch (id.id)
	{
		case A_ITEM:
		{
			pd->item = TRUE;
		} break;
		case A_ENCLOSURE:
		{
			const gchar *build, *version;
			build = lookup_attr_value(
						"sparkle:version", attr_names, attr_values);
			version = lookup_attr_value(
						"sparkle:shortVersionString", attr_names, attr_values);
			if (build)
				pd->build = g_strdup(build);
			if (version)
				pd->version = g_strdup(version);
		} break;
	}
}

static void
end_element(
	GMarkupParseContext *ctx, 
	const gchar *tag, 
	gpointer ud,
	GError **error)
{
	parse_data_t *pd = (parse_data_t*)ud;
	gint id;
	union 
	{
		gint id;
		gpointer pid;
	} start_id;
	gint ii;

	for (ii = 0; ii < TAG_MAP_SZ; ii++)
	{
		if (strcmp(tag, tag_map[ii].tag) == 0)
		{
			id = tag_map[ii].id;
			break;
		}
	}
	if (ii == TAG_MAP_SZ)
	{
		g_debug("Unrecognized end tag (%s)", tag);
		id = A_NONE;
	}
	start_id.pid = g_queue_pop_head(pd->tag_stack);
	if (start_id.id != id)
		g_warning("start tag != end tag: (%s %d) %d", tag, start_id.id, id);
	switch (id)
	{
		case A_ITEM:
		{
			pd->item = FALSE;
		} break;
		default:
		{
		} break;
	}

}

static void
text_data(
	GMarkupParseContext *ctx, 
	const gchar *text, 
	gsize len,
	gpointer ud,
	GError **error)
{
	parse_data_t *pd = (parse_data_t*)ud;
	union 
	{
		gint id;
		gpointer pid;
	} start_id;

	start_id.pid = g_queue_peek_head(pd->tag_stack);
	switch (start_id.id)
	{
		case A_DESCRIPTION:
		{
			if (pd->item)
			{
				g_string_append(pd->description, text);
			}
		} break;
		default:
		{
			if (pd->value) g_free(pd->value);
			pd->value = g_strdup(text);
		} break;
	}
}

static void
passthrough(
	GMarkupParseContext *ctx, 
	const gchar *text, 
	gsize len,
	gpointer ud,
	GError **error)
{
	//parse_data_t *pd = (parse_data_t*)ud;

	//g_debug("passthrough %s", text);
}

static void
parse_error(GMarkupParseContext *ctx, GError *error, gpointer ud)
{
	g_warning("Resource parse error: %s", error->message);
}

// This is required or the parser crashes
static void 
destroy_notify(gpointer data)
{ // Do nothing
	//g_debug("destroy parser");
}

void
ghb_appcast_parse(gchar *buf, gchar **desc, gchar **build, gchar **version)
{
	GMarkupParseContext *ctx;
	GMarkupParser parser;
	parse_data_t pd;
	GError *err = NULL;
	gint len;
	gchar *start;
	//gchar tmp[4096]

	// Skip junk at beginning of buffer
	start = strstr(buf, "<?xml ");
	pd.description = g_string_new("");
	pd.item = FALSE;
	pd.build = NULL;
	pd.version = NULL;
	len = strlen(start);
	pd.tag_stack = g_queue_new();
	pd.key = NULL;
	pd.value = NULL;

	parser.start_element = start_element;
	parser.end_element = end_element;
	parser.text = text_data;
	parser.passthrough = passthrough;
	parser.error = parse_error;
	ctx = g_markup_parse_context_new(
			&parser, G_MARKUP_TREAT_CDATA_AS_TEXT, &pd, destroy_notify);

	g_markup_parse_context_parse(ctx, start, len, &err);
	g_markup_parse_context_end_parse(ctx, &err);
	g_markup_parse_context_free(ctx);
	g_queue_free(pd.tag_stack);
	*desc = g_string_free(pd.description, FALSE);
	// work around a bug to leaves the CDATA closing brakets on the string
	gchar *glitch;
	glitch = g_strrstr(*desc, "]]>");
	if (glitch)
		*glitch = 0;
	*build = pd.build;
	*version = pd.version;
}
