/* Yo Emacs, this is -*- C++ -*-
 *******************************************************************
 *******************************************************************
 *
 *
 * KSHISEN
 *
 *
 *******************************************************************
 *
 * A japanese game similar to mahjongg
 *
 *******************************************************************
 *
 * Copyright (C)  1997 by Mario Weilguni <mweilguni@sime.com>
 * Copyright (C) 2002-2004 Dave Corrie  <kde@davecorrie.com>
 * Copyright (C) 2007 Mauricio Piacentini <mauricio@tabuleiro.com>
 *
 *******************************************************************
 *
 * This file is part of the KDE project "KSHISEN"
 *
 * KSHISEN 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, or (at your option)
 * any later version.
 *
 * KSHISEN 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.
 *
 * You should have received a copy of the GNU General Public License
 * along with KSHISEN; see the file COPYING.  If not, write to
 * the Free Software Foundation, 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 *
 *******************************************************************
 */

#include "app.h"

#include <kactioncollection.h>
#include <kstandardaction.h>
#include <kseparator.h>
#include <kmessagebox.h>
#include <kconfig.h>
#include <kaction.h>
#include <kstandardgameaction.h>
#include <khighscore.h>
#include <kdebug.h>
#include <kshortcutsdialog.h>
#include <kmenu.h>
#include <kstatusbar.h>
#include <klocale.h>
#include <kpushbutton.h>
#include <KStandardGuiItem>
#include <kconfigdialog.h>

#include <QTimer>
#include <KLineEdit>

#include <cmath>
#include <kglobal.h>
#include "prefs.h"
#include "ui_settings.h"

#include <kmahjonggconfigdialog.h>

class Settings : public QWidget, public Ui::Settings
{
public:
	Settings(QWidget *parent)
		: QWidget(parent)
	{
		setupUi(this);
	}
};

App::App(QWidget *parent) : KXmlGuiWindow(parent),
   cheat(false)
{
	highscoreTable = new KHighscore(this);

	// TODO?
	// Would it make sense long term to have a kconfig update rather then
	// havin both formats supported in the code?
	if(highscoreTable->hasTable())
		readHighscore();
	else
		readOldHighscore();

	setupStatusBar();
	setupActions();

	board = new Board(this);
	board->setObjectName( "board" );

	setCentralWidget(board);

	setupGUI();

	connect(board, SIGNAL(changed()), this, SLOT(enableItems()));
	connect(board, SIGNAL(tilesDontMatch()), this, SLOT(notifyTilesDontMatch()));
	connect(board, SIGNAL(invalidMove()), this, SLOT(notifyInvalidMove()));
	connect(board, SIGNAL(selectATile()), this, SLOT(notifySelectATile()));
	connect(board, SIGNAL(selectAMatchingTile()), this, SLOT(notifySelectAMatchingTile()));
	connect(board, SIGNAL(selectAMove()), this, SLOT(notifySelectAMove()));

	QTimer *t = new QTimer(this);
	t->start(1000);
	connect(t, SIGNAL(timeout()), this, SLOT(updateScore()));
	connect(board, SIGNAL(endOfGame()), this, SLOT(slotEndOfGame()));

	qApp->processEvents();

	updateScore();
	enableItems();
}

void App::setupStatusBar()
{
	gameTipLabel= new QLabel(i18n("Select a tile"), statusBar());
	statusBar()->addWidget(gameTipLabel, 1);
	
	gameTimerLabel = new QLabel(i18n("Time: 0:00:00"), statusBar());
	statusBar()->addWidget(gameTimerLabel);
	
	gameTilesLabel = new QLabel(i18n("Removed: 0/0"), statusBar());
	statusBar()->addWidget(gameTilesLabel);
	
	gameCheatLabel = new QLabel(i18n("Cheat mode"), statusBar());
	statusBar()->addWidget(gameCheatLabel);
	gameCheatLabel->hide();
}

