/*
 * This file is part of Sargasso, http://zamez.org/sargasso
 * Licensed under the GNU General Public License,
 *                http://www.opensource.org/licenses/gpl-license
 * Copyright 2006 James Bursa <james@zamez.org>
 */

#include <assert.h>
#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <oslib/colourtrans.h>
#include <oslib/osfile.h>
#include <oslib/uri.h>
#include <oslib/wimp.h>
#include <oslib/wimpspriteop.h>
#include <rufl.h>
#include "feed.h"


#define MAX_LINES 20
#define MARGIN 10
#define FEEDS_READ "Choices:Sargasso.Feeds"
#define FEEDS_WRITE "<Choices$Write>.Sargasso.Feeds"
#define CHOICES_READ "Choices:Sargasso.Choices"
#define CHOICES_WRITE "<Choices$Write>.Sargasso.Choices"
#define WRITE_DIR "<Choices$Write>.Sargasso"

#define LOG(x) (printf(__FILE__ " %s %i: ", __PRETTY_FUNCTION__, __LINE__), printf x, fputc('\n', stdout));

typedef void (*click_callback)(unsigned int i);

struct paragraph {
	int x0, y0, x1, y1;
	int background, colour;
	const char *font_family;
	rufl_style font_style;
	unsigned int font_size;
	click_callback click;
	unsigned int click_i;
	unsigned int lines;
	const char *text[MAX_LINES + 1];
	struct paragraph *next;
};

bool quit = false;
int interval = 30 * 60;
os_t last_update = 0;

wimp_t task;
wimp_w info_window, main_window, feed_window, add_feed_window,
		warning_window = 0, choices_window;
osspriteop_area *sprites;
struct paragraph *main_window_paragraphs = 0;
struct paragraph *feed_window_paragraphs = 0;
unsigned int current_feed = 0;
char font_headings[100] = "Homerton";
char font_summaries[100] = "NewHall";
char font_links[100] = "Homerton";

#define ICON_FLAGS (wimp_ICON_TEXT | \
		(wimp_COLOUR_BLACK << wimp_ICON_FG_COLOUR_SHIFT))
wimp_MENU(4) iconbar_menu = { { "Sargasso" }, wimp_COLOUR_BLACK,
	wimp_COLOUR_LIGHT_GREY, wimp_COLOUR_BLACK, wimp_COLOUR_WHITE,
	200, 44, 0,
	{ { 0, 0, ICON_FLAGS, { "Info" } },
	  { 0, 0, ICON_FLAGS, { "Choices..." } },
	  { 0, 0, ICON_FLAGS, { "Update feeds" } },
	  { wimp_MENU_LAST, 0, ICON_FLAGS, { "Quit" } } } };
wimp_MENU(2) main_menu = { { "Sargasso" }, wimp_COLOUR_BLACK,
	wimp_COLOUR_LIGHT_GREY, wimp_COLOUR_BLACK, wimp_COLOUR_WHITE,
	200, 44, 0,
	{ { 0, 0, ICON_FLAGS, { "Add feed..." } },
	  { wimp_MENU_LAST, 0, ICON_FLAGS, { "Remove feed" } } } };
wimp_menu *current_menu;
unsigned int current_removing = 0;
wimp_i current_font_menu;

const char *default_feeds[] = {
	"http://news.google.co.uk/?output=rss",
	"http://www.ft.com/rss/home/uk",
	"http://newsrss.bbc.co.uk/rss/newsonline_uk_edition/front_page/rss.xml",
	"http://rss.cnn.com/rss/edition.rss",
	"http://feeds.chicagotribune.com/chicagotribune/news/",
	"http://www.drobe.co.uk/rss.php",
	"http://www.theregister.co.uk/feeds/latest.rdf",
	"http://www.snackspot.org.uk/rss/rss.xml",
	"http://www.mode7games.com/blog/?feed=rss2",
	"http://cia.navi.cx/stats/project/NetSurf/.rss",
};

void gui_init(void);
void gui_quit(void);
wimp_w create_window(const char *name);
void gui_poll(void);
void mouse_click(wimp_w w, wimp_i i, int x, int y,
		wimp_mouse_state buttons);
void set_pointer(const char *name, int x, int y);
void reset_pointer(void);
void key_pressed(wimp_key *key);
void menu_selection(wimp_selection *selection);
void open_window(wimp_w w);
void close_window(wimp_w w);
void open_menu(wimp_menu *menu, int x, int y);
void update_main_window(void);
void set_extent(wimp_w w, int y);
void click_main_window(unsigned int i);
void update_feed_window(unsigned int i);
void click_feed_link(unsigned int i);
void click_item_link(unsigned int j);
struct paragraph *add_paragraph(struct paragraph **paragraph_list,
		int x0, int y0, int x1, int background, int colour,
		const char *font_family, rufl_style font_style,
		unsigned int font_size, const char *text,
		click_callback click, unsigned int click_i);
void free_paragraph_list(struct paragraph **paragraph_list);
void redraw_window(wimp_draw *redraw, struct paragraph *paragraphs);
void fill_rectangle(int x0, int y0, int x1, int y1, int colour);
void choices_save(void);
void choices_load(void);
void die(const char *error);
void warn(const char *warning);
char *get_icon_string(wimp_w w, wimp_i i);
void set_icon_string(wimp_w w, wimp_i i, const char *text);


