/*
 *  tenace - bridge hand viewer and editor
 *  Copyright (C) 2005-2013 Christoph Berg <cb@df7cb.de>
 *
 *  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.
 */

#define _GNU_SOURCE

#include <math.h>
#include <ctype.h>
#include <errno.h>
#include <locale.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "bridge.h"
#include "functions.h"
#include "window_card.h"
#include "file.h"

static void
add_filters (GtkFileChooser *dialog)
{
	GtkFileFilter *filter = gtk_file_filter_new ();
	gtk_file_filter_set_name (filter, _("Bridge files"));
	gtk_file_filter_add_pattern (filter, "*.lin");
	gtk_file_filter_add_pattern (filter, "*.pbn");
	gtk_file_chooser_add_filter (dialog, filter);
	filter = gtk_file_filter_new ();
	gtk_file_filter_set_name (filter, _("LIN files"));
	gtk_file_filter_add_pattern (filter, "*.lin");
	gtk_file_chooser_add_filter (dialog, filter);
	filter = gtk_file_filter_new ();
	gtk_file_filter_set_name (filter, _("PBN files"));
	gtk_file_filter_add_pattern (filter, "*.pbn");
	gtk_file_chooser_add_filter (dialog, filter);
	filter = gtk_file_filter_new ();
	gtk_file_filter_set_name (filter, _("All files"));
	gtk_file_filter_add_pattern (filter, "*");
	gtk_file_chooser_add_filter (dialog, filter);
}

/*
 * loading
 */

static char *sane_strtok_r (char *const str, const char *delim, char **saveptr)
{
	char *this = str ? str : *saveptr;
	if (!this)
		return NULL;

	char *next = strpbrk (this, delim);
	if (next) {
		*next = '\0';
		*saveptr = next + 1;
	} else {
		*saveptr = NULL;
	}
	return this;
}

