// Copyright (C) 2002 Neil Stevens <neil@qualityassistant.com>
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
// 
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
// THE AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
// AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// 
// Except as contained in this notice, the name(s) of the author(s) shall not be
// used in advertising or otherwise to promote the sale, use or other dealings
// in this Software without prior written authorization from the author(s).

#include <cassert>
#include <kapplication.h>
#include <klocale.h>
#include "dealer.h"

using namespace Megami;

////////////////
// Connection //
////////////////

Connection::Connection(Dealer *parent, int s, int o)
	: QObject(parent)
	, serial(s)
	, order(o)
	, dealer(parent)
{
}

void Connection::bet(unsigned b)
{
	emit connBet(serial, b);
}

void Connection::hit(void)
{
	emit connHit(serial);
}

void Connection::stand(void)
{
	emit connStand(serial);
}

void Connection::doubleDown(void)
{
	emit connDoubleDown(serial);
}

////////////
// Dealer //
////////////

const int Dealer::DealerPlayer = -1;
const int Dealer::ErrorPlayer = -2;

Dealer::Dealer(int max, QObject *parent, const char *name)
	: QObject(parent, name)
	, state(StateIdle)
	, maximum(max)
	, count(0)
	, players(new Player[maximum])
{
	dealer.serial = makeSerial();
	dealer.name = i18n("Dealer");
	for(unsigned i = 0; i < maximum; ++i)
		players[i].serial = 0;
}

Dealer::~Dealer()
{
	// TODO: handle a game in progress
	delete[] players;
}

Player Dealer::createPlayer(const QString &name, int money)
{
	Player p;
	p.money = money;
	p.name = name;
	return p;
}

Connection *Dealer::join(Player p)
{
	if(count == maximum) return 0;

	int serial = makeSerial();
	unsigned order = 0;
	while(players[order].serial) ++order;
	assert(order < maximum);

	Connection *conn = new Connection(this, serial, order);
	connect(conn, SIGNAL(connBet(int, unsigned)), this, SLOT(connBet(int, unsigned)));
	connect(conn, SIGNAL(connHit(int)), this, SLOT(connHit(int)));
	connect(conn, SIGNAL(connStand(int)), this, SLOT(connStand(int)));
	connect(conn, SIGNAL(connDoubleDown(int)), this, SLOT(connDoubleDown(int)));
	connections.insert(serial, conn);

	players[order] = p;
	players[order].bet = 0;
	players[order].serial = conn->serial;

	++count;

	emit join(order);

	return conn;
}

void Dealer::quit(Connection *c)
{
	assert(c);

	--count;

	int player = intForSerial(c->serial);
	if(player < 0) return;

	if(state == StateTurn && turn == player)
		connStand(c->serial);

	players[player].bet = 0;
	players[player].serial = 0;

	connections.remove(c->serial);
	delete c;

	emit quit(player);
}

QString Dealer::name(int p)
{
	if(p == DealerPlayer)
		return dealer.name;
	else if(players[p].serial)
		return players[p].name;
	else
		return QString::null;
}

void Dealer::setName(int p, const QString &name)
{
	if(p == DealerPlayer)
		dealer.name = name;
	else if(players[p].serial)
		players[p].name = name;
}

int Dealer::cash(int p)
{
	if(p == DealerPlayer)
		return -1;
	else if(players[p].serial)
		return players[p].money;
	else
		return -1;
}

void Dealer::setCash(int p, unsigned cash)
{
	if(players[p].serial)
		players[p].money = cash;
}


int Dealer::hiddenCards(int p)
{
	if(p == DealerPlayer)
		return dealer.hidden.stackSize();
	else if(players[p].serial)
		return players[p].hidden.stackSize();
	else
		return -1;
}

Deck::Stack Dealer::visibleCards(int p)
{
	if(p == DealerPlayer)
		return dealer.visible;
	else if(players[p].serial)
		return players[p].visible;
	else
		return Deck::Stack();
}

