/*
 *  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.
 */

#include <assert.h>
#include <ctype.h>
#include <stdlib.h>
#include <string.h>

#include "bridge.h"
#include "functions.h"
#include "solve.h"
#include "window_board.h"

/* bridge math */

void calculate_target(board *b)
{
	int side = b->declarer % 2;
	b->target[side] = b->level + 6;
	b->target[1 - side] = 14 - b->target[side]; /* 1 more to beat contract */
}

int
card_overtricks (board *b, card c)
{
	assert (b->current_dd && b->current_dd->card_score[c] >= 0);

	return b->current_dd->card_score[c] - 6 - b->level;
}

int
card_is_good (board *b, card c)
{
	assert (b->current_dd && b->current_dd->card_score[c] >= 0);

	int side = b->current_turn % 2;
	if (side == (b->declarer % 2))
		return b->current_dd->card_score[c] >= b->target[side];
	else
		return 13 - b->current_dd->card_score[c] >= b->target[side];

	/* NOT REACHED */
}

static int
ns_is_vulnerable (int board_number)
{
	switch (board_number % 16) {
		case 2:
		case 4:
		case 5:
		case 7:
		case 10:
		case 12:
		case 13:
		case 15:
			return 1;
			break;
	}
	return 0;
}

static int
ew_is_vulnerable (int board_number)
{
	switch (board_number % 16) {
		case 0: /* actually 16 */
		case 3:
		case 4:
		case 6:
		case 7:
		case 9:
		case 10:
		case 13:
			return 1;
			break;
	}
	return 0;
}

/* dealing with boards */

void board_clear(board *b)
{
	b->n_dealt_cards = b->n_played_cards = 0;
	int i;
	for (i = 0; i < 52; i++) {
		b->cards[i] = 0;
		b->dealt_cards[i] = 0;
		if (b->next_dd[i]) {
			free (b->next_dd[i]);
			b->next_dd[i] = NULL;
		}
		b->played_cards[i] = -1;
	}
	for (i = 0; i < 4; i++)
		b->hand_cards[i] = 0;
	b->current_turn = seat_mod(b->declarer + 1);
	b->tricks[0] = b->tricks[1] = 0;
	b->declarer_tricks = -1;

	if (b->current_dd) {
		free (b->current_dd);
		b->current_dd = NULL;
	}
	b->par_score = -1;
	b->par_dec = b->par_suit = b->par_level = b->par_tricks = 0;
}

void board_set_contract(board *b, int level, suit trumps, seat declarer, int doubled)
{
	b->level = level;
	b->trumps = trumps;
	b->declarer = declarer;
	b->current_turn = seat_mod(declarer + 1);
	b->doubled = doubled;
	calculate_target(b);
}

board *board_new (int board_number) /* user-visible board number 1.. */
{
	int i;
	char *names[] = {"West", "North", "East", "South"};
	board *b = calloc (1, sizeof(board));
	assert(b);

	b->name = g_string_new(NULL);
	/* newly created board */
	g_string_printf (b->name, _("Board %d"), board_number);
	for (i = 0; i < 4; i++) {
		b->hand_name[i] = g_string_new (_(names[i]));
	}

	b->dealer = DEALER (board_number);
	board_set_contract(b, 1, NT, south, 0); /* default contract is 1NT by south */
	b->current_dd = NULL;
	for (i = 0; i < 52; i++) {
		b->next_dd[i] = NULL;
	}
	board_clear(b);
	b->vuln[0] = ns_is_vulnerable (board_number);
	b->vuln[1] = ew_is_vulnerable (board_number);

	b->bidding = calloc(4, sizeof(card));
	assert(b->bidding);
	b->alerts = calloc(4, sizeof(char *));
	assert(b->alerts);
	b->n_bids = 0;
	b->n_bid_alloc = 4;

	return b;
}

