// 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 <climits>

#include <kaction.h>
#include <kcarddialog.h>
#include <kconfig.h>
#include <kdebug.h>
#include <klocale.h>
#include <kglobal.h>
#include <kkeydialog.h>
#include <kmenubar.h>
#include <kmessagebox.h>
#include <kpushbutton.h>
#include <kstatusbar.h>
#include <kstdaction.h>
#include <kstdgameaction.h>
#include <qcanvas.h>
#include <qlayout.h>
#include <qspinbox.h>

#include "kintinputdialog.h"
#include "megamiwindow.h"

namespace
{
int wH(QWidget *w)
{
	return w->sizeHint().height();
}

int wW(QWidget *w)
{
	return w->sizeHint().width();
}

}

MegamiWindow::MegamiWindow(QWidget *parent, const char *name)
	: KMainWindow(parent, name)
	, dealer(numberOfPlayers)
	, canvas(2000, 2000)
	, cardArray(0)
{
	clearWFlags(WDestructiveClose);
	
	gameNewAction = KStdGameAction::gameNew(this, SLOT(gameNew()), actionCollection());
	gameDealAction = new KAction(i18n("&Deal"), Key_D, this, SLOT(gameDeal()), actionCollection(), "game_deal");
	gameHitAction = new KAction(i18n("&Hit"), Key_H, this, SLOT(gameHit()), actionCollection(), "game_hit");
	gameHitAction->setEnabled(false);
	gameStandAction = new KAction(i18n("&Stand"), Key_S, this, SLOT(gameStand()), actionCollection(), "game_stand");
	gameStandAction->setEnabled(false);
	gameBetAction = new KAction(i18n("&Bet"), Key_B, this, SLOT(gameBet()), actionCollection(), "game_bet");
	gameBetAction->setEnabled(false);
	KStdGameAction::quit(this, SLOT(gameQuit()), actionCollection());
	KStdGameAction::carddecks(this, SLOT(settingsCarddecks()), actionCollection());
	KStdAction::preferences(this, SLOT(settingsPreferences()), actionCollection());
	KStdAction::keyBindings(this, SLOT(settingsKeys()), actionCollection());
	createGUI();

	//
	statusBar()->insertItem(i18n("Dealer"), -1, true);
	for(unsigned i = 0; i < numberOfPlayers; ++i)
		statusBar()->insertItem(QString::number(i), i, true);

	//
	QFrame *frame = new QFrame(this);
	setCentralWidget(frame);

	view = new QCanvasView(&canvas, frame);
	view->setHScrollBarMode(QCanvasView::AlwaysOff);
	view->setVScrollBarMode(QCanvasView::AlwaysOff);
	view->setMinimumSize(700, 150);
	view->setMaximumSize(2000, 2000);
	view->setFocusPolicy(NoFocus);

	dealButton = new KPushButton(i18n("Deal Hand", "Deal"), frame);
	dealButton->setEnabled(true);
	dealButton->setFocus();
	connect(dealButton, SIGNAL(clicked()), this, SLOT(gameDeal()));
	betButton = new KPushButton(i18n("Place a Bet", "Bet"), frame);
	betButton->setEnabled(false);
	connect(betButton, SIGNAL(clicked()), this, SLOT(gameBet()));
	hitButton = new KPushButton(i18n("Get Another Card", "Hit"), frame);
	hitButton->setEnabled(false);
	connect(hitButton, SIGNAL(clicked()), this, SLOT(gameHit()));
	standButton = new KPushButton(i18n("Stop Getting Cards", "Stand"), frame);
	standButton->setEnabled(false);
	connect(standButton, SIGNAL(clicked()), this, SLOT(gameStand()));

	betBox = new QSpinBox(frame);
	betBox->setEnabled(false);
	betBox->setLineStep(10);

	// I repeat: Who needs Designer?
	QHBoxLayout *buttonLayout = new QHBoxLayout(0, 0, KDialog::spacingHint());
	buttonLayout->addWidget(dealButton);
	buttonLayout->addWidget(betBox);
	buttonLayout->addWidget(betButton);
	buttonLayout->addWidget(hitButton);
	buttonLayout->addWidget(standButton);

	QVBoxLayout *layout = new QVBoxLayout(frame, 0, KDialog::spacingHint());
	layout->addWidget(view);
	layout->addLayout(buttonLayout);
	layout->addStretch();

	resize(750, 150);

	//
	for(unsigned i = 0; i < numberOfPlayers; ++i)
	{
		Megami::Player p = Megami::Dealer::createPlayer(" ", 250);
		Megami::Connection *c = dealer.join(p);
		connection.append(c);
	}

	for(unsigned i = 0; i <= numberOfPlayers; ++i)
	{
		QPtrList<QCanvasItem> *newSpriteList = new QPtrList<QCanvasItem>;
		newSpriteList->setAutoDelete(true);
		sprites.append(newSpriteList);
		labels.append(new QCanvasText(" ", &canvas));
		labels.at(i)->setVisible(true);
		results.append(new QCanvasText(" ", &canvas));
		results.at(i)->setVisible(true);
	}
	labels.setAutoDelete(true);
	results.setAutoDelete(true);
	sprites.setAutoDelete(false);

	//
	connect(&dealer, SIGNAL(acceptingBets()), this, SLOT(dealerAcceptingBets()));
	connect(&dealer, SIGNAL(bet(int, unsigned)), this, SLOT(dealerBet(int, unsigned)));
	connect(&dealer, SIGNAL(dealt(int, bool, int)), this, SLOT(dealerDealt(int, bool, int)));
	connect(&dealer, SIGNAL(play(int)), this, SLOT(dealerPlay(int)));
	connect(&dealer, SIGNAL(bust(int)), this, SLOT(dealerBust(int)));
	connect(&dealer, SIGNAL(push(int)), this, SLOT(dealerPush(int)));
	connect(&dealer, SIGNAL(megami(int)), this, SLOT(dealerMegami(int)));
	connect(&dealer, SIGNAL(win(int)), this, SLOT(dealerWin(int)));
	connect(&dealer, SIGNAL(lose(int)), this, SLOT(dealerLose(int)));
	connect(&dealer, SIGNAL(done(void)), this, SLOT(dealerDone(void)));

	//
	KConfig &config = *KGlobal::config();

	applyMainWindowSettings(&config, "Window");

	config.setGroup("Cash");
	dealer.setCash(0, config.readNumEntry("Player", 250));
	for(unsigned i = 1; i < numberOfPlayers; ++i)
		dealer.setCash(i, config.readNumEntry("Computer" + QString::number(i), 250));

	config.setGroup("Cards");
	setCards(config.readEntry("Deck", KCardDialog::getDefaultDeck()),
	         config.readEntry("Dir", KCardDialog::getDefaultCardDir()));

	applyPreferences(MegamiConfig::readData());
}