// pn|Frederic,gm,Myon,mecky|st||md|4S27KAHKD49C3589TJ,S36H48AD358KC27QK,STJH259TJQD26TJQC,|rh||ah|Board 14|sv|o|mb|p|mb|p|mb|1D|mb|2H|mb|2S|mb|p|mb|p|mb|p|pg||pc|HK|pc|H4|pc|H2|pc|H6|pg||pc|CJ|pc|CK|pc|ST|pc|C4|pg||pc|H9|pc|H3|pc|S2|pc|H8|pg||pc|CT|pc|C2|pc|SJ|pc|C6|pg||pc|HQ|pc|H7|pc|S7|pc|HA|pg||pc|D9|pc|D3|pc|DT|pc|DA|pg||pc|S4|pc|SK|pc|S3|pc|H5|pg||pc|SA|pc|S6|pc|HT|pc|S5|pg||pc|D4|pc|DK|pc|D2|pc|D7|pg||pc|C7|pc|D6|pc|CA|pc|C3|pg||mc|6|
#define STRTOK sane_strtok_r(NULL, "|\n\r", &saveptr)
static int
board_parse_lin (window_board_t *win, char *line, FILE *f)
{
	char *saveptr;
	char *tok;
	int card_nr = 0;

	setlocale (LC_NUMERIC, "C");

	board *b = board_new (win->n_boards + 1);
	int board_filled = 0;
	board_window_append_board (win, b);

	/* global list of names for vugraph files */
	char *name_arr[8] = { 0, 0, 0, 0, 0, 0, 0, 0 };
	int name_n = 0;
	/* IMP/MP list */
	char *mp_str = NULL;
	char *mp_ptr = NULL;

	do {
	for (tok = sane_strtok_r(line, "|", &saveptr); tok; tok = STRTOK) {
		/* single hand */
		if (!strcmp(tok, "pn")) { /* SWNE */
			tok = STRTOK;
			char *nameptr;
			char *name = sane_strtok_r(tok, ",", &nameptr);
			int i = 0;
			do {
				if (i < 4)
					g_string_printf(b->hand_name[seat_mod (i++) - 1], "%s", name);
				if (name_n < 8)
					name_arr[name_n++] = strdup(name);
			} while ((name = sane_strtok_r(NULL, ",", &nameptr)));

		} else if (!strcmp(tok, "md")) { /* make deal */
			tok = STRTOK;
			if (*tok == '0') {
				printf ("md|0| (keep deal) mode not supported\n");
				continue;
			}
			if (board_filled) {
				/* start new board */
				card_nr = 0;
				board_filled = 0;

				b = board_new (win->n_boards + 1);
				board_window_append_board (win, b);
				/* initialize player names, required for vugraph files */
				int i;
				for (i = 0; i < 4; i++) {
					g_string_printf(b->hand_name[seat_mod (i) - 1],
						"%s", name_arr[i + (tok[0] == 'c' ? 4 : 0)]);
				}
			}
			char *c;
			seat se = south;
			suit su = spade;
			b->dealer = seat_mod(*tok - '0' - 1);
			for (c = tok + 1; *c; c++) {
				int i;
				if ((i = parse_suit(*c)) != -1) {
					su = i;
				} else if ((i = parse_rank_char(*c)) != -1) {
					if (add_card(b, se, (su * 13) + i) != 1)
						goto error;
				} else if (*c == ',') {
					se = seat_mod(se + 1);
				} else {
					printf("Parse error: %s", tok);
					goto error;
				}
			}
			// TODO: end positions
			deal_random(b); /* compute east hand */
			board_filled = 1; /* consider this board finished on next qx token */

		} else if (!strcmp(tok, "ah")) { /* board name */
			g_string_printf(b->name, "%s", STRTOK);

		} else if (!strcmp(tok, "qx")) { /* board number, o1, c1, o2, ... */
			tok = STRTOK;
			if (board_filled) { /* first token in new vugraph board */
				card_nr = 0;
				board_filled = 0;

				b = board_new (win->n_boards + 1);
				board_window_append_board (win, b);
				/* initialize player names, required for vugraph files */
				int i;
				for (i = 0; i < 4; i++) {
					g_string_printf(b->hand_name[seat_mod (i) - 1],
						"%s", name_arr[i + (tok[0] == 'c' ? 4 : 0)]);
				}
			}
			if (strlen (tok) >= 1)
				g_string_printf(b->name, "%s %s",
					tok[0] == 'c' ? _("Closed") :
						(tok[0] == 'o' ? _("Open") : _("Board")),
					tok + 1);

			/* for now assume qx|| is present in all lin files with mp|| */
			if (mp_str) {
				// FIXME: skip leading boards that were kibitzed but not played
				// (01-26-08-3.lin)
				char *score = mp_ptr ? sane_strtok_r (NULL, ",", &mp_ptr) :
						sane_strtok_r (mp_str, ",", &mp_ptr);
				b->mp[0] = score ? round (strtod (score, NULL) * 100.0) : 0;
				score = sane_strtok_r (NULL, ",", &mp_ptr);
				b->mp[1] = score ? round (strtod (score, NULL) * 100.0) : 0;
			}

		} else if (!strcmp(tok, "sv")) {
			tok = STRTOK;
			switch (*tok) {
				case '0':
				case 'o': b->vuln[0] = 0; b->vuln[1] = 0; break;
				case 'n': b->vuln[0] = 1; b->vuln[1] = 0; break;
				case 'e': b->vuln[0] = 0; b->vuln[1] = 1; break;
				case 'b': b->vuln[0] = 1; b->vuln[1] = 1; break;
				default: printf("Unknown vulnerability: sv|%s|\n", tok);
			}

		} else if (!strcmp(tok, "mb")) {
			/* mb|-ppp1Cp1Hp3Np4Dp4Hppp| */
			tok = STRTOK;
			char *bidp = tok;
			char *al = strchr (bidp, '!');
			if (al) {
				*al++ = '\0';
			}
			do {
				int bid = parse_bid(&bidp);
				if (bid == -1) {
					printf("Invalid bid %s/%s\n", tok, bidp);
					break;
				}
				board_append_bid(b, bid, 1);
				if (al) {
					board_set_alert (b, al);
					al = NULL;
				}
			} while (*bidp);

		} else if (!strcmp(tok, "an")) {
			tok = STRTOK;
			board_set_alert (b, !strcmp (tok, "!") ? "" : tok); /* filter uninteresting ! */

		} else if (!strcmp(tok, "pc")) {
			int c = parse_card(tok = STRTOK);
			if (c == -1) {
				printf("Invalid card %s\n", tok);
				continue;
			}
			if (card_nr < 52)
				b->played_cards[card_nr++] = c;

		} else if (!strcmp(tok, "mc")) {
			tok = STRTOK; // TODO: store number of (total) claimed tricks
			b->played_cards[card_nr] = claim_rest; // no card_nr increment here
			b->declarer_tricks = atoi (tok);

		/* vugraph file */
		} else if (!strcmp(tok, "vg")) { /* match title */
			tok = STRTOK;
			//printf ("Match title: %s\n", tok);
			if (win->title)
				free (win->title);
			if (win->subtitle)
				free (win->subtitle);
			if (win->team1)
				free (win->team1);
			if (win->team2)
				free (win->team2);

			char *t_ptr;
			char *title = sane_strtok_r (tok, ",", &t_ptr);
			if (title)
				win->title = strdup (title);
			char *subtitle = sane_strtok_r (NULL, ",", &t_ptr);
			if (subtitle)
				win->subtitle = strdup (subtitle);
			sane_strtok_r (NULL, ",", &t_ptr); /* scoring I IMPs P MPs B board-a-match */
			sane_strtok_r (NULL, ",", &t_ptr); /* first board nr */
			sane_strtok_r (NULL, ",", &t_ptr); /* last board nr */
			char *team1 = sane_strtok_r (NULL, ",", &t_ptr);
			if (team1)
				win->team1 = strdup (team1);
			sane_strtok_r (NULL, ",", &t_ptr); /* carry-over score team 1 */
			char *team2 = sane_strtok_r (NULL, ",", &t_ptr);
			if (team2)
				win->team2 = strdup (team2);
			/* carry-over score team 2 */

		} else if (!strcmp(tok, "pw")) { /* more player names */
			tok = STRTOK;
			//printf ("Players: %s\n", tok);
		} else if (!strcmp(tok, "bn")) { /* board numbers */
			tok = STRTOK;
			//printf ("Board numbers: %s\n", tok);
		} else if (!strcmp(tok, "rs")) { /* results */
			tok = STRTOK;
			//printf ("Results: %s\n", tok);
		} else if (!strcmp(tok, "mp")) { /* MP result */
			tok = STRTOK;
			//printf ("Scores: %s\n", tok);
			mp_str = strdup (tok);
		} else if (!strcmp(tok, "nt")) { /* comment (new text) */
			tok = STRTOK;
			//printf ("Comment: %s\n", tok);

		} else if (!strcmp(tok, "at")) { /* add text */
			STRTOK;
		} else if (!strcmp(tok, "cr") || !strcmp(tok, "cg") ||
			   !strcmp(tok, "cb")) { /* color */
			STRTOK;
		} else if (!strcmp(tok, "hc") || !strcmp(tok, "lc") ||
			   !strcmp(tok, "hs") || !strcmp(tok, "ls")) {
			STRTOK; /* hilight card, suit */
		} else if (!strcmp(tok, "pg")) {
			STRTOK; /* page break, e.g. after trick or comment */
		} else if (!strcmp(tok, "rh")) { /* reset heading */
			STRTOK;
		} else if (!strcmp(tok, "sk")) { /* set kibitzed */
			STRTOK;
		} else if (!strcmp(tok, "st")) { /* small text */
			STRTOK;
		} else if (!strcmp(tok, "up")) { /* undo play */
			tok = STRTOK;
		} else if (!*tok || *tok == '\n' || *tok == '\r') {
			/* empty token, hopefully end of line */
		} else {
			printf("Unknown token '%s|%s|'\n", tok, STRTOK);
		}
	}
	} while (fgets(line, 1023, f));

	int ret = 1;
	int i;
	goto ok;
error:
	ret = 0;
ok:
	for (i = 0; i < name_n; i++)
		free (name_arr[i]);
	if (mp_str)
		free (mp_str);

	setlocale (LC_NUMERIC, "");

	return ret;
}
#undef STRTOK