void App::setupActions()
{
	// Game
	KStandardGameAction::gameNew(this, SLOT(newGame()), actionCollection());
	KStandardGameAction::restart(this, SLOT(restartGame()), actionCollection());
	KStandardGameAction::pause(this, SLOT(pause()), actionCollection());
	KStandardGameAction::highscores(this, SLOT(hallOfFame()), actionCollection());
	KStandardGameAction::quit(this, SLOT(close()), actionCollection());

	// Move
	KStandardGameAction::undo(this, SLOT(undo()), actionCollection());
	KStandardGameAction::redo(this, SLOT(redo()), actionCollection());
	KStandardGameAction::hint(this, SLOT(hint()), actionCollection());
	//new KAction(i18n("Is Game Solvable?"), 0, this,
	//	SLOT(isSolvable()), actionCollection(), "move_solvable");

#ifdef DEBUGGING
	// broken ..
	//(void)new KAction(i18n("&Finish"), 0, board, SLOT(finish()), actionCollection(), "move_finish");
#endif

	// Settings
	KStandardAction::preferences(this, SLOT(showSettings()), actionCollection());
}

void App::hallOfFame()
{
	showHighscore();
}

void App::newGame()
{
	board->newGame();
	resetCheatMode();
	enableItems();
}

void App::restartGame()
{
	board->setUpdatesEnabled(false);
	while(board->canUndo())
		board->undo();
	board->setUpdatesEnabled(true);
	board->resetRedo();
	board->update();
	board->resetTimer();
	resetCheatMode();
	enableItems();
}

void App::isSolvable()
{
	if(board->solvable())
		KMessageBox::information(this, i18n("This game is solvable."));
	else
		KMessageBox::information(this, i18n("This game is NOT solvable."));
}

void App::pause()
{
	lockMenus(board->pause());
}

void App::undo()
{
	if(board->canUndo())
	{
		board->undo();
		setCheatMode();
		enableItems();
	}
}

void App::redo()
{
	if(board->canRedo())
		board->redo();
	enableItems();
}

void App::hint()
{
#ifdef DEBUGGING
	board->makeHintMove();
#else
	board->showHint();
	setCheatMode();
#endif
	enableItems();
}

void App::lockMenus(bool lock)
{
	// Disable all actions apart from (un)pause, quit and those that are help-related.
	// (Only undo/redo and hint actually *need* to be disabled, but disabling everything
	// provides a good visual hint to the user, that they need to unpause to continue.
	KMenu* help = findChild<KMenu*>("help" );
	QList<QAction*> actions = actionCollection()->actions();
	QList<QAction*>::const_iterator actionIter = actions.constBegin();
	QList<QAction*>::const_iterator actionIterEnd = actions.constEnd();

	while(actionIter != actionIterEnd)
	{
		QAction* a = *actionIter;
		if(!a->associatedWidgets().contains(help))
			a->setEnabled(!lock);
		++actionIter;
	}

	actionCollection()->action(KStandardGameAction::name(KStandardGameAction::Pause))->setEnabled(true);
	actionCollection()->action(KStandardGameAction::name(KStandardGameAction::Quit))->setEnabled(true);

	enableItems();
}

void App::enableItems()
{
	if(!board->isPaused())
	{
		actionCollection()->action(KStandardGameAction::name(KStandardGameAction::Undo))->setEnabled(board->canUndo());
		actionCollection()->action(KStandardGameAction::name(KStandardGameAction::Redo))->setEnabled(board->canRedo());
		actionCollection()->action(KStandardGameAction::name(KStandardGameAction::Restart))->setEnabled(board->canUndo());
	}
}

void App::slotEndOfGame()
{
	if(board->tilesLeft() > 0)
	{
		KMessageBox::information(this, i18n("No more moves possible!"), i18n("End of Game"));
	}
	else
	{
		// create highscore entry
		HighScore hs;
		hs.seconds = board->getTimeForGame();
		hs.x = board->x_tiles();
		hs.y = board->y_tiles();
		hs.gravity = (int)board->gravityFlag();

		// check if we made it into Top10
		bool isHighscore = false;
		if(highscore.size() < HIGHSCORE_MAX)
			isHighscore = true;
		else if(isBetter(hs, highscore[HIGHSCORE_MAX-1]))
			isHighscore = true;

		if(isHighscore && !cheat)
		{
			hs.name = getPlayerName();
			hs.date = time((time_t*)0);
			int rank = insertHighscore(hs);
			showHighscore(rank);
		}
		else
		{
			QString s = i18n("Congratulations! You made it in %1:%2:%3",
				 QString().sprintf("%02d", board->getTimeForGame()/3600),
				 QString().sprintf("%02d", (board->getTimeForGame() / 60) % 60),
				 QString().sprintf("%02d", board->getTimeForGame() % 60));

			if(isHighscore) // player would have been in the hisghscores if he did not cheat
			{
				s += '\n' + i18n("You could have been in the highscores if you did not use Undo or Hint. Try without them next time.");
			}

			KMessageBox::information(this, s, i18n("End of Game"));
		}
	}

	resetCheatMode();
	board->newGame();
}