void Dealer::start(void)
{
	if(!count || state != StateIdle) return;

	// initialize
	for(unsigned i = 0; i < maximum; ++i)
	{
		players[i].bet = 0;
		players[i].done = false;
		players[i].visible.popAll();
		players[i].hidden.popAll();
	}
	dealer.hidden.popAll();
	dealer.visible.popAll();

	state = StateBets;
	emit acceptingBets();
}

void Dealer::deal(void)
{
	if(state != StateBets) return;

	// Come on, deal the cards, deal the cards
	// Now just need to spin the wheel of penalties when player loses
	int bets = 0;
	for(unsigned i = 0; i < maximum; ++i)
	{
		Player &cur = players[i];
		if(cur.bet)
		{
			++bets;
			dealCard(cur, false);
			dealCard(cur, false);
		}
		
		if(getCount(cur) == 21)
			cur.done = true;
	}

	// if nobody bet we're done
	if(!bets)
	{
		state = StateIdle;
		return;
	}

	dealCard(dealer, false);
	dealCard(dealer, true);

	if(hasMegami(dealer))
	{
		resolve();
		return;
	}

	state = StateTurn;

	int first = getFirstActivePlayer();
	turn = first;
	if(players[turn].done)
		cont();
	else
		emit play(turn);
}

void Dealer::connBet(int serial, unsigned b)
{
	int p = intForSerial(serial);
	if(p < 0) return;

	Player &player = players[p];
	if(player.bet) return;

	addBet(player, b);

	// if all players have bet, then just deal
	unsigned i = getFirstPlayer();
	do
	{
		if(!players[i].bet) return;
	
		i = getNextPlayer(i);
	}
	while(i != getFirstPlayer());

	deal();
}

void Dealer::connHit(int serial)
{
	if(turn != intForSerial(serial)) return;

	Player &cur = players[turn];

	dealCard(cur, false);

	if(getCount(cur) == 21)
	{
		cont();
	}
	else if(getCount(cur) > 21)
	{
		emit bust(turn);
		cont();
	}
	else
	{
		emit play(turn);
	}
}

void Dealer::connStand(int serial)
{
	if(turn != intForSerial(serial)) return;
	cont();
}

void Dealer::connDoubleDown(int serial)
{
	if(turn != intForSerial(serial)) return;

	Player &cur = players[turn];

	if(cur.visible.stackSize() + cur.hidden.stackSize() > 2) return;

	cur.bet *= 2;

	dealCard(cur, false);

	if(getCount(cur) > 21)
		emit bust(turn);
	cont();
}

void Dealer::resolve(void)
{
	state = StateResolve;

	// if all players megami or lost, don't bother having the dealer play
	int first = getFirstActivePlayer();
	int p = first;
	int unresolved = 0;
	do
	{
		Player &player = players[p];
		int count = getCount(player);
		if(count <= 21 && !hasMegami(player)) unresolved++;
		p = getNextActivePlayer(p);
	}
	while(p != first);

	// manual stack manipulation
	// danger - danger
	dealer.visible.push(dealer.visible[0]);
	dealer.visible[0] = dealer.hidden.pop();
	emit dealt(DealerPlayer, false, dealer.visible[0]);

	// Play the dealer's turn if necessary
	int dealerCount = getCount(dealer);
	if(unresolved)
	{
		while(dealerCount <= 16 || (dealerCount == 17 && isSoft(dealer)))
		{
			dealCard(dealer, false);
			dealerCount = getCount(dealer);
		}

		if(dealerCount > 21)
			emit bust(DealerPlayer);
	}

	if(hasMegami(dealer))
		emit megami(DealerPlayer);

	// payoffs
	p = first;
	do
	{
		Player &player = players[p];
		int count = getCount(player);
		if(count <= 21)
		{
			if(hasMegami(dealer) && !hasMegami(player))
			{
				emit lose(p);
			}
			else if(!hasMegami(dealer) && hasMegami(player))
			{
				player.money += (int)(player.bet * 2.5);
				emit megami(p);
			}
			else if(count == dealerCount)
			{
				player.money += player.bet;
				emit push(p);
			}
			else if(count > dealerCount || dealerCount > 21)
			{
				player.money += player.bet * 2;
				emit win(p);
			}
			else
			{
				// note that bust is handled as it happens
				emit lose(p);
			}
		}
		p = getNextActivePlayer(p);
	}
	while(p != first);

	state = StateIdle;
	emit done();
}