int board_parse_line(const char *line, board *b, char handsep, char suitsep)
{
	const char *c = line;
	seat se = west;
	int su = spade;
	while (*c && *c != '\n') {
		int ra;
		if (*c == suitsep) {
			su--;
			if (su < 0)
				return 0;
		} else if (*c == handsep) {
			se++;
			su = spade;
			if (se > south)
				return 0;
		} else if ((ra = parse_rank_char(*c)) >= 0) {
			if (add_card(b, se, (su * 13) + ra) != 1)
				return 0;
		} else {
			printf ("parse error at char %ld: %s\n", c - line + 1, line);
			return 0;
		}
		c++;
	}
	return 1;
}

int
board_load (window_board_t *win, char *fname)
{
	FILE *f;
	char buf[1024];
	if (! (f = fopen (fname, "r")))
		return 0;
	if (fgets (buf, 1023, f) == NULL)
		return 0;

	int ret;
	if (!strncmp(buf, "pn|", 3) || !strncmp(buf, "vg|", 3) ||
	    !strncmp(buf, "st|", 3) || !strncmp(buf, "qx|", 3)) {
		ret = board_parse_lin (win, buf, f);
	} else {
		board *b = board_new (win->n_boards + 1);
		ret = board_parse_line(buf, b, ' ', '.');
		if (ret)
			board_window_append_board (win, b);
		else {
			errno = EMEDIUMTYPE;
			board_free (b);
		}
	}
	int e = errno;
	fclose(f);
	if (ret) {
		if (win->filename)
			free (win->filename);
		win->filename = strdup (fname);
	}
	errno = e;
	return ret;
}

