/***************************************************************************
 *   Copyright (C) 2004-2005 Artur Wiebe                                   *
 *   wibix@gmx.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.                          *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   along with this program; if not, write to the                         *
 *   Free Software Foundation, Inc.,                                       *
 *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
 ***************************************************************************/
#include <QLayout>
#include <QDate>
#include <QDebug>
#include <QTimer>

#include "pdn.h"
#include "echeckers.h"
#include "rcheckers.h"
#include "view.h"
#include "common.h"
#include "history.h"
#include "newgamedlg.h"

#include "player.h"
#include "humanplayer.h"
#include "computerplayer.h"


#define MAX_CMD_LEN		80
#define MOVE_PAUSE		1000

// this class is used to note differencies between moves.
class myDiff {
public:
	myDiff(int pos, int from, int to)
		: m_pos(pos), m_from(from), m_to(to) {}
	int m_pos;
	int m_from;
	int m_to;
};


myView::myView(QWidget* parent)
	: QFrame(parent)
{
	/*
	 * board & info 
	 */
	m_board = new myBoard(this);
	connect(m_board, SIGNAL(fieldClicked(int)),
			this, SLOT(slot_click(int)));

	m_history = new myHistory(this);
	connect(m_history, SIGNAL(previewGame(int)),
			this, SLOT(slot_preview_game(int)));
	connect(m_history, SIGNAL(applyMoves(const QString&)),
			this, SLOT(slot_apply_moves(const QString&)));
	connect(m_history, SIGNAL(newMode(bool, bool)),
			this, SLOT(slot_new_mode(bool, bool)));
	connect(this, SIGNAL(working(bool)),
			m_history, SLOT(slotWorking(bool)));

	QHBoxLayout* hb = new QHBoxLayout(0);
	hb->addWidget(m_board);
	hb->addSpacing(5);
	hb->addWidget(m_history);


	/*
	 *
	 */
	m_log = new QTextEdit(this);
    	m_log->setFixedHeight(100); //FIXME
	m_log->setReadOnly(true);


	/*
	 * it's the final layout.
	 */
	QVBoxLayout* vb = new QVBoxLayout(this);
	vb->addLayout(hb);
	vb->addWidget(m_log);
	vb->setSizeConstraint(QLayout::SetFixedSize);


	/*
	 * game init
	 */
	m_player = m_current = 0;
}


myView::~myView()
{
	if(m_player) {
		delete m_player;
		delete m_player->opponent();
	}
}


void myView::setEnabled(bool b)
{
	m_board->setEnabled(b);
	if(b)
		setCursor(Qt::ArrowCursor);	// should be m_board bound.
	else
		setCursor(Qt::WaitCursor);
}


void myView::setTheme(const QString& path)
{
	m_board->setTheme(path, m_player ? m_player->isWhite() : true);
	m_history->setFixedHeight(m_board->height());
}


void myView::newGame(int rules, bool freeplace,
		const QString& name, bool is_white,
		int opponent, const QString& opp_name, int skill)
{
	m_freeplace_from = -1;

	if(m_player) {
		delete m_player;
		delete m_player->opponent();
	}

	m_board->setColorWhite(is_white);

	// create players
	myPlayer* plr = new myHumanPlayer(name, is_white, false);
	myPlayer* opp = 0;
	if(opponent==HUMAN)
		opp = new myHumanPlayer(opp_name, !is_white, true);
	else
		opp = new myComputerPlayer(opp_name, !is_white, skill);

	emit working(true);


	/*
	 * set up player stuff. slots/signals.
	 */
	m_player = plr;

	plr->setOpponent(opp);
	opp->setOpponent(plr);

	plr->disconnect();
	opp->disconnect();

	connect(plr, SIGNAL(moveDone(const QString&)),
			this, SLOT(slot_move_done(const QString&)));

	connect(opp, SIGNAL(moveDone(const QString&)),
			this, SLOT(slot_move_done(const QString&)));


	/*
	 * create game board.
	 */
	m_board->setGame(rules);

	m_board->reset();
	m_history->clear();

	begin_game(1, freeplace);
}


void myView::begin_game(unsigned int round, bool freeplace)
{
	if(m_clear_log)
		m_log->clear();

	m_board->adjustNotation(m_player->isWhite());

	m_history->newPdn(APPNAME" Game", freeplace);
	m_history->setTag(PdnGame::Type, QString::number(m_board->type()));
	m_history->setTag(PdnGame::Date,
		QDate::currentDate().toString("yyyy.MM.dd"));
	m_history->setTag(PdnGame::Result, "*");
	m_history->setTag(PdnGame::Round, QString::number(round));


	/*
	 * go!
	 */
	myPlayer* last_player = get_first_player()->opponent();

	m_game_over = false;
	m_aborted = false;
	m_current = last_player;

	// setup names
	if(m_player->isWhite()) {
		m_history->setTag(PdnGame::White, m_player->name());
		m_history->setTag(PdnGame::Black, m_player->opponent()->name());
	} else {
		m_history->setTag(PdnGame::White, m_player->opponent()->name());
		m_history->setTag(PdnGame::Black, m_player->name());
	}

	if(m_history->isFreePlacement())
		emit working(false);
	else
		slot_move_done(m_board->game()->toString(false));
}