void App::notifySelectATile()
{
	gameTipLabel->setText(i18n("Select a tile"));
}

void App::notifySelectAMatchingTile()
{
	gameTipLabel->setText(i18n("Select a matching tile"));
}

void App::notifySelectAMove()
{
	gameTipLabel->setText(i18n("Select the move you want by clicking on the blue line"));
}

void App::notifyTilesDontMatch()
{
	gameTipLabel->setText(i18n("This tile did not match the one you selected"));
}

void App::notifyInvalidMove()
{
	gameTipLabel->setText(i18n("You cannot make this move"));
}

void App::updateScore()
{
	int t = board->getTimeForGame();
	QString s = i18n(" Your time: %1:%2:%3 %4",
		 QString().sprintf("%02d", t / 3600 ),
		 QString().sprintf("%02d", (t / 60) % 60 ),
		 QString().sprintf("%02d", t % 60 ),
		 board->isPaused()?i18n("(Paused) "):QString());

	//statusBar()->changeItem(s, SBI_TIME);
	gameTimerLabel->setText(s);

	// Number of tiles
	int tl = (board->x_tiles() * board->y_tiles());
	s = i18n(" Removed: %1/%2 ",
		 QString().sprintf("%d", tl - board->tilesLeft()),
		 QString().sprintf("%d", tl ));

	//statusBar()->changeItem(s, SBI_TILES);
	gameTilesLabel->setText(s);
}

void App::setCheatMode()
{
	// set the cheat mode if not set
	if(!cheat)
	{
		cheat = true;
		gameCheatLabel->show();
	}
}

void App::resetCheatMode()
{
	// reset cheat mode if set
	if(cheat)
	{
		cheat = false;
		gameCheatLabel->hide();
	}
}

QString App::getPlayerName()
{
	KDialog dlg(this);
	dlg.setObjectName( "Hall of Fame" );
	dlg.setButtons( KDialog::Ok );

	QWidget* dummy = new QWidget( &dlg );
	dlg.setMainWidget( dummy );

	QLabel *l1 = new QLabel(i18n("You have made it into the \"Hall Of Fame\". Type in\nyour name so mankind will always remember\nyour cool rating."), dummy);

	QLabel *l2 = new QLabel(i18n("Your name:"), dummy);

	KLineEdit *e = new KLineEdit( dummy );
	e->setMinimumWidth( e->fontMetrics().width( "XXXXXXXXXXXXXXXX" ) );
	e->setText( lastPlayerName );
	e->setFocus();

	// create layout
	QHBoxLayout *tl1 = new QHBoxLayout;
	tl1->addWidget(l2);
	tl1->addWidget(e);
	QVBoxLayout *tl = new QVBoxLayout( dummy );
	tl->setMargin( 10 );
	tl->setSpacing( 5 );
	tl->addWidget(l1);
	tl->addLayout(tl1);

	dlg.exec();

	lastPlayerName = e->text();

	if(lastPlayerName.isEmpty())
		return " ";
	return lastPlayerName;
}

int App::getScore(const HighScore &hs)
{
	double ntiles = hs.x*hs.y;
	double tilespersec = ntiles/(double)hs.seconds;

	double sizebonus = std::sqrt(ntiles/(double)(14.0 * 6.0));
	double points = tilespersec / 0.14 * 100.0;

	if(hs.gravity)
		return (int)(2.0 * points * sizebonus);
	else
		return (int)(points * sizebonus);
}

bool App::isBetter(const HighScore &hs, const HighScore &than)
{
	if(getScore(hs) > getScore(than))
		return true;
	else
		return false;
}