int main(int argc, char *argv[])
{
	char error[200];
	struct stat s;

/* 	memdebug_memdebug("memdump"); */

	if (!feed_init())
		die(feed_error);

	interval = (30 + time(0) % 20) * 60;
	choices_load();
	gui_init();

	if (stat(FEEDS_READ, &s)) {
		warn("Welcome to Sargasso! A selection of feeds have been "
				"added. To add more feeds, use the main menu.");
		mkdir(WRITE_DIR, S_IRWXU);
		for (unsigned int i = 0; i != sizeof default_feeds /
				sizeof default_feeds[0]; i++)
			feed_add(default_feeds[i]);
		feed_list_save(FEEDS_WRITE);
	} else if (!feed_list_load(FEEDS_READ)) {
		snprintf(error, sizeof error, "Unable to load feed list: %s",
				feed_error);
		warn(error);
	}

	update_main_window();
	open_window(main_window);

	last_update = os_read_monotonic_time();

	while (!quit)
		gui_poll();

	gui_quit();
	feed_quit();

	return 0;
}


void gui_init(void)
{
	rufl_code code;
	const wimp_MESSAGE_LIST(1) messages = { { message_QUIT } };
	int size;
	fileswitch_object_type obj_type;
	wimp_icon_create icon = {
		wimp_ICON_BAR_RIGHT,
		{ { 0, 0, 68, 68 },
		wimp_ICON_SPRITE | wimp_ICON_HCENTRED | wimp_ICON_VCENTRED |
			(wimp_BUTTON_CLICK << wimp_ICON_BUTTON_TYPE_SHIFT),
		{ "!sargasso" } } };
	os_error *error;

	error = xwimp_initialise(wimp_VERSION_RO3, "Sargasso",
			(const wimp_message_list *) &messages, 0, &task);
	if (error) {
		LOG(("xwimp_initialise: 0x%x: %s",
				error->errnum, error->errmess));
		die(error->errmess);
	}

	code = rufl_init();
	if (code != rufl_OK) {
		LOG(("rufl_init: %i", code));
		die("Failed to initialise Unicode font library");
	}

	error = xosfile_read_stamped_no_path("<Sargasso$Dir>.Sprites",
			&obj_type, 0, 0, &size, 0, 0);
	if (error) {
		LOG(("xosfile_read_stamped_no_path: 0x%x: %s",
				error->errnum, error->errmess));
		die(error->errmess);
	}
	if (obj_type != fileswitch_IS_FILE)
		die("Sprites file missing");
	sprites = malloc(size + 4);
	if (!sprites)
		die("Out of memory");
	sprites->size = size + 4;
	sprites->sprite_count = 0;
	sprites->first = 16;
	sprites->used = 16;
	error = xosspriteop_load_sprite_file(osspriteop_USER_AREA,
			sprites, "<Sargasso$Dir>.Sprites");
	if (error) {
		LOG(("xosspriteop_load_sprite_file: 0x%x: %s",
				error->errnum, error->errmess));
		die(error->errmess);
	}

	error = xwimp_create_icon(&icon, 0);
	if (error) {
		LOG(("xwimp_create_icon: 0x%x: %s",
				error->errnum, error->errmess));
		die(error->errmess);
	}

	error = xwimp_open_template("<Sargasso$Dir>.Templates");
	if (error) {
		LOG(("xwimp_open_template: 0x%x: %s",
				error->errnum, error->errmess));
		die(error->errmess);
	}

	info_window = create_window("info");
	main_window = create_window("main");
	feed_window = create_window("main");
	add_feed_window = create_window("add_feed");
	warning_window = create_window("warning");
	choices_window = create_window("choices");

	iconbar_menu.entries[0].sub_menu = (wimp_menu *) info_window;

	error = xwimp_close_template();
	if (error) {
		LOG(("xwimp_close_template: 0x%x: %s",
				error->errnum, error->errmess));
		die(error->errmess);
	}
}


void gui_quit(void)
{
	free_paragraph_list(&main_window_paragraphs);
	free_paragraph_list(&feed_window_paragraphs);
	rufl_quit();
}


wimp_w create_window(const char *name)
{
	char name_buf[12];
	int window_size;
	int data_size;
	wimp_window *window;
	char *data;
	wimp_w w;
	os_error *error;

	assert(strlen(name) < 12);

	strncpy(name_buf, name, sizeof name_buf);

	error = xwimp_load_template(wimp_GET_SIZE, 0, 0, wimp_NO_FONTS,
			name_buf, 0, &window_size, &data_size, 0);
	if (error) {
		LOG(("xwimp_load_template: 0x%x: %s",
				error->errnum, error->errmess));
		die(error->errmess);
	}

	window = malloc(window_size);
	data = malloc(data_size);
	if (!window || !data)
		die("Out of memory");

	error = xwimp_load_template(window, data, data + data_size,
			wimp_NO_FONTS, name_buf, 0, 0, 0, 0);
	if (error) {
		LOG(("xwimp_load_template: 0x%x: %s",
				error->errnum, error->errmess));
		die(error->errmess);
	}

	error = xwimp_create_window(window, &w);
	if (error) {
		LOG(("xwimp_create_window: 0x%x: %s",
				error->errnum, error->errmess));
		die(error->errmess);
	}

	return w;
}