#define TRY_FREE(p) if (p) free (p)
#define MOVE_PTR(dst, src) TRY_FREE (dst); dst = src; src = NULL

/* tries opening a file in a temporary win structure, and displays errors if unable */
int
board_load_popup (window_board_t *win, int append, char *filename)
{
	int i;
	int ret = 0;
	window_board_t *win1 = calloc (1, sizeof (window_board_t));

	if (board_load (win1, filename)) {
		if (append) {
			int n = win->n_boards;
			for (i = 0; i < win1->n_boards; i++)
				board_window_append_board (win, win1->boards[i]);
			if (win->n_boards > n) /* set to first new board */
				win->cur = n;
		} else {
			MOVE_PTR (win->filename, win1->filename);
			MOVE_PTR (win->title, win1->title);
			MOVE_PTR (win->subtitle, win1->subtitle);
			MOVE_PTR (win->team1, win1->team1);
			MOVE_PTR (win->team2, win1->team2);

			for (i = 0; i < win->n_boards; i++)
				if (win->boards[i])
					board_free (win->boards[i]);
			win->boards = win1->boards;
			win->n_boards = win1->n_boards;
			win->n_boards_alloc = win1->n_boards_alloc;
			win->cur = 0;
		}

		card_window_update(win->boards[win->cur]->dealt_cards);
		show_board(win->boards[win->cur], REDRAW_FULL);
		recently_used_add (filename);

		ret = 1;
	} else {
		GtkWidget *error = gtk_message_dialog_new (GTK_WINDOW (win->window),
				GTK_DIALOG_DESTROY_WITH_PARENT,
				GTK_MESSAGE_ERROR,
				GTK_BUTTONS_CLOSE,
				_("Error loading file '%s': %s"),
				filename, g_strerror (errno));
		gtk_dialog_run (GTK_DIALOG (error));
		gtk_widget_destroy (error);
	}

	TRY_FREE (win1->filename);
	TRY_FREE (win1->title);
	TRY_FREE (win1->subtitle);
	TRY_FREE (win1->team1);
	TRY_FREE (win1->team2);
	free (win1);

	return ret;
}

int
board_load_dialog (window_board_t *win, int append)
{
	GtkWidget *dialog;

	dialog = gtk_file_chooser_dialog_new (_("Open File"),
			GTK_WINDOW (win->window),
			GTK_FILE_CHOOSER_ACTION_OPEN,
			GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
			GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT,
			NULL);
	add_filters (GTK_FILE_CHOOSER (dialog));

	if (win->filename) {
		char *cwd = g_path_get_dirname (win->filename);
		gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (dialog), cwd);
		g_free (cwd);
		gtk_file_chooser_set_filename (GTK_FILE_CHOOSER (dialog), win->filename);
	}

	int ret = 0;
	while (ret == 0) {
		if (gtk_dialog_run (GTK_DIALOG (dialog)) != GTK_RESPONSE_ACCEPT)
			break;
		char *filename = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (dialog));
		ret = board_load_popup (win, append, filename);
		g_free (filename);
	}

	gtk_widget_destroy (dialog);
	return ret;
}

/*
 * saving
 */