int App::insertHighscore(const HighScore &hs)
{
	int i;

	if(highscore.size() == 0)
	{
		highscore.resize(1);
		highscore[0] = hs;
		writeHighscore();
		return 0;
	}
	else
	{
		HighScore last = highscore[highscore.size() - 1];
		if(isBetter(hs, last) || (highscore.size() < HIGHSCORE_MAX))
		{
			if(highscore.size() == HIGHSCORE_MAX)
			{
				highscore[HIGHSCORE_MAX - 1] = hs;
			}
			else
			{
				highscore.resize(highscore.size()+1);
				highscore[highscore.size() - 1] = hs;
			}

			// sort in new entry
			int bestsofar = highscore.size() - 1;
			for(i = highscore.size() - 1; i > 0; i--)
			{
				if(isBetter(highscore[i], highscore[i-1]))
				{
					// swap entries
					HighScore temp = highscore[i-1];
					highscore[i-1] = highscore[i];
					highscore[i] = temp;
					bestsofar = i - 1;
				}
			}

			writeHighscore();
			return bestsofar;
		}
	}
	return -1;
}

void App::readHighscore()
{
	QStringList hi_x, hi_y, hi_sec, hi_date, hi_grav, hi_name;
	hi_x = highscoreTable->readList("x", HIGHSCORE_MAX);
	hi_y = highscoreTable->readList("y", HIGHSCORE_MAX);
	hi_sec = highscoreTable->readList("seconds", HIGHSCORE_MAX);
	hi_date = highscoreTable->readList("date", HIGHSCORE_MAX);
	hi_grav = highscoreTable->readList("gravity", HIGHSCORE_MAX);
	hi_name = highscoreTable->readList("name", HIGHSCORE_MAX);

	highscore.resize(0);

	for (int i = 0; i < hi_x.count(); i++)
	{
		highscore.resize(i+1);

		HighScore hs;

		hs.x = hi_x[i].toInt();
		hs.y = hi_y[i].toInt();
		hs.seconds = hi_sec[i].toInt();
		hs.date = hi_date[i].toInt();
		hs.date = hi_date[i].toInt();
		hs.gravity = hi_grav[i].toInt();
		hs.name = hi_name[i];

		highscore[i] = hs;
	}
}

void App::readOldHighscore()
{
	// this is for before-KHighscore-highscores
	int i;
	QString s, e;
	KSharedConfig::Ptr conf = KGlobal::config();

	highscore.resize(0);
	i = 0;
	bool eol = false;
	KConfigGroup group = conf->group("Hall of Fame");
	while ((i < (int)HIGHSCORE_MAX) && !eol)
	{
		s.sprintf("Highscore_%d", i);
		if(group.hasKey(s))
		{
			e = group.readEntry(s,QString());
			highscore.resize(i+1);

			HighScore hs;

			QStringList e = group.readEntry(s, QString()).split(' ');
			int nelem = e.count();
			hs.x = e.at(0).toInt();
			hs.y = e.at(1).toInt();
			hs.seconds = e.at(2).toInt();
			hs.date = e.at(3).toInt();

			if(nelem == 4) // old version <= 1.1
			{
				hs.gravity = 0;
				hs.name = e.at(4);
			}
			else
			{
				hs.gravity = e.at(4).toInt();
				hs.name = e.at(5);
			}

			highscore[i] = hs;
		}
		else
		{
			eol = true;
		}
		i++;
	}

//	// freshly installed, add my own highscore
//	if(highscore.size() == 0)
//	{
//		HighScore hs;
//		hs.x = 28;
//		hs.y = 16;
//		hs.seconds = 367;
//		hs.name = "Mario";
//		highscore.resize(1);
//		highscore[0] = hs;
//	}

	// write in new KHighscore format
	writeHighscore();
	// read form KHighscore format
	readHighscore();
}

void App::writeHighscore()
{
	int i;
	QStringList hi_x, hi_y, hi_sec, hi_date, hi_grav, hi_name;
	for(i = 0; i < (int)highscore.size(); i++)
	{
		HighScore hs = highscore[i];
		hi_x.append(QString::number(hs.x));
		hi_y.append(QString::number(hs.y));
		hi_sec.append(QString::number(hs.seconds));
		hi_date.append(QString::number(hs.date));
		hi_grav.append(QString::number(hs.gravity));
		hi_name.append(hs.name);
	}
	highscoreTable->writeList("x", hi_x);
	highscoreTable->writeList("y", hi_y);
	highscoreTable->writeList("seconds", hi_sec);
	highscoreTable->writeList("date", hi_date);
	highscoreTable->writeList("gravity", hi_grav);
	highscoreTable->writeList("name", hi_name);
	highscoreTable->writeAndUnlock();
}