bool myView::check_game_over()
{
	if(m_game_over)		// no further checks
		return true;

	m_game_over = true;

	bool player_can = m_board->game()->checkMove1()
		|| m_board->game()->checkCapture1();
	bool opp_can = m_board->game()->checkMove2()
		|| m_board->game()->checkCapture2();

	// player cannot go but opponent can -> player lost.
	if(/*FIXME*/m_player==m_current && !player_can && opp_can) {
		you_won(false);
		return m_game_over;
	}
	// player can go but opponent cannot -> player won.
	if(/*FIXME*/m_player!=m_current && player_can && !opp_can) {
		you_won(true);
		return m_game_over;
	}
	// neither of the player can go -> draw.
	if(!player_can && !opp_can) {
		add_log(myView::System, tr("Drawn game."));
		m_history->setTag(PdnGame::Result, "1/2-1/2");
		return m_game_over;
	}

	m_game_over = false;
	return m_game_over;
}


void myView::slot_click(int field_num)
{
	if(m_game_over || m_aborted)
		return;

	if(m_history->isPaused()) {
		if(m_history->isFreePlacement()) {
			// FIXME - hightlight fields
			if(m_freeplace_from < 0) {
				m_freeplace_from = field_num;
				m_board->selectField(field_num, true);
			} else {
				m_board->selectField(m_freeplace_from, false);
				m_board->doFreeMove(m_freeplace_from,
						field_num);
				m_freeplace_from = -1;
			}
		}
	} else {
		bool select = false;
		QString err_msg;

		if(!m_current->fieldClicked(field_num, &select, err_msg)) {
			add_log(myView::Warning, m_current->name()+": "
					+ (err_msg.length()
						? err_msg
						: tr("Invalid move.")));
		} else {
			m_board->selectField(field_num, select);
		}
	}
}


void myView::slotNextRound()
{
	if(m_aborted)
		return;

	m_player->setWhite(!m_player->isWhite());
	m_player->opponent()->setWhite(!m_player->isWhite());

	m_board->setColorWhite(m_player->isWhite());
	m_board->reset();

	unsigned int round = m_history->getTag(PdnGame::Round).toUInt() + 1;
	begin_game(round, m_history->isFreePlacement());
}


void myView::slotStopGame()
{
	m_player->stop();
	m_player->opponent()->stop();
}


void myView::stop_game(const QString& msg)
{
	m_game_over = true;
	m_aborted = true;

	QString text(tr("Game aborted.")+(!msg.isEmpty() ? "\n"+msg : ""));
	add_log(myView::System, text);

	emit working(false);
}


void myView::slot_move_done(const QString& board_str)
{
	if(m_history->isPaused())	// FIXME - ???
		return;

	perform_jumps(m_board->game()->toString(false), board_str);

	// show who is next?
	m_current = m_current->opponent();
	m_history->setCurrent(m_current->name());

	if(!m_current->isHuman()) {
		emit working(true);
	} else {
		emit working(false);
	}

	if(m_current->opponent()->isHuman() && !m_current->isHuman())
		QTimer::singleShot(MOVE_PAUSE, this,
				SLOT(slot_move_done_step_two()));
	else
		slot_move_done_step_two();
}

void myView::slot_move_done_step_two()
{
	//
	m_current->yourTurn(m_board->game());

	if(check_game_over())
		emit working(false);
}


void myView::you_won(bool yes)
{
	if(yes&&m_player->isWhite() || !yes&&!m_player->isWhite()) {
		m_history->setTag(PdnGame::Result, "1-0");	// white wins
		add_log(myView::System, tr("White wins!"));
	} else {
		m_history->setTag(PdnGame::Result, "0-1");	// black wins
		add_log(myView::System, tr("Black wins!"));
	}

	emit working(false);
}


bool myView::openPdn(const QString& fn)
{
	emit working(false);

	m_current->stop();

	QString log_text;
	if(!m_history->openPdn(fn, log_text)) {
		return false;
	}

	if(log_text.length()) {
		add_log(myView::System, tr("Opened:")+" "+fn);
		add_log(myView::Error, log_text.trimmed());
		add_log(myView::Warning, tr("Warning! Some errors occured."));
	}

	return true;
}


bool myView::savePdn(const QString& fn)
{
	if(!m_history->savePdn(fn)) {
		qDebug() << __PRETTY_FUNCTION__ << "failed.";
		return false;
	}
	add_log(myView::System, tr("Saved:")+" "+fn);
	return true;
}