GString *board_format_line(board *b, char handsep, char suitsep)
{
	GString *out = g_string_new(NULL);

	int h;
	for (h = 1; h < 5; h++) {
		int c;
		for (c = 51; c >= 39; c--)
			if (b->dealt_cards[c] == h)
				g_string_append_printf(out, "%c", rank_char(RANK(c)));
		g_string_append_printf(out, "%c", suitsep);
		for (c = 38; c >= 26; c--)
			if (b->dealt_cards[c] == h)
				g_string_append_printf(out, "%c", rank_char(RANK(c)));
		g_string_append_printf(out, "%c", suitsep);
		for (c = 25; c >= 13; c--)
			if (b->dealt_cards[c] == h)
				g_string_append_printf(out, "%c", rank_char(RANK(c)));
		g_string_append_printf(out, "%c", suitsep);
		for (c = 12; c >= 0; c--)
			if (b->dealt_cards[c] == h)
				g_string_append_printf(out, "%c", rank_char(RANK(c)));

		if (h < 4)
			g_string_append_printf(out, "%c", handsep);
	}
	return out;
}

static char *lin_card_string(board *b)
{
	static char out[39+16+4+1];
	int i = 0, h;
	for (h = 4; h != 3; h = (h == 4 ? 1 : h + 1)) {
		int c;
		out[i++] = 'S';
		for (c = 39; c < 52; c++)
			if (b->dealt_cards[c] == h) out[i++] = rank_char(RANK(c));
		out[i++] = 'H';
		for (c = 26; c < 39; c++)
			if (b->dealt_cards[c] == h) out[i++] = rank_char(RANK(c));
		out[i++] = 'D';
		for (c = 13; c < 26; c++)
			if (b->dealt_cards[c] == h) out[i++] = rank_char(RANK(c));
		out[i++] = 'C';
		for (c = 0; c < 13; c++)
			if (b->dealt_cards[c] == h) out[i++] = rank_char(RANK(c));
		out[i++] = ',';
	}
	return out;
}

static char *lin_bid(card bid)
{
	static char buf[3];
	if (bid == bid_pass)
		return "p";
	else if (bid == bid_x)
		return "d";
	else if (bid == bid_xx)
		return "r";
	snprintf(buf, sizeof (buf), "%d%c", LEVEL(bid), "CDHSN"[DENOM(bid)]);
	return buf;
}

static char *
lin_contract (board *b)
{
	static char buf[10];
	if (b->level == 0) {
		snprintf (buf, sizeof (buf), "P");
	} else {
		snprintf (buf, sizeof (buf), "%d%s%s%s%s",
			b->level, trump_str_char[b->trumps],
			seat_str[b->declarer], double_str[b->doubled],
			overtricks (b->declarer_tricks - b->level - 6));
	}
	return buf;
}

static int
board_save_lin(window_board_t *win, char *filename)
{
	int cur;
	FILE *f;
	if ((f = fopen (filename, "w")) == NULL)
		return 0;

	setlocale (LC_NUMERIC, "C");

	if (win->title) {
		fprintf (f, "vg|%s,%s,%s,%d,%d,%s,,%s,|\n",
			win->title,
			win->subtitle ? win->subtitle : "",
			"P", // FIXME
			1, win->n_boards,
			win->team1 ? win->team1 : "",
			win->team2 ? win->team2 : "");

		fprintf (f, "rs|");
		for (cur = 0; cur < win->n_boards; cur++) {
			board *b = win->boards[cur];
			fprintf (f, "%s,", lin_contract (b));
			if (cur != win->n_boards - 1)
				fprintf (f, ",");
		}
		fprintf (f, "|\n");

		fprintf (f, "pw|");
		for (cur = 0; cur < win->n_boards; cur++) {
			board *b = win->boards[cur];
			fprintf (f, "%s,%s,%s,%s",
				b->hand_name[south-1]->str, b->hand_name[west-1]->str,
				b->hand_name[north-1]->str, b->hand_name[east-1]->str);
			if (cur != win->n_boards - 1)
				fprintf (f, ",");
		}
		fprintf (f, "|\n");

		fprintf (f, "mp|");
		for (cur = 0; cur < win->n_boards; cur++) {
			board *b = win->boards[cur];
			if (b->mp[0] == 0 && b->mp[1] == 0)
				fprintf (f, "--,--");
			else {
				if (b->mp[0])
					fprintf (f, "%.2f", b->mp[0] / 100.0);
				fprintf (f, ",");
				if (b->mp[1])
					fprintf (f, "%.2f", b->mp[1] / 100.0);
			}
			if (cur != win->n_boards - 1)
				fprintf (f, ",");
		}
		fprintf (f, "|\n");

		fprintf (f, "bn|");
		for (cur = 0; cur < win->n_boards; cur++) {
			//board *b = win->boards[cur];
			fprintf (f, "%d", cur + 1); // TODO: original number?
			if (cur != win->n_boards - 1)
				fprintf (f, ",");
		}
		fprintf (f, "|\n");

		fprintf (f, "pg||\n");
	}

	for (cur = 0; cur < win->n_boards; cur++) {
		board *b = win->boards[cur];
		int i;

		if (win->n_boards > 1)
			fprintf (f, "qx|o%d|", cur + 1); // TODO: open/closed, real board number

		fprintf (f, "pn|%s,%s,%s,%s|",
			b->hand_name[south-1]->str, b->hand_name[west-1]->str,
			b->hand_name[north-1]->str, b->hand_name[east-1]->str);
		fprintf (f, "st||");
		fprintf (f, "md|%d%s|", seat_mod(b->dealer + 1), lin_card_string(b)); // TODO: end positions
		fprintf (f, "rh||");
		fprintf (f, "ah|%s|", b->name->str);
		fprintf (f, "sv|%c|", b->vuln[0] ? (b->vuln[1] ? 'b' : 'n')
						: (b->vuln[1] ? 'e' : 'o'));
		for (i = 0; i < b->n_bids; i++) {
			fprintf (f, "mb|%s|", lin_bid(b->bidding[i]));
			if (b->alerts[i])
				fprintf (f, "an|%s|", *b->alerts[i] ? b->alerts[i] : "!");
		}
		for (i = 0; i < 52; i++) {
			if (i % 4 == 0)
				fprintf (f, "pg||");
			card c = b->played_cards[i];
			if (c < 0)
				break;
			if (c == claim_rest) {
				fprintf (f, "mc|%d|",
					b->declarer_tricks >= 0 ? b->declarer_tricks : 0);
				break;
			}
			fprintf (f, "pc|%c%c|", "CDHS"[SUIT(c)], rank_char(RANK(c)));
		}
		fprintf (f, "pg||\n");
	}

	int ret = 1, e = 0;
	if (ferror (f)) {
		ret = 0;
		e = errno;
	}
	fclose(f);
	errno = e;

	setlocale (LC_NUMERIC, "");

	return ret;
}