void App::showHighscore(int focusitem)
{
	// this may look a little bit confusing...
	KDialog dlg;
	dlg.setObjectName( "hall_Of_fame" );
	dlg.setButtons( KDialog::Close );
	dlg.setWindowTitle(i18n("Hall of Fame"));

	QWidget* dummy = new QWidget(&dlg);
	dlg.setMainWidget(dummy);

	QVBoxLayout *tl = new QVBoxLayout(dummy);
	tl->setMargin( 10 );

	QLabel *l = new QLabel(i18n("Hall of Fame"), dummy);
	QFont f = font();
	f.setPointSize(24);
	f.setBold(true);
	l->setFont(f);
	l->setAlignment(Qt::AlignCenter);
	tl->addWidget(l);

	// insert highscores in a gridlayout
	QGridLayout *table = new QGridLayout;
	tl->setSpacing(5);
	tl->addLayout(table, 1);

	// add a separator line
	KSeparator *sep = new KSeparator(dummy);
	table->addWidget(sep, 1, 0, 1, 5);

	// add titles
	f = font();
	f.setBold(true);
	l = new QLabel(i18n("Rank"), dummy);
	l->setFont(f);
	table->addWidget(l, 0, 0);
	l = new QLabel(i18n("Name"), dummy);
	l->setFont(f);
	table->addWidget(l, 0, 1);
	l = new QLabel(i18n("Time"), dummy);
	l->setFont(f);
	table->addWidget(l, 0, 2);
	l = new QLabel(i18n("Size"), dummy);
	l->setFont(f);
	table->addWidget(l, 0, 3);
	l = new QLabel(i18n("Score"), dummy);
	l->setFont(f);
	table->addWidget(l, 0, 4);

	QString s;
	QLabel *e[10][5];
	signed i, j;

	for(i = 0; i < 10; i++)
	{
		HighScore hs;
		if(i < highscore.size())
			hs = highscore[i];

		// insert rank
		s.sprintf("%d", i+1);
		e[i][0] = new QLabel(s, dummy);

		// insert name
		if(i < highscore.size())
			e[i][1] = new QLabel(hs.name, dummy);
		else
			e[i][1] = new QLabel("", dummy);

		// insert time
		QTime ti(0,0,0);
		if(i < highscore.size())
		{
			ti = ti.addSecs(hs.seconds);
			s.sprintf("%02d:%02d:%02d", ti.hour(), ti.minute(), ti.second());
			e[i][2] = new QLabel(s, dummy);
		}
		else
		{
			e[i][2] = new QLabel("", dummy);
		}

		// insert size
		if(i < highscore.size())
			s.sprintf("%d x %d", hs.x, hs.y);
		else
			s = "";

		e[i][3] = new QLabel(s, dummy);

		// insert score
		if(i < highscore.size())
		{
			s = QString("%1 %2")
			    .arg(getScore(hs))
			    .arg(hs.gravity ? i18n("(gravity)") : QString(""));
		}
		else
		{
			s = "";
		}

		e[i][4] = new QLabel(s, dummy);
		e[i][4]->setAlignment(Qt::AlignRight);
	}

	f = font();
	f.setBold(true);
	f.setItalic(true);
	for(i = 0; i < 10; i++)
	{
		for(j = 0; j < 5; j++)
		{
			if((int)i == focusitem)
				e[i][j]->setFont(f);

			table->addWidget(e[i][j], i+2, j, Qt::AlignCenter);
		}
	}

	dlg.exec();
}

void App::keyBindings()
{
	KShortcutsDialog::configure( actionCollection(), KShortcutsEditor::LetterShortcutsAllowed, this );

}

/**
 * Show Settings dialog.
 */
void App::showSettings(){
	if(KConfigDialog::showDialog("settings"))
		return;

	//Use the classes exposed by LibKmahjongg for our configuration dialog
	KMahjonggConfigDialog *dialog = new KMahjonggConfigDialog(this, "settings", Prefs::self());
	dialog->addPage(new Settings(0), i18n("General"), "games-config-options");
	dialog->addTilesetPage();
	dialog->addBackgroundPage();
	dialog->setHelp(QString(),"kshisen");
	connect(dialog, SIGNAL(settingsChanged(const QString &)), board, SLOT(loadSettings()));
	dialog->show();
}

#include "app.moc"