const unsigned Dealer::deckCount = 6;
const unsigned Dealer::deckMinimum = 20;

int Dealer::popCard(void)
{
	if(deck.stackSize() <= deckMinimum)
		deck = Deck::shuffle(false, deckCount);

	return deck.pop();
}

void Dealer::dealCard(Player &cur, bool hidden)
{
	int card = popCard();

	if(hidden)
		cur.hidden.push(card);
	else
		cur.visible.push(card);

	int player = intForSerial(cur.serial);

	emit dealt(player, hidden, card);
}

int Dealer::makeSerial(void)
{
	int serial = 0;
	while(!serial || connections.find(serial) || dealer.serial == serial)
		serial = KApplication::random();
	return serial;
}

int Dealer::intForSerial(int serial)
{
	assert(serial);

	if(dealer.serial == serial)
		return DealerPlayer;

	for(unsigned i = 0; i < maximum; i++)
		if(players[i].serial == serial)
			return i;

	return ErrorPlayer;
}

Player *Dealer::playerForSerial(int serial)
{
	int t = intForSerial(serial);
	if(t == ErrorPlayer)
		return 0;
	else if(t == DealerPlayer)
		return &dealer;
	else
		return &players[t];
}

unsigned Dealer::getNextPlayer(unsigned fromThis)
{
	assert(count);

	for(unsigned i = fromThis + 1; i < maximum; ++i)
		if(players[i].serial) return i;
	// wrap around
	for(unsigned i = 0; i < fromThis; ++i)
		if(players[i].serial) return i;
	// only one player, apparently
	return fromThis;
}

unsigned Dealer::getFirstPlayer(void)
{
	assert(count);

	return getNextPlayer(maximum);
}

unsigned Dealer::getNextActivePlayer(unsigned fromThis)
{
	assert(state > StateBets);

	unsigned p = fromThis;
	do
		p = getNextPlayer(p);
	while(!players[p].bet && p != fromThis);

	// fromThis prevents infinite loop, now check for that condition occurring
	assert(players[p].bet);

	return p;
}

unsigned Dealer::getFirstActivePlayer(void)
{
	assert(count);

	return getNextActivePlayer(maximum);
}

QPair<int, bool> Dealer::getCountAndSoft(Player &player)
{
	int count = 0;
	int soft = 0;

	for(unsigned i = 0; i < player.hidden.stackSize(); i++)
	{
		int val = Deck::blackjackValue(player.hidden[i]);
		if(val == 11) soft++;
		count += val;
	}
	for(unsigned i = 0; i < player.visible.stackSize(); i++)
	{
		int val = Deck::blackjackValue(player.visible[i]);
		if(val == 11) soft++;
		count += val;
	}

	while(count > 21 && soft)
	{
		soft--;
		count -= 10;
	}

	return QPair<int, bool>(count, soft > 0);
}

int Megami::handCount(const Deck::Stack &hand)
{
	int count = 0;
	int soft = 0;

	for(unsigned i = 0; i < hand.stackSize(); i++)
	{
		int val = Deck::blackjackValue(hand[i]);
		if(val == 11) soft++;
		count += val;
	}

	while(count > 21 && soft)
	{
		soft--;
		count -= 10;
	}

	return count;
}

bool Dealer::hasMegami(Player &player)
{
	return (getCount(player) == 21) &&
	       (player.hidden.stackSize() + player.visible.stackSize() == 2);
}

void Dealer::cont(void)
{
	players[turn].done = true;
	int first = turn;

	do
	{
		if(!players[turn].done)
		{
			emit play(turn);
			return;
		}
		turn = getNextActivePlayer(turn);
	}
	while(turn != first);

	resolve();
}

void Dealer::addBet(Player &p, int b)
{
	if(p.money < b) return;
	p.bet += b;
	p.money -= b;
	emit bet(intForSerial(p.serial), p.bet);
}

#include "dealer.moc"