int
board_save(window_board_t *win, char *filename)
{
	int len = strlen(filename);
	char *suffix = len > 4 ? filename + len - 4 : filename;
	int ret = 0;
	if (!strcmp(suffix, ".lin")) {
		ret = board_save_lin (win, filename);
	} else if (!strcmp(suffix, ".pbn")) {
		printf("pbn not yet implemented\n");
		errno = EMEDIUMTYPE;
	} else {
		printf("unrecognized suffix %s\n", suffix);
		errno = EMEDIUMTYPE;
	}
	return ret;
}

int
board_save_dialog (window_board_t *win, int save_as)
{
	GtkWidget *dialog;

	if (!save_as && win->filename) {
		int ret = board_save(win, win->filename);
		if (! ret) {
			GtkWidget *error = gtk_message_dialog_new (GTK_WINDOW (win->window),
					GTK_DIALOG_DESTROY_WITH_PARENT,
					GTK_MESSAGE_ERROR,
					GTK_BUTTONS_CLOSE,
					_("Error saving file '%s': %s"),
					win->filename, g_strerror (errno));
			gtk_dialog_run (GTK_DIALOG (error));
			gtk_widget_destroy (error);
		}
		return ret;
	}

	dialog = gtk_file_chooser_dialog_new (_("Save File"),
			GTK_WINDOW (win->window),
			GTK_FILE_CHOOSER_ACTION_SAVE,
			GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
			GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT,
			NULL);
	gtk_file_chooser_set_do_overwrite_confirmation (GTK_FILE_CHOOSER (dialog), TRUE);
	add_filters (GTK_FILE_CHOOSER (dialog));

	if (!win->filename)
		gtk_file_chooser_set_current_name (GTK_FILE_CHOOSER (dialog), _("hand.lin"));
	else
		gtk_file_chooser_set_filename (GTK_FILE_CHOOSER (dialog), win->filename);

retry_save:
	if (gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_ACCEPT) {
		char *filename;

		filename = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (dialog));
		int ret = board_save (win, filename);

		if (ret) {
			if (win->filename)
				free (win->filename);
			win->filename = filename;
			show_board (CUR_BOARD, REDRAW_TITLE);
			recently_used_add (filename);
		} else {
			GtkWidget *error = gtk_message_dialog_new (GTK_WINDOW (win->window),
					GTK_DIALOG_DESTROY_WITH_PARENT,
					GTK_MESSAGE_ERROR,
					GTK_BUTTONS_CLOSE,
					_("Error saving file '%s': %s"),
					filename, g_strerror (errno));
			g_free (filename);
			gtk_dialog_run (GTK_DIALOG (error));
			gtk_widget_destroy (error);
			goto retry_save;
		}
	}

	gtk_widget_destroy (dialog);
	return 1;
}