void gui_poll(void)
{
	static bool in_window = false;
	os_t t;
	wimp_block block;
	wimp_event_no event;
	wimp_pointer pointer;
	osbool more;
	os_error *error;

	t = os_read_monotonic_time();

	error = xwimp_poll_idle(0,
			&block,
			feed_work_needed || in_window ? t + 5 : t + 100,
			0, &event);
	if (error) {
		LOG(("xwimp_poll: 0x%x: %s", error->errnum, error->errmess));
		warn(error->errmess);
	}

	switch (event) {
	case wimp_NULL_REASON_CODE:
		if (feed_work_needed) {
			if (feed_work()) {
				update_main_window();
				if (feed_count && feeds[current_feed].updated)
					update_feed_window(current_feed);
			}
		}

		t = os_read_monotonic_time();
		if (last_update + interval * 100 <= t) {
			LOG(("updating"));
			last_update = t;
			feed_update();
		}

		error = xwimp_get_pointer_info(&pointer);
		if (error) {
			LOG(("xwimp_get_pointer_info: 0x%x: %s",
					error->errnum, error->errmess));
			warn(error->errmess);
		}
		mouse_click(pointer.w, pointer.i,
				pointer.pos.x, pointer.pos.y, 0);
		break;

	case wimp_REDRAW_WINDOW_REQUEST:
		error = xwimp_redraw_window(&block.redraw, &more);
		if (error) {
			LOG(("xwimp_redraw_window: 0x%x: %s",
					error->errnum, error->errmess));
			break;
		}
		while (more) {
			if (block.redraw.w == main_window)
				redraw_window(&block.redraw,
						main_window_paragraphs);
			else if (block.redraw.w == feed_window)
				redraw_window(&block.redraw,
						feed_window_paragraphs);
			error = xwimp_get_rectangle(&block.redraw, &more);
			if (error) {
				LOG(("xwimp_get_rectangle: 0x%x: %s",
						error->errnum, error->errmess));
				break;
			}
		}
		break;

	case wimp_OPEN_WINDOW_REQUEST:
		error = xwimp_open_window(&block.open);
		if (error) {
			LOG(("xwimp_open_window: 0x%x: %s",
					error->errnum, error->errmess));
			warn(error->errmess);
		}
		break;

	case wimp_CLOSE_WINDOW_REQUEST:
		error = xwimp_close_window(block.close.w);
		if (error) {
			LOG(("xwimp_close_window: 0x%x: %s",
					error->errnum, error->errmess));
			warn(error->errmess);
		}
		break;

	case wimp_POINTER_LEAVING_WINDOW:
		reset_pointer();
		in_window = false;
		break;

	case wimp_POINTER_ENTERING_WINDOW:
		in_window = true;
		break;

	case wimp_MOUSE_CLICK:
		mouse_click(block.pointer.w, block.pointer.i,
				block.pointer.pos.x, block.pointer.pos.y,
				block.pointer.buttons);
		break;

	case wimp_KEY_PRESSED:
		key_pressed(&block.key);
		break;

	case wimp_MENU_SELECTION:
		menu_selection(&block.selection);
		break;

	case wimp_USER_MESSAGE:
	case wimp_USER_MESSAGE_RECORDED:
		switch (block.message.action) {
		case message_QUIT:
			quit = true;
			break;
		}
		break;
	}
}