board *
board_dup (board *b0)
{
	int i;
	board *b = malloc(sizeof(board));
	assert(b);

	*b = *b0; /* copy "real" struct members */

	b->name = g_string_new(b0->name->str);
	for (i = 0; i < 4; i++) {
		b->hand_name[i] = g_string_new (b0->hand_name[i]->str);
	}

	b->current_dd = NULL;
	for (i = 0; i < 52; i++) {
		b->next_dd[i] = NULL;
	}

	b->n_bids = 0;
	b->bidding = calloc(b0->n_bid_alloc, sizeof(card));
	assert(b->bidding);
	b->alerts = calloc(b0->n_bid_alloc, sizeof(char *));
	assert(b->alerts);

	for (i = 0; i < b0->n_bids; i++) {
		board_append_bid (b, b0->bidding[i], 0);
		board_set_alert (b, b0->alerts[i]);
	}

	return b;
}

void board_free(board *b)
{
	int i;
	assert(b);
	g_string_free(b->name, TRUE);
	for (i = 0; i < 4; i++) {
		g_string_free(b->hand_name[i], TRUE);
	}
	free(b->bidding);
	for (i = 0; i < b->n_bid_alloc; i++)
		if (b->alerts[i])
			free (b->alerts[i]);
	free (b->alerts);
}

/* dealing with cards */

int assert_board(board *b) /* check proper number of cards in hands */
{
	int i;
	for (i = 1; i < 4; i++)
		if (b->hand_cards[0] != b->hand_cards[i])
			return 0;
	return 1;
}

int add_card(board *b, seat s, card c)
/* return: 1 = card added */
{
	if (b->dealt_cards[c] != 0)
		return 0;
	if (b->hand_cards[s-1] == 13)
		return 0;

	b->cards[c] = s;
	b->dealt_cards[c] = s;
	b->n_dealt_cards++;
	b->hand_cards[s-1]++;

	invalidate_dd_scores (b);
	b->par_score = -1;

	return 1;
}

int remove_card(board *b, seat s, card c)
/* return: 1 = card removed */
{
	assert (b->dealt_cards[c] == s);

	b->cards[c] = 0;
	b->dealt_cards[c] = 0;
	b->n_dealt_cards--;
	b->hand_cards[s-1]--;

	invalidate_dd_scores (b);
	b->par_score = -1;

	return 1;
}

int
flip_hands (board *b, seat h1, seat h2)
{
	int cards[13];
	int n_cards = 0;
	int c, i;
	for (c = 0; c < 52; c++) {
		if (b->cards[c] == h1) {
			cards[n_cards++] = c;
			remove_card (b, h1, c);
		}
	}
	for (c = 0; c < 52; c++) {
		if (b->cards[c] == h2) {
			remove_card (b, h2, c);
			if (!add_card (b, h1, c)) {
				add_card (b, h2, c);
				break;
			}
		}
	}
	for (i = 0; i < n_cards; i++) {
		if (!add_card (b, h2, cards[i]))
			break;
		cards[i] = -1;
	}
	for (i = 0; i < n_cards; i++) {
		if (cards[i] >= 0) {
			c = add_card (b, h1, cards[i]);
			assert (c);
		}
	}

	invalidate_dd_scores (b);
	b->par_score = -1; /* invalidate par score */

	return 1;
}

void deal_random(board *b)
{
	seat s;
	for (s = west; s <= south; s++) {
		while (b->hand_cards[s-1] < 13) {
			int c = rand() % 52;
			if (b->dealt_cards[c] == 0) {
				int ret = add_card(b, s, c);
				assert(ret);
			}
		}
	}
}

/* playing */

static int has_suit(seat *cards, seat h, suit s)
{
	int i;
	for (i = 0; i < 52; i++)
		if (SUIT(i) == s && cards[i] == h)
			return 1;
	return 0;
}

static void play_card_0(board *b, seat s, card c)
{
	assert (s);
	assert (b->dealt_cards[c] == s);
	assert (b->cards[c] == s);

	b->cards[c] = 0;

	b->played_cards[b->n_played_cards] = c;
	b->n_played_cards++;
}