void myView::slot_new_mode(bool paused, bool freeplace)
{
	if(paused) {
		if(freeplace)
			m_board->setCursor(Qt::PointingHandCursor);
		else
			m_board->setCursor(Qt::ForbiddenCursor);
	} else {
		m_board->setCursor(Qt::ArrowCursor);
	}

	// resume game: ask info for who is next, black or white.XXX FIXME TODO
	if(!paused) {
		myPlayer* next = 0;
		if(m_history->moveCount()%2==0)
			next = get_first_player();
		else
			next = get_first_player()->opponent();

		m_current = next->opponent();
		slot_move_done(m_board->game()->toString(false));
	}
}


void myView::slot_preview_game(int rules)
{
	if(rules!=RUSSIAN && rules!=ENGLISH) {
		qDebug() << __PRETTY_FUNCTION__ << rules << "Wrong game type.";
		return;
	}

	m_board->setGame(rules);

	if(m_player->isWhite() && rules==RUSSIAN) {
		m_player->setName(m_history->getTag(PdnGame::White));
		m_player->opponent()->setName(m_history->getTag(PdnGame::Black));
	} else {
		m_player->setName(m_history->getTag(PdnGame::Black));
		m_player->opponent()->setName(m_history->getTag(PdnGame::White));
	}

	// FIXME
	m_player->setWhite(rules==RUSSIAN);// FIXME TODO
	m_player->opponent()->setWhite(!m_player->isWhite());
	m_board->setColorWhite(m_player->isWhite());
	m_board->adjustNotation(m_player->isWhite());
}


void myView::slot_apply_moves(const QString& moves)
{
	QStringList move_list= moves.split(MOVE_SPLIT, QString::SkipEmptyParts);

	m_board->reset();

	bool white_player = get_first_player()->isWhite();
	foreach(QString move, move_list) {
		m_board->doMove(move, white_player);
		white_player = !white_player;
	}

	// set current player who pulls next. assume is white.
	m_current = (m_player->isWhite() ? m_player : m_player->opponent());
	if(!white_player)
		m_current = m_current->opponent();
	m_history->setCurrent(m_current->name());
}


void myView::add_log(enum LogType type, const QString& text)
{
	QString str = text;
	str = str.replace('<', "&lt;");
	str = str.replace('>', "&gt;");

	QString tag_b, tag_e;
	switch(type) {
	case Error:	tag_b="<ul><pre>"; tag_e="</pre></ul>"; break;
	case Warning:	tag_b="<b>"; tag_e="</b>"; break;
	case System:	tag_b="<font color=\"blue\">"; tag_e="</font>"; break;
	default:	break;
	}

	m_log->append(tag_b + str + tag_e);

	m_log->ensureCursorVisible();
}


void myView::perform_jumps(const QString& from_board, const QString& to_board)
{
	if(from_board==to_board) {
		return;
	}

	QString new_to_board = to_board;

	//qDebug("F:%s\nT:%s", from_board.latin1(), new_to_board.latin1());

	// diff
	QList<myDiff*> diff_list;

	// collect information
	for(int i=0; i<32; i++) {
		if(from_board[2*i]!=new_to_board[2*i]
				|| from_board[2*i+1]!=new_to_board[2*i+1]) {
			myDiff* diff = new myDiff(i,
					from_board.mid(2*i, 2).toInt(),
					new_to_board.mid(2*i, 2).toInt());
			diff_list.append(diff);

			//qDebug(">%d: %d->%d", diff->m_pos, diff->m_from, diff->m_to);
		}
	}

	int from_pos = -1;
	int to_pos = -1;
	bool captured = (diff_list.count()>2);

	int man = -1;
	// find the dest. first: so we have the man moved.
	foreach(myDiff* diff, diff_list) {
		if(diff->m_to!=FREE) {
			man = diff->m_to;
			to_pos = diff->m_pos;
			break;
		}
	}

	int king = -1;
	switch(man) {
	case MAN1:	king=KING1; break;
	case KING1:	king=MAN1; break;
	case MAN2:	king=KING2; break;
	case KING2:	king=MAN2; break;
	}
	// find src.
	foreach(myDiff* diff, diff_list) {
		if(diff->m_to==FREE) {
			if(diff->m_from==man || diff->m_from==king) {
				from_pos = diff->m_pos;
				break;
			}
		}
	} 

	/*
	  qDebug("  to_pos=%d with man/king=%d from=%d", to_pos, man,
	  from_pos);
	  */

	// finally - animate :)
	QString move = m_board->doMove(from_pos, to_pos, m_current->isWhite());
	m_history->appendMove(move.replace("?", captured ? "x" : "-" ), "");

	qDeleteAll(diff_list);
}
 
void myView::setNotation(bool enabled, bool show_above)
{
	// TODO - intermediate function - remove somehow!
	m_board->setNotation(enabled, show_above);
}


void myView::setNotationFont(const QFont& f)
{
	// TODO - intermediate function - remove somehow!
	m_board->setNotationFont(f);
}


myPlayer* myView::get_first_player() const
{
	bool white = m_board->type()==RUSSIAN ? true : false;
	// it is white.
	if((white && m_player->isWhite()) || (!white && !m_player->isWhite()))
		return m_player;
	return m_player->opponent();
}