void mouse_click(wimp_w w, wimp_i i, int x, int y,
		wimp_mouse_state buttons)
{
	wimp_window_state state;
	int wx, wy;
	struct paragraph *p = 0;
	char minutes[10];
	int mins;
	os_error *error;

	if (w == wimp_ICON_BAR) {
		if (buttons == wimp_CLICK_MENU)
			open_menu((wimp_menu *) &iconbar_menu,
					x - 64, 96 + 44 * 4);
		else if (buttons == wimp_CLICK_SELECT ||
				buttons == wimp_CLICK_ADJUST)
			open_window(main_window);

	} else if (w == main_window || w == feed_window) {
		state.w = w;
		error = xwimp_get_window_state(&state);
		if (error) {
			LOG(("xwimp_get_window_state: 0x%x: %s",
					error->errnum, error->errmess));
			warn(error->errmess);
		}
		wx = x - (state.visible.x0 - state.xscroll);
		wy = -(y - (state.visible.y1 - state.yscroll));
		if (w == main_window)
			p = main_window_paragraphs;
		else
			p = feed_window_paragraphs;
		for (; p; p = p->next) {
			if (p->x0 <= wx && wx <= p->x1 &&
					p->y0 <= wy && wy <= p->y1 &&
					p->click)
				break;
		}
		if (p && buttons == wimp_CLICK_SELECT)
			p->click(p->click_i);
		else if (buttons == wimp_CLICK_MENU &&
				w == main_window) {
			if (p)
				main_menu.entries[1].icon_flags &=
						~wimp_ICON_SHADED;
			else
				main_menu.entries[1].icon_flags |=
						wimp_ICON_SHADED;
			open_menu((wimp_menu *) &main_menu, x - 64, y);
			current_removing = p ? p->click_i : 0;
		} else if (buttons == 0) {
			if (p)
				set_pointer("ptr_point", 6, 0);
			else
				reset_pointer();
		}

	} else if (w == add_feed_window && buttons) {
		if (i == 1) {
			feed_add(get_icon_string(add_feed_window, 0));
			update_main_window();
			if (!feed_list_save(FEEDS_WRITE))
				warn(feed_error);
			if (buttons == wimp_CLICK_SELECT)
				close_window(add_feed_window);
		} else if (i == 2) {
			close_window(add_feed_window);
		}

	} else if (w == warning_window && buttons) {
		if (i == 1)
			close_window(warning_window);

	} else if (w == choices_window && buttons) {
		if (i == 6) {
			mins = atoi(get_icon_string(choices_window, 1));
			if (mins < 15) {
				mins = 15;
				warn("To avoid overloading feed providers, "
						"the minimum update interval "
						"is 15 minutes.");
			}
			interval = mins * 60;
			choices_save();
			if (buttons == wimp_CLICK_SELECT)
				close_window(choices_window);
		} else if (i == 5) {
			close_window(choices_window);
		} else if (i == 2 || i == 3) {
			mins = atoi(get_icon_string(choices_window, 1));
			if ((i == 2 && buttons == wimp_CLICK_SELECT) ||
			    (i == 3 && buttons == wimp_CLICK_ADJUST))
				mins -= 3;
			else
				mins += 3;
			if (mins < 15)
				mins = 15;
			else if (999 < mins)
				mins = 999;
			snprintf(minutes, sizeof minutes, "%i", mins);
			set_icon_string(choices_window, 1, minutes);
			xwimp_set_caret_position(choices_window, 1,
					0, 0, -1, strlen(minutes));
		} else if (i == 7 || i == 8 ||
				i == 10 || i == 11 ||
				i == 13 || i == 14) {
			if (i == 8 || i == 11 || i == 14)
				i--;
			wimp_window_state state;
			wimp_icon_state icon_state;
			state.w = w;
			xwimp_get_window_state(&state);
			icon_state.w = w;
			icon_state.i = i;
			xwimp_get_icon_state(&icon_state);
			open_menu((wimp_menu *) rufl_family_menu,
				state.visible.x0 + icon_state.icon.extent.x1,
				state.visible.y1 + icon_state.icon.extent.y1);
			current_font_menu = i;
		}
	}
}


void set_pointer(const char *name, int x, int y)
{
	os_error *error;
	error = xosspriteop_set_pointer_shape(osspriteop_USER_AREA, sprites,
			(osspriteop_id) name, 1, x, y, 0, 0);
	if (error) {
		LOG(("xosspriteop_set_pointer_shape: 0x%x: %s",
				error->errnum, error->errmess));
		warn(error->errmess);
	}
}


void reset_pointer(void)
{
	os_error *error;
	error = xwimpspriteop_set_pointer_shape("ptr_default", 1, 0, 0, 0, 0);
	if (error) {
		LOG(("xwimpspriteop_set_pointer_shape: 0x%x: %s",
				error->errnum, error->errmess));
		warn(error->errmess);
	}
}


void key_pressed(wimp_key *key)
{
	os_error *error;

	if (key->w == add_feed_window) {
		if (key->c == wimp_KEY_ESCAPE) {
			mouse_click(key->w, 2, 0, 0, wimp_CLICK_SELECT);
			return;
		} else if (key->c == wimp_KEY_RETURN) {
			mouse_click(key->w, 1, 0, 0, wimp_CLICK_SELECT);
			return;
		}

	} else if (key->w == choices_window) {
		if (key->c == wimp_KEY_ESCAPE) {
			mouse_click(key->w, 5, 0, 0, wimp_CLICK_SELECT);
			return;
		} else if (key->c == wimp_KEY_RETURN) {
			mouse_click(key->w, 6, 0, 0, wimp_CLICK_SELECT);
			return;
		} else if (key->c == wimp_KEY_DOWN) {
			mouse_click(key->w, 2, 0, 0, wimp_CLICK_SELECT);
			return;
		} else if (key->c == wimp_KEY_UP) {
			mouse_click(key->w, 3, 0, 0, wimp_CLICK_SELECT);
			return;
		}

	} else if (key->w == warning_window) {
		if (key->c == wimp_KEY_ESCAPE ||
		    key->c == wimp_KEY_RETURN) {
			close_window(warning_window);
		}
	}

	error = xwimp_process_key(key->c);
	if (error) {
		LOG(("xwimp_process_key: 0x%x: %s",
				error->errnum, error->errmess));
		warn(error->errmess);
	}
}