int play_card(board *b, seat s, card c)
{
	board_statusbar(NULL);

	if (b->cards[c] == 0) {
		board_statusbar(_("Card is not in your hand"));
		return 0;
	}

	if (b->cards[c] != b->current_turn) {
		board_statusbar(_("Not your turn"));
		return 0;
	}

	int firstcard = 0;
	card lead = 0;
	if (b->n_played_cards % 4 != 0) { /* must follow suit */
		firstcard = b->n_played_cards - (b->n_played_cards % 4);
		lead = b->played_cards[firstcard];
		if (SUIT(c) != SUIT(lead) && has_suit(b->cards, s, SUIT(lead))) {
			board_statusbar(_("Please follow suit"));
			return 0;
		}
	}

	int old_next_card = b->played_cards[b->n_played_cards];

	play_card_0(b, s, c);

	if (b->n_played_cards % 4 == 0) { /* trick complete */
		seat leader = b->dealt_cards[lead];
		card wincard = lead;
		b->current_turn = leader;
		int i;
		for (i = 1; i <= 3; i++) {
			card thiscard = b->played_cards[firstcard + i];
			if ((SUIT(thiscard) == b->trumps && SUIT(wincard) != b->trumps) ||
			    (SUIT(thiscard) == SUIT(wincard) && RANK(thiscard) > RANK(wincard))) {
				wincard = thiscard;
				b->current_turn = seat_mod(leader + i);
			}
		}
		b->tricks[b->current_turn % 2]++;
	} else {
		b->current_turn = seat_mod(b->current_turn + 1);
	}

	/* update DD scores */
	dd_t *dd = b->next_dd[c];
	int i;
	for (i = 0; i < 52; i++) { /* remove the other DD previews */
		if (b->next_dd[i] && b->cards[i] && (b->cards[i] == s || c != old_next_card)) {
			/* invalidate "what-if" previews and old scores from other cards */
			free (b->next_dd[i]);
			b->next_dd[i] = NULL;
		}
	}
	b->next_dd[c] = b->current_dd; /* remember scores for rewind */
	b->current_dd = dd;

	/*
	printf ("previews for: ");
	for (i = 0; i < 52; i++)
		if (b->next_dd[i])
			printf ("%s ", card_string(i)->str);
	printf ("\n");
	*/

	return 1;
}

int rewind_card(board *b)
{
	if (b->n_played_cards == 0) {
		board_statusbar(_("Nothing to undo"));
		return 0;
	}

	if (b->n_played_cards % 4 == 0)
		b->tricks[b->current_turn % 2]--;
	b->n_played_cards--;

	card c = b->played_cards[b->n_played_cards];
	assert (b->cards[c] == 0);
	seat s = b->dealt_cards[c];

	/* remove DD previews */
	dd_t *dd = b->next_dd[c];
	int i;
	for (i = 0; i < 52; i++) {
		if (b->next_dd[i] && b->cards[i] == s) {
			free (b->next_dd[i]);
			b->next_dd[i] = NULL;
		}
	}
	b->next_dd[c] = b->current_dd;
	b->current_dd = dd;

	/* return card */
	b->current_turn = b->cards[c] = s;
	/*
	printf ("previews for: ");
	for (i = 0; i < 52; i++)
		if (b->next_dd[i])
			printf ("%s ", card_string(i)->str);
	printf ("\n");
	*/

	return 1;
}

void board_rewind(board *b)
{
	while (b->n_played_cards)
		rewind_card(b);
}

int next_card(board *b)
{
	if (b->n_played_cards >= b->n_dealt_cards) {
		board_statusbar(_("No cards left to play"));
		return 0;
	}
	if (b->played_cards[b->n_played_cards] == -1) {
		board_statusbar(_("Which card should I play?"));
		return 0;
	}
	if (b->played_cards[b->n_played_cards] == claim_rest) {
		char buf[50];
		snprintf (buf, sizeof (buf),
			_("Declarer claims %d tricks"), b->declarer_tricks);
		board_statusbar(buf);
		return 0;
	}
	if (b->cards[b->played_cards[b->n_played_cards]] == 0) {
		board_statusbar(_("Card was already played"));
		return 0;
	}
	if (b->cards[b->played_cards[b->n_played_cards]] != b->current_turn) {
		board_statusbar(_("Card belongs to wrong player"));
		return 0;
	}
	return play_card(b, b->current_turn, b->played_cards[b->n_played_cards]);
}