MegamiWindow::~MegamiWindow()
{
	// These have to be cleared before the cardArray, or it seems to eat a lot
	// of cpu on shutdown
	for(unsigned i = 0; i <= numberOfPlayers; ++i)
		sprites.at(i)->clear();
	labels.clear();
	results.clear();

	delete cardArray;
	cardArray = 0;

	KConfig &config = *KGlobal::config();
	config.setGroup("Cash");
	config.writeEntry("Player", dealer.cash(0));
	for(unsigned i = 1; i < numberOfPlayers; ++i)
		config.writeEntry("Computer" + QString::number(i), dealer.cash(i));
}

void MegamiWindow::closeEvent(QCloseEvent *e)
{
	saveMainWindowSettings(KGlobal::config(), "Window");
	KMainWindow::closeEvent(e);
}

void MegamiWindow::resizeEvent(QResizeEvent *e)
{
	KMainWindow::resizeEvent(e);
	refresh();
}

void MegamiWindow::gameNew(void)
{
	KIntInputDialog dlg(0, 1000000, i18n("Start with how much cash?"), 250, this);
	if(dlg.exec())
	{
		for(unsigned i = 0; i < numberOfPlayers; ++i)
			dealer.setCash(i, dlg.result());
		refresh();
	}
}

void MegamiWindow::gameDeal(void)
{
	gameNewAction->setEnabled(false);
	gameDealAction->setEnabled(false);
	dealButton->setEnabled(false);
	dealer.start();
	refresh();
}

void MegamiWindow::gameQuit(void)
{
	close();
}

void MegamiWindow::settingsCarddecks(void)
{
	KConfig &config = *KGlobal::config();
	config.setGroup("Cards");
	QString deck = config.readEntry("Deck", KCardDialog::getDefaultDeck());
	QString dir = config.readEntry("Dir", KCardDialog::getDefaultCardDir());
	if(KCardDialog::getCardDeck(deck, dir, this))
	{
		KConfig &config = *KGlobal::config();
		config.setGroup("Cards");
		config.writeEntry("Deck", deck);
		config.writeEntry("Dir", dir);

		setCards(deck, dir);
	}
}