void menu_selection(wimp_selection *selection)
{
	char minutes[10];
	wimp_pointer pointer;

	xwimp_get_pointer_info(&pointer);

	if (current_menu == (wimp_menu *) &iconbar_menu) {
		switch (selection->items[0]) {
		case 1:
			snprintf(minutes, sizeof minutes, "%i", interval / 60);
			set_icon_string(choices_window, 1, minutes);
			set_icon_string(choices_window, 8, font_headings);
			set_icon_string(choices_window, 11, font_summaries);
			set_icon_string(choices_window, 14, font_links);
			open_window(choices_window);
			xwimp_set_caret_position(choices_window, 1,
					0, 0, -1, strlen(minutes));
			break;
		case 2:
			last_update = os_read_monotonic_time();
			feed_update();
			break;
		case 3:
			quit = true;
			break;
		}

	} else if (current_menu == (wimp_menu *) &main_menu) {
		switch (selection->items[0]) {
		case 0:
			set_icon_string(add_feed_window, 0, "http://");
			open_window(add_feed_window);
			xwimp_set_caret_position(add_feed_window, 0,
					0, 0, -1, 7);
			break;
		case 1:
			feed_remove(current_removing);
			update_main_window();
			close_window(feed_window);
			if (!feed_list_save(FEEDS_WRITE))
				warn(feed_error);
			break;
		}
	} else if (current_menu == (wimp_menu *) rufl_family_menu) {
		char *font = ((wimp_menu *) rufl_family_menu)->
			entries[selection->items[0]].data.indirected_text.text;
		if (current_font_menu == 7)
			strncpy(font_headings, font, sizeof font_headings);
		else if (current_font_menu == 10)
			strncpy(font_summaries, font, sizeof font_summaries);
		else if (current_font_menu == 13)
			strncpy(font_links, font, sizeof font_links);
		set_icon_string(choices_window, 8, font_headings);
		set_icon_string(choices_window, 11, font_summaries);
		set_icon_string(choices_window, 14, font_links);
		update_main_window();
		if (feed_count)
			update_feed_window(current_feed);
	}

	if (pointer.buttons == wimp_CLICK_ADJUST)
		xwimp_create_menu(current_menu, pointer.pos.x, pointer.pos.y);
}


void open_window(wimp_w w)
{
	const os_VDU_VAR_LIST(5) vars = { { os_MODEVAR_XWIND_LIMIT,
			os_MODEVAR_YWIND_LIMIT, os_MODEVAR_XEIG_FACTOR,
			os_MODEVAR_YEIG_FACTOR, os_VDUVAR_END_LIST } };
	int vals[4];
	int width, height;
	wimp_window_state state;
	int dx, dy;
	os_error *error;

	error = xos_read_vdu_variables((const os_vdu_var_list *) &vars, vals);
	if (error) {
		LOG(("xos_read_vdu_variables: 0x%x: %s",
				error->errnum, error->errmess));
		warn(error->errmess);
		return;
	}
	width = (vals[0] + 1) << vals[2];
	height = (vals[1] + 1) << vals[3];

	state.w = w;
	error = xwimp_get_window_state(&state);
	if (error) {
		LOG(("xwimp_get_window_state: 0x%x: %s",
				error->errnum, error->errmess));
		warn(error->errmess);
		return;
	}

	dx = (state.visible.x1 - state.visible.x0) / 2;
	dy = (state.visible.y1 - state.visible.y0) / 2;
	state.visible.x0 = width / 2 - dx;
	state.visible.y0 = height / 2 - dy;
	state.visible.x1 = width / 2 + dx;
	state.visible.y1 = height / 2 + dy;
	state.xscroll = 0;
	state.yscroll = 0;
	state.next = wimp_TOP;

	error = xwimp_open_window((wimp_open *) &state);
	if (error) {
		LOG(("xwimp_open_window: 0x%x: %s",
				error->errnum, error->errmess));
		warn(error->errmess);
	}
}


void close_window(wimp_w w)
{
	os_error *error;

	error = xwimp_close_window(w);
	if (error) {
		LOG(("xwimp_close_window: 0x%x: %s",
				error->errnum, error->errmess));
		warn(error->errmess);
	}
}


void open_menu(wimp_menu *menu, int x, int y)
{
	os_error *error;

	error = xwimp_create_menu(menu, x, y);
	if (error) {
		LOG(("xwimp_create_menu: 0x%x: %s",
				error->errnum, error->errmess));
		warn(error->errmess);
	}
	current_menu = menu;
}