void board_fast_forward(board *b)
{
	while (b->n_played_cards < b->n_dealt_cards &&
			b->played_cards[b->n_played_cards] != -1) {
		if (! next_card (b))
			return;
	}
}

/* bidding */

int
board_append_bid(board *b, card bid, int set_contract)
{
	int cur_bid = 0, cur_bid_i = 0, n_pass = 0, doubl = 0, redoubl = 0;
	int i;
	for (i = b->n_bids - 1; i >= 0; i--) {
		if (b->bidding[i] == bid_pass && !doubl && !redoubl)
			n_pass++; /* number of trailing passes */
		if (b->bidding[i] == bid_x)
			doubl = 1;
		if (b->bidding[i] == bid_xx)
			redoubl = 1;
		if (b->bidding[i] > bid_xx) {
			cur_bid = b->bidding[i];
			cur_bid_i = i;
			break;
		}
	}

	if (n_pass == 4 || (n_pass == 3 && cur_bid)) {
		board_statusbar(_("Bidding is over"));
		return 0;
	}

	if (bid == bid_x)
		if (doubl || redoubl || cur_bid == 0 || n_pass % 2 != 0) {
			board_statusbar(_("Invalid double"));
			return 0;
		}

	if (bid == bid_xx)
		if (!doubl || redoubl || cur_bid == 0 || n_pass % 2 != 0) {
			board_statusbar(_("Invalid redouble"));
			return 0;
		}

	if (bid > bid_xx && bid <= cur_bid) {
		board_statusbar(_("Insufficient bid"));
		return 0;
	}

	if (b->n_bids >= b->n_bid_alloc) {
		b->n_bid_alloc <<= 2;
		b->bidding = realloc(b->bidding, b->n_bid_alloc * sizeof (card));
		b->alerts = realloc(b->alerts, b->n_bid_alloc * sizeof (char *));
		assert(b->bidding && b->alerts);
		int i; /* I wished there was recalloc() */
		for (i = b->n_bid_alloc >> 2; i < b->n_bid_alloc; i++)
			b->alerts[i] = NULL;
	}

	b->bidding[b->n_bids] = bid;
	b->alerts[b->n_bids] = NULL;
	b->n_bids++;

	if (bid == bid_pass && (n_pass == 3 || (n_pass == 2 && cur_bid))) {
		if (set_contract) {
			int trumps = DENOM (cur_bid);
			int declarer = 0;
			if (cur_bid) { /* find declarer */
				for (i = cur_bid_i % 2; i < b->n_bids; i += 2)
					if (DENOM (b->bidding[i]) == trumps) {
						declarer = seat_mod (b->dealer + i);
						break;
					}
				assert (declarer); /* someone must actually bid the contract */
			} else {
				declarer = b->dealer; /* default for passed boards */
			}
			board_set_contract (b, LEVEL (cur_bid), trumps, declarer, doubl + redoubl);
		}
		return 2; /* signal end of bidding */
	}

	return 1;
}

void
board_remove_bid (board *b)
{
	assert (b->n_bids > 0);
	b->n_bids--;
	if (b->alerts[b->n_bids]) {
		free (b->alerts[b->n_bids]);
		b->alerts[b->n_bids] = NULL;
	}
}

void
board_clear_bidding (board *b)
{
	while (b->n_bids > 0)
		board_remove_bid (b);
}

void
board_set_alert (board *b, const char *alert)
{
	assert (b->n_bids > 0);
	if (b->alerts[b->n_bids - 1])
		free (b->alerts[b->n_bids - 1]);
	if (alert)
		b->alerts[b->n_bids - 1] = strdup (alert);
	else
		b->alerts[b->n_bids - 1] = NULL;
}