void MegamiWindow::settingsPreferences(void)
{
	MegamiConfig::Data data = MegamiConfig::readData();
	MegamiConfig config(data, this);
	connect(&config, SIGNAL(apply(const MegamiConfig::Data &)), this, SLOT(applyPreferences(const MegamiConfig::Data &)));

	if(config.exec())
		applyPreferences(config.result());
}

void MegamiWindow::settingsKeys(void)
{
	KKeyDialog::configureKeys(actionCollection(), xmlFile());
}

void MegamiWindow::applyPreferences(const MegamiConfig::Data &data)
{
	MegamiConfig::writeData(data);

	canvas.setBackgroundColor(data.bgColor);

	for(unsigned i = 0; i < labels.count() ; ++i)
	{
		labels.at(i)->setColor(data.fgColor);
		results.at(i)->setColor(data.fgColor);
	}

	dealer.setName(-1, i18n("Dealer"));
	dealer.setName(0, data.playerName);
	for(unsigned i = 1; i < numberOfPlayers; ++i)
		dealer.setName(i, data.computerNames[i - 1]);

	refresh();
}

void MegamiWindow::gameBet(void)
{
	connection.at(0)->bet(betBox->text().toInt());
	betButton->setEnabled(false);
	gameBetAction->setEnabled(false);
	betBox->setEnabled(false);
	dealer.deal();
}

void MegamiWindow::gameHit(void)
{
	connection.at(0)->hit();
}

void MegamiWindow::gameStand(void)
{
	connection.at(0)->stand();
	hitButton->setEnabled(false);
	gameHitAction->setEnabled(false);
	standButton->setEnabled(false);
	gameStandAction->setEnabled(false);
}

void MegamiWindow::dealerAcceptingBets(void)
{
	betButton->setEnabled(true);
	betBox->setEnabled(true);
	betButton->setFocus();
	betBox->setMinValue(1);
	betBox->setMaxValue(dealer.cash(0));
	gameBetAction->setEnabled(true);
	for(unsigned i = 0; i <= numberOfPlayers; ++i)
		results.at(i)->setText("");
	for(unsigned i = 1; i < numberOfPlayers; ++i)
		connection.at(i)->bet(5);
}

void MegamiWindow::dealerBet(int p, unsigned)
{
	drawCash(p);
}

namespace
{
QString displayCount(Megami::Dealer &dealer, int p)
{
	return QString::number(Megami::handCount(dealer.visibleCards(p)));
}
}

void MegamiWindow::dealerDealt(int p, bool hidden, int)
{
	if(!hidden) drawResult(p,displayCount(dealer, p));
	drawHand(p);
}

void MegamiWindow::dealerPlay(int p)
{
	const bool enable = (p == 0);
	hitButton->setEnabled(enable);
	standButton->setEnabled(enable);
	gameHitAction->setEnabled(enable);
	gameStandAction->setEnabled(enable);
	hitButton->setFocus();

	if(p > 0) computerTurn(p);
}

void MegamiWindow::dealerBust(int p)
{
	drawResult(p, QString("%1 - %2").arg(i18n("Bust")).arg(displayCount(dealer, p)));
	drawCash(p);
}

void MegamiWindow::dealerPush(int p)
{
	drawResult(p, QString("%1 - %2").arg(i18n("Push")).arg(displayCount(dealer, p)));
	drawCash(p);
}

void MegamiWindow::dealerMegami(int p)
{
	drawResult(p, i18n("Megami"));
	drawCash(p);
}

void MegamiWindow::dealerWin(int p)
{
	drawResult(p, QString("%1 - %2").arg(i18n("Win")).arg(displayCount(dealer, p)));
	drawCash(p);
}

void MegamiWindow::dealerLose(int p)
{
	drawResult(p, QString("%1 - %2").arg(i18n("Lose")).arg(displayCount(dealer, p)));
	drawCash(p);
}

void MegamiWindow::dealerDone(void)
{
	hitButton->setEnabled(false);
	standButton->setEnabled(false);
	gameHitAction->setEnabled(false);
	gameStandAction->setEnabled(false);
	gameNewAction->setEnabled(true);
	gameDealAction->setEnabled(true);
	dealButton->setEnabled(true);
	dealButton->setFocus();
}