void update_main_window(void)
{
	static char *status = 0;
	int y = 0;
	int y1;
	unsigned int i;
	unsigned int j;
	unsigned int new_items;
	rufl_style style;
	struct paragraph *p;

	free_paragraph_list(&main_window_paragraphs);
	free(status);

	status = malloc(feed_count * 40);
	if (!status) {
		LOG(("out of memory"));
		return;
	}

	for (i = 0; i != feed_count; i++) {
		new_items = 0;
		for (j = 0; j != feeds[i].item_count; j++)
			if (feeds[i].item[j].new_item)
				new_items++;
		style = new_items ? rufl_WEIGHT_900 : rufl_WEIGHT_400;

		p = add_paragraph(&main_window_paragraphs, 0, y, 700,
				0xffaa99, 0x000000,
				font_headings, style, 200,
				feeds[i].title ? (char *) feeds[i].title :
				feeds[i].url,
				click_main_window, i);
		y1 = p->y1;

		switch (feeds[i].status) {
		case FEED_NEW:
		case FEED_FETCHING:
		case FEED_UPDATE:
			snprintf(status + i * 40, 40, "Fetching");
			break;
		case FEED_OK:
			if (new_items)
				snprintf(status + i * 40, 40,
						"%i items (%i new)",
						feeds[i].item_count,
						new_items);
			else
				snprintf(status + i * 40, 40,
						"%i items",
						feeds[i].item_count);
			break;
		case FEED_ERROR:
			snprintf(status + i * 40, 40, "Failed");
			break;
		}
		p = add_paragraph(&main_window_paragraphs, 700, y, 1000,
				0xffaa99, 0x000000,
				font_headings, style, 200,
				status + i * 40,
				click_main_window, i);
		y = p->y1 = y1;

		if (feeds[i].error) {
			p = add_paragraph(&main_window_paragraphs, 0, y, 1000,
					0, 0x0000a0,
					font_summaries, style, 200,
					feeds[i].error,
					click_main_window, i);
			y = p->y1;
		}

		if (feeds[i].description) {
			p = add_paragraph(&main_window_paragraphs, 0, y, 1000,
					0, 0x000000,
					font_summaries, rufl_WEIGHT_400, 200,
					feeds[i].description,
					click_main_window, i);
			y = p->y1;
		}

		y += MARGIN;
	}

	set_extent(main_window, y);
}


void set_extent(wimp_w w, int y)
{
	os_box extent = { 0, 0, 1000, 0 };
	wimp_window_state state;
	os_error *error;

	extent.y0 = -y;
	error = xwimp_set_extent(w, &extent);
	if (error) {
		LOG(("xwimp_set_extent: 0x%x: %s",
				error->errnum, error->errmess));
		warn(error->errmess);
	}

	state.w = w;
	error = xwimp_get_window_state(&state);
	if (error) {
		LOG(("xwimp_get_window_state: 0x%x: %s",
				error->errnum, error->errmess));
		warn(error->errmess);
	}

	if (state.flags & wimp_WINDOW_OPEN) {
		error = xwimp_close_window(w);
		if (error) {
			LOG(("xwimp_close_window: 0x%x: %s",
					error->errnum, error->errmess));
			warn(error->errmess);
		}

		/*state.visible.y0 = 100;
		state.visible.y1 = 100 + y;*/
		error = xwimp_open_window((wimp_open *) &state);
		if (error) {
			LOG(("xwimp_open_window: 0x%x: %s",
					error->errnum, error->errmess));
			warn(error->errmess);
		}
	}
}


void click_main_window(unsigned int i)
{
	unsigned int j;

	update_feed_window(i);
	open_window(feed_window);

	for (j = 0; j != feeds[i].item_count; j++)
		feeds[i].item[j].new_item = false;
	update_main_window();
}


void update_feed_window(unsigned int i)
{
	int y = 0;
	int y1;
	unsigned int j;
	struct paragraph *p;
	rufl_style style;

	current_feed = i;

	free_paragraph_list(&feed_window_paragraphs);

	p = add_paragraph(&feed_window_paragraphs, 0, y, 1000,
			0xffaa99, 0x000000,
			font_headings, rufl_WEIGHT_400, 200,
			feeds[i].title ? (char *) feeds[i].title : feeds[i].url,
			0, 0);
	y = p->y1;

	if (feeds[i].link) {
		p = add_paragraph(&feed_window_paragraphs, 0, y, 1000,
				0, 0xff0000,
				font_links, rufl_WEIGHT_400, 160,
				feeds[i].link,
				click_feed_link, i);
		y = p->y1;
	}
	if (feeds[i].pub_date) {
		p = add_paragraph(&feed_window_paragraphs, 0, y, 1000,
				0, 0x0060ee,
				font_summaries, rufl_WEIGHT_400 | rufl_SLANTED,
				160,
				feeds[i].pub_date,
				0, 0);
		y = p->y1;
	}
	if (feeds[i].description) {
		p = add_paragraph(&feed_window_paragraphs, 0, y, 1000,
				0, 0x000000,
				font_summaries, rufl_WEIGHT_400, 200,
				feeds[i].description,
				0, 0);
		y = p->y1;
	}
	if (feeds[i].copyright) {
		p = add_paragraph(&feed_window_paragraphs, 0, y, 1000,
				0, 0x666666,
				font_summaries, rufl_WEIGHT_400 | rufl_SLANTED, 160,
				feeds[i].copyright,
				0, 0);
		y = p->y1;
	}

	y += MARGIN;

	for (j = 0; j != feeds[i].item_count; j++) {
		style = feeds[i].item[j].new_item ? rufl_WEIGHT_900 :
				rufl_WEIGHT_400;

		p = add_paragraph(&feed_window_paragraphs, 0, y, 1000,
				0xa0dddd, 0x000000,
				font_headings, style, 200,
				feeds[i].item[j].title ?
					(char *) feeds[i].item[j].title : "",
				0, j);
		y1 = y = p->y1;

		if (feeds[i].item[j].pub_date) {
			p = add_paragraph(&feed_window_paragraphs, 0, y, 420,
					0, 0x0060ee,
					font_summaries,
					rufl_WEIGHT_400 | rufl_SLANTED, 160,
					feeds[i].item[j].pub_date,
					0, j);
			if (y1 < p->y1)
				y1 = p->y1;
		}
		if (feeds[i].item[j].author) {
			p = add_paragraph(&feed_window_paragraphs, 420, y, 720,
					0, 0x666666,
					font_summaries,
					rufl_WEIGHT_400 | rufl_SLANTED, 160,
					feeds[i].item[j].author,
					0, j);
			if (y1 < p->y1)
				y1 = p->y1;
		}
		if (feeds[i].item[j].category) {
			p = add_paragraph(&feed_window_paragraphs, 720, y, 1000,
					0, 0xee6000,
					font_summaries, rufl_WEIGHT_400, 160,
					feeds[i].item[j].category,
					0, j);
			if (y1 < p->y1)
				y1 = p->y1;
		}
		y = y1;

		if (feeds[i].item[j].link) {
			p = add_paragraph(&feed_window_paragraphs, 0, y, 1000,
					0, 0xff0000,
					font_links, rufl_WEIGHT_400, 160,
					feeds[i].item[j].link,
					click_item_link, j);
			y = p->y1;
		}

		if (feeds[i].item[j].description) {
			p = add_paragraph(&feed_window_paragraphs, 0, y, 1000,
					0, 0x000000,
					font_summaries, rufl_WEIGHT_400, 180,
					feeds[i].item[j].description,
					0, j);
			y = p->y1;
		}

		y += MARGIN;
	}

	set_extent(feed_window, y);
}