/* open on bridgebase.com */

/* non-lin interface
static char seat_lc[] = { 0, 'w', 'n', 'e', 's' };
static char vuln_lc[] = { 'o', 'n', 'e', 'b' };
*/

void
on_menu_file_web_activate ()
{
	board *b = CUR_BOARD;
	GString *url = g_string_new ("http://www.bridgebase.com/tools/handviewer.html?");

	/* non-lin interface
	int h;

	g_string_append_printf (url, "d=%c", seat_lc[b->dealer]);
	g_string_append_printf (url, "&v=%c", vuln_lc[b->vuln[0] + 2 * b->vuln[1]]);
	g_string_append_printf (url, "&n=%d", b->n + 1); // TODO: real board number

	for (h = west; h <= south; h++) {
		if (*b->hand_name[h - 1]->str)
			g_string_append_printf (url, "&%cn=%s", seat_lc[h], b->hand_name[h - 1]->str);

		g_string_append_printf (url, "&%c=", seat_lc[h]);
		int s;
		for (s = club; s <= spade; s++) {
			g_string_append_printf (url, "%s", trump_str_char[s]);
			int c;
			for (c = s * 13 + 12; c >= (int)s * 13; c--)
				if (b->dealt_cards[c] == h)
					g_string_append_printf(url, "%c", rank_char(RANK(c)));
		}
	}

	if (b->n_bids) {
		g_string_append_printf (url, "&a=");
		int i;
		for (i = 0; i < b->n_bids; i++)
			g_string_append_printf (url, "%s", lin_bid (b->bidding[i]));
	}

	if (b->n_played_cards) {
		g_string_append_printf (url, "&p=");
		int i;
		for (i = 0; i < b->n_played_cards; i++)
			g_string_append_printf (url, "%s%c",
					trump_str_char[SUIT(b->played_cards[i])], rank_char(RANK(b->played_cards[i])));
		if (b->played_cards[b->n_played_cards] = claim_rest)
			g_string_append_printf (url, "&c=%d", b->declarer_tricks);
	}
	*/

	int i;

	// TODO: merge with code from board_save_lin
	g_string_append_printf (url, "lin=pn|%s,%s,%s,%s|",
			b->hand_name[south-1]->str, b->hand_name[west-1]->str,
			b->hand_name[north-1]->str, b->hand_name[east-1]->str);
	g_string_append_printf (url, "st||");
	g_string_append_printf (url, "md|%d%s|", seat_mod(b->dealer + 1), lin_card_string(b)); // TODO: end positions
	g_string_append_printf (url, "rh||");
	g_string_append_printf (url, "ah|%s|", b->name->str);
	g_string_append_printf (url, "sv|%c|", b->vuln[0] ? (b->vuln[1] ? 'b' : 'n')
			: (b->vuln[1] ? 'e' : 'o'));
	for (i = 0; i < b->n_bids; i++) {
		g_string_append_printf (url, "mb|%s|", lin_bid(b->bidding[i]));
		if (b->alerts[i])
			g_string_append_printf (url, "an|%s|", *b->alerts[i] ? b->alerts[i] : "!");
	}
	for (i = 0; i < 52; i++) {
		if (i % 4 == 0)
			g_string_append_printf (url, "pg||");
		card c = b->played_cards[i];
		if (c < 0)
			break;
		if (c == claim_rest) {
			g_string_append_printf (url, "mc|%d|",
					b->declarer_tricks >= 0 ? b->declarer_tricks : 0);
			break;
		}
		g_string_append_printf (url, "pc|%c%c|", "CDHS"[SUIT(c)], rank_char(RANK(c)));
	}
	g_string_append_printf (url, "pg||\n");

	printf ("%s\n", url->str);

	GError *error = NULL;
	gtk_show_uri (gdk_screen_get_default (), url->str, GDK_CURRENT_TIME, &error);
	if (error) {
		printf ("%s\n", error->message);
		g_error_free (error);
	}
	g_string_free (url, TRUE);
}