void MegamiWindow::setCards(QString deck, QString dir)
{
	for(unsigned i = 0; i < sprites.count(); ++i)
		sprites.at(i)->clear();

	delete cardArray;
	cardArray = 0;

	QPtrList<QPixmap> list;
	list.setAutoDelete(true);
	list.append(new QPixmap(deck));
	for(int i = 1; i <= 52; ++i)
		list.append(new QPixmap(dir + QString::number(i) + QString::fromLatin1(".png")));

	QPtrList<QPoint> points;
	QPoint point(0, 0);
	for(int i = 0; i <= 52; ++i)
		points.append(&point);

	cardArray = new QCanvasPixmapArray(list, points);

	refresh();
}

void MegamiWindow::refresh(void)
{
	for(unsigned i = 0; i <= numberOfPlayers; ++i)
		drawHand(i - 1);
}

void MegamiWindow::drawHand(int p)
{
	// dealer is -1-based, but Window is 0-based (For the lists)
	int pos = p + 1;

	drawCash(p);

	QPtrList<QCanvasItem> &list = *sprites.at(pos);
	for(unsigned i = 0; i < list.count(); ++i)
		list.at(i)->setVisible(false);

	QCanvasText &label = *labels.at(pos);
	QCanvasText &result = *results.at(pos);
	if(!dealer.name(p))
	{
		label.setVisible(false);
		return;
	}

	const double margin = 5.0;
	const double playerXOffset = (double)width() / (numberOfPlayers + 1.0);
	const double thisPlayerXOffset = (double)pos * playerXOffset + margin;
	const double cardYOffset = 25.0;
	const double cardXOffset = 15.0;

	label.setVisible(true);
	label.setText(dealer.name(p));
	label.move(thisPlayerXOffset, margin);

	unsigned hidden = dealer.hiddenCards(p);
	Deck::Stack visible = dealer.visibleCards(p);

	if(!(visible.stackSize() + hidden))
	{
		canvas.update();
		return;
	}

	for(unsigned i = 0; i < hidden; ++i)
	{
		if(!list.at(i))
			list.append( new QCanvasSprite(cardArray, &canvas) );
		QCanvasSprite &sprite = *static_cast<QCanvasSprite *>(list.at(i));

		sprite.move(thisPlayerXOffset + cardXOffset * i, cardYOffset, 0);
		sprite.setVisible(true);
		sprite.setZ(i);
	}

	for(unsigned i = 0; i < visible.stackSize(); ++i)
	{
		if(!list.at(i + hidden))
			list.append( new QCanvasSprite(cardArray, &canvas) );
		QCanvasSprite &sprite = *static_cast<QCanvasSprite *>(list.at(i + hidden));

		sprite.move(thisPlayerXOffset + cardXOffset * (i + hidden), cardYOffset, visible[i]);
		sprite.setVisible(true);
		sprite.setZ(i + hidden);
	}

	const QRect resultRect = result.boundingRect();
	const QRect cardRect = list.at(visible.stackSize() + hidden - 1)->boundingRect();
	const double count = hidden + visible.stackSize();

	const double x = thisPlayerXOffset + (cardXOffset * count + cardRect.width() - (double)resultRect.width()) / 2.0 - margin;
	const double y = (double)cardRect.height() + (double)cardRect.y() + margin;
	result.move(x, y);

	canvas.update();
}

void MegamiWindow::drawCash(int p)
{
	if(p == Megami::Dealer::DealerPlayer)
	{
		statusBar()->changeItem("", p);
	}
	else
	{
		QString m = QString("%1: %2")
		            .arg(dealer.name(p))
		            .arg(KGlobal::locale()->formatMoney(dealer.cash(p), QString::null, 0));
		statusBar()->changeItem(m, p);
	}
}

void MegamiWindow::drawResult(int p, const QString &text)
{
	// dealer is -1-based, but Window is 0-based (For the lists)
	int pos = p + 1;
	QCanvasText &result = *results.at(pos);
	result.setText(text);
	drawHand(p);
}

void MegamiWindow::computerTurn(int p)
{
	// shortcut - assume no hidden cards
	Deck::Stack hand = dealer.visibleCards(p);
	int count = Megami::handCount(hand);
	if(count <= 14)
		connection.at(p)->hit();
	else
		connection.at(p)->stand();
}

const unsigned MegamiWindow::numberOfPlayers = 4;

#include "megamiwindow.moc"