void click_feed_link(unsigned int i)
{
	os_error *error;
	error = xuri_dispatch(0, feeds[i].link, task, 0, 0, 0);
	if (error) {
		LOG(("xuri_dispatch: 0x%x: %s",
				error->errnum, error->errmess));
	}
}


void click_item_link(unsigned int j)
{
	os_error *error;
	error = xuri_dispatch(0, feeds[current_feed].item[j].link, task,
			0, 0, 0);
	if (error) {
		LOG(("xuri_dispatch: 0x%x: %s",
				error->errnum, error->errmess));
	}
}


struct paragraph *add_paragraph(struct paragraph **paragraph_list,
		int x0, int y0, int x1, int background, int colour,
		const char *font_family, rufl_style font_style,
		unsigned int font_size, const char *text,
		click_callback click, unsigned int click_i)
{
	struct paragraph *p;
	const char *t;
	size_t len = strlen(text);
	unsigned int lines = 0;
	size_t char_offset;
	int actual_x;
	rufl_code code;

	p = malloc(sizeof *p);
	if (!p)
		return 0;

	p->x0 = x0;
	p->y0 = y0;
	p->x1 = x1;
	p->background = background;
	p->colour = colour;
	p->font_family = font_family;
	p->font_style = font_style;
	p->font_size = font_size;
	p->click = click;
	p->click_i = click_i;
	p->lines = 0;
	p->text[0] = text;

	t = text;
	while (len && lines != MAX_LINES) {
		code = rufl_split(font_family, font_style, font_size, t, len,
				x1 - x0 - MARGIN - MARGIN,
				&char_offset, &actual_x);
		if (code != rufl_OK) {
			LOG(("rufl_split: %i", code));
			break;
		}
		if (char_offset != len) {
			size_t space;
			for (space = char_offset; space && t[space] != ' ';
					space--)
				continue;
			if (space)
				char_offset = space + 1;
		}
		len -= char_offset;
		t += char_offset;
		p->text[++lines] = t;
	}

	p->y1 = p->y0 + MARGIN + lines * font_size * 0.2 + MARGIN;
	p->lines = lines;

	p->next = *paragraph_list;
	*paragraph_list = p;

	return p;
}


void free_paragraph_list(struct paragraph **paragraph_list)
{
	struct paragraph *p = *paragraph_list;
	struct paragraph *next;

	while (p) {
		next = p->next;
		free(p);
		p = next;
	}

	*paragraph_list = 0;
}


void redraw_window(wimp_draw *redraw, struct paragraph *paragraphs)
{
	int ox = redraw->box.x0 - redraw->xscroll;
	int oy = redraw->box.y1 - redraw->yscroll;
	int clip_x0 = redraw->clip.x0 - ox;
	int clip_y0 = oy - redraw->clip.y1;
	int clip_x1 = redraw->clip.x1 - ox;
	int clip_y1 = oy - redraw->clip.y0;
	struct paragraph *p;
	unsigned int i;
	os_error *error;
	rufl_code code;

	for (p = paragraphs; p; p = p->next) {
		if (p->x1 < clip_x0 || clip_x1 < p->x0 ||
				p->y1 < clip_y0 || clip_y1 < p->y0)
			continue;
		if (p->background)
			fill_rectangle(ox + p->x0, oy - p->y1,
					ox + p->x1, oy - p->y0,
					p->background);
		for (i = 0; i != p->lines; i++) {
			error = xcolourtrans_set_font_colours(font_CURRENT,
					p->background << 8, p->colour << 8,
					14, 0, 0, 0);
			if (error) {
				LOG(("xcolourtrans_set_font_colours: 0x%x: %s",
						error->errnum, error->errmess));
				return;
			}
			code = rufl_paint(p->font_family, p->font_style,
					p->font_size, p->text[i],
					p->text[i + 1] - p->text[i],
					ox + p->x0 + MARGIN,
					oy - p->y0 - MARGIN -
						i * p->font_size * 0.2 -
						p->font_size * 0.15,
					rufl_BLEND_FONT);
			if (code != rufl_OK) {
				LOG(("rufl_paint: %i", code));
				return;
			}
		}
	}
}


void fill_rectangle(int x0, int y0, int x1, int y1, int colour)
{
	os_error *error;

	error = xcolourtrans_set_gcol(colour << 8, colourtrans_USE_ECFS,
			os_ACTION_OVERWRITE, 0, 0);
	if (error) {
		LOG(("xcolourtrans_set_gcol: 0x%x: %s",
				error->errnum, error->errmess));
		return;
	}

	error = xos_plot(os_MOVE_TO, x0, y0);
	if (error) {
		LOG(("xos_plot: 0x%x: %s", error->errnum, error->errmess));
		return;
	}

	error = xos_plot(os_PLOT_RECTANGLE | os_PLOT_TO, x1, y1);
	if (error) {
		LOG(("xos_plot: 0x%x: %s", error->errnum, error->errmess));
		return;
	}
}


void choices_save(void)
{
	FILE *choices;
	char error[200];

	choices = fopen(CHOICES_WRITE, "w");
	if (!choices) {
		LOG(("fopen: %i: %s", errno, strerror(errno)));
		snprintf(error, sizeof error, "Unable to save choices: %s",
				strerror(errno));
		warn(error);
		return;
	}
	fprintf(choices, "interval: %i\n", interval);
	fprintf(choices, "font_headings: %s\n", font_headings);
	fprintf(choices, "font_summaries: %s\n", font_summaries);
	fprintf(choices, "font_links: %s\n", font_links);
	fclose(choices);
}


void choices_load(void)
{
	FILE *choices;
	char error[200];
	char s[100];

	choices = fopen(CHOICES_READ, "r");
	if (!choices) {
		LOG(("fopen: %i: %s", errno, strerror(errno)));
		if (errno != ENOENT) {
			snprintf(error, sizeof error,
					"Unable to read choices: %s",
					strerror(errno));
			warn(error);
		}
		return;
	}
	while (fgets(s, sizeof s, choices)) {
		sscanf(s, "interval: %i", &interval);
		sscanf(s, "font_headings: %s", font_headings);
		sscanf(s, "font_summaries: %s", font_summaries);
		sscanf(s, "font_links: %s", font_links);
	}
	fclose(choices);
}


/**
 * Display an error and exit.
 */

void die(const char *error)
{
	os_error warn_error;

	warn_error.errnum = 1;
	strncpy(warn_error.errmess, error, sizeof warn_error.errmess - 1);
	warn_error.errmess[sizeof warn_error.errmess - 1] = '\0';
	xwimp_report_error_by_category(&warn_error,
			wimp_ERROR_BOX_OK_ICON |
			wimp_ERROR_BOX_GIVEN_CATEGORY |
			wimp_ERROR_BOX_CATEGORY_ERROR <<
					wimp_ERROR_BOX_CATEGORY_SHIFT,
			"Sargasso", "!sargasso",
			(osspriteop_area *) 1, 0, 0);
	exit(EXIT_FAILURE);
}


void warn(const char *warning)
{
	os_error error;

	LOG(("%s", warning));

	if (warning_window) {
		set_icon_string(warning_window, 0, warning);
		open_window(warning_window);
		xwimp_set_caret_position(warning_window, wimp_ICON_WINDOW,
				-100, -100, 1, 0);
		xos_bell();
	} else {
		strncpy(error.errmess, warning, sizeof error.errmess);
		error.errmess[sizeof error.errmess - 1] = 0;
		xwimp_report_error_by_category(&error,
				wimp_ERROR_BOX_OK_ICON |
				wimp_ERROR_BOX_GIVEN_CATEGORY |
				wimp_ERROR_BOX_CATEGORY_ERROR <<
						wimp_ERROR_BOX_CATEGORY_SHIFT,
				"Sargasso", "!sargasso",
				(osspriteop_area *) 1, 0, 0);
	}
}


char *get_icon_string(wimp_w w, wimp_i i)
{
	wimp_icon_state icon_state = {w, i};
	os_error *error;

	error = xwimp_get_icon_state(&icon_state);
	if (error) {
		LOG(("xwimp_get_icon_state: 0x%x: %s",
				error->errnum, error->errmess));
		return "";
	}

	return icon_state.icon.data.indirected_text.text;
}


void set_icon_string(wimp_w w, wimp_i i, const char *text)
{
	wimp_icon_state icon_state = {w, i};
	unsigned int size;
	os_error *error;

	error = xwimp_get_icon_state(&icon_state);
	if (error) {
		LOG(("xwimp_get_icon_state: 0x%x: %s",
				error->errnum, error->errmess));
		return;
	}
	size = (unsigned int) icon_state.icon.data.indirected_text.size;

	if (!strncmp(icon_state.icon.data.indirected_text.text, text, size))
		return;

	strncpy(icon_state.icon.data.indirected_text.text, text, size);
	icon_state.icon.data.indirected_text.text[size - 1] = '\0';

	xwimp_set_icon_state(w, i, 0, 0);
}
