#include "ai.h"
#include "ai.moc"

#include <qlabel.h>
#include <qhbox.h>
#include <qvbox.h>
#include <klocale.h>
#include <kapp.h>
#include <kconfig.h>
#include <kseparator.h>
#include "board.h"
#include "gpiece.h"
#include "defines.h"

//-----------------------------------------------------------------------------
AIPiece::AIPiece(const Piece *p, const Board *b)
: piece(p)
{
	nbPos = b->matrix().width() - p->width() + 1;
	nbRot = p->nbConfigurations();
}

void AIPiece::reset()
{
	curPos = 0;
	curRot = 0;
}

bool AIPiece::increment()
{
	curPos++;
	if ( curPos==nbPos ) {
		if ( curRot==nbRot ) return TRUE;
		curPos = 0;
		curRot++;
	}
	return FALSE;
}

void AIPiece::setPoints(double points)
{
	if ( (curPos==0 && curRot==0)  // first move
		 || points>betterPoints) { // better move
		betterPoints = points;
		betterRot    = curRot;
		betterDec    = curDec;
	}
}

void AIPiece::place(Board *b)
{
	if ( curRot==3 ) b->rotateRight();
	else for (uint i=0; i<curRot; i++) b->rotateLeft();
	curDec = curPos - b->currentCol() - piece->minX();
	b->moveTo(curDec, 0);

	b->dropDown();	
}

AIPiece::Order AIPiece::emitOrder()
{
	if ( betterRot==3 ) {
		betterRot = 0;
		return RotateRight;
	} else if (betterRot) {
		betterRot--;
		return RotateLeft;
	} else if ( betterDec>0 ) {
		betterDec--;
		return MoveRight;
	} else if ( betterDec<0 ) {
		betterDec++;
		return MoveLeft;
	} else return NoOrderLeft;
}

//-----------------------------------------------------------------------------
const AIElementInfo THINKING_DEPTH
    = { "thinking depth", 0, 0, 0, true, 1, 1, 1, false };

AI::AI(uint tTime, uint oTime)
: timer(this), thinkTime(tTime), orderTime(oTime), stopped(FALSE), board(0)
{
	connect(&timer, SIGNAL(timeout()), SLOT(timeout()));
	add(i18n("Thinking depth"), THINKING_DEPTH);

	add(i18n("Number of occupied lines"), OCC_LINES, nbOccupiedLines);
	add(i18n("Peak-to-peak distance"), PEAK_TO_PEAK, peakToPeak);
	add(i18n("Number of holes"), HOLES, nbHoles);
	add(i18n("Number of spaces (under the mean height)"), SPACES, nbSpaces);
	add(i18n("Mean height"), MEAN, mean);
}

AI::~AI()
{
	delete board;
}

void AI::add(const QString &d, const AIElementInfo &i,
			 int (*f)(const Board &, const Board &))
{
	elts.append(new AIElement(d, i, f));
}

void AI::initThink()
{
	board->copy(*main);
}

void AI::launch(const Board *m)
{
	if ( board==0 ) board = createAIBoard();
	timer.stop(); // clean eventual pending orders

	main = m;
	pieces.clear();
	pieces.append( AIPiece(main->currentPiece(), main) );
	pieces[0].reset();
//	pieces.append( Piece(main->nextPiece(),    main) );
//	pieces[1].reset();

	state = Thinking;
	startTimer();
}

void AI::stop()
{
	if ( timer.isActive() ) {
		timer.stop();
		stopped = TRUE;
	}
}

void AI::start()
{
	if (stopped) {
		startTimer();
		stopped = FALSE;
	}
}

void AI::startTimer()
{
	switch (state) {
	case Thinking:     timer.start(thinkTime, TRUE); break;
	case GivingOrders: timer.start(orderTime, TRUE); break;
	}
}

void AI::timeout()
{
	switch (state) {
	case Thinking:
		if ( think() ) state = GivingOrders;
		break;
	case GivingOrders:
		if ( emitOrder() ) return;
		break;
	}
	
	startTimer();
}

bool AI::emitOrder()
{
	switch (pieces[0].emitOrder()) {
	case AIPiece::RotateRight: emit rotateRight(); return FALSE;
	case AIPiece::RotateLeft:  emit rotateLeft();  return FALSE;
	case AIPiece::MoveRight:   emit moveRight();   return FALSE;
	case AIPiece::MoveLeft:    emit moveLeft();    return FALSE;
	case AIPiece::NoOrderLeft: emit dropDown();    return TRUE;
	}
	return TRUE; //dummy
}	

bool AI::think()
{
	initThink();
	pieces[0].place(board);
//	pieces[1].place(board);
	pieces[0].setPoints( points() );

//	if ( pieces[1].increment() ) {
//		pieces[1].reset();
		return pieces[0].increment();
//	}
//	return FALSE;
}

double AI::points() const
{
	double pts = 0;
	QListIterator<AIElement> it(elts);
	for (; it.current(); ++it) pts += it.current()->points(*main, *board);
	return pts;
}

bool AI::configDialog(QWidget *parent)
{
	AIConfigDialog acd(elts, parent);
	return acd.exec()==QDialog::Accepted;
}

void AI::configChanged()
{
	QListIterator<AIElement> it(elts);
	for (; it.current(); ++it) it.current()->readConfig();
}

int AI::nbOccupiedLines(const Board &, const Board &current)
{
	return current.matrix().height() - current.nbClearLines();
}

int AI::nbHoles(const Board &, const Board &current)
{
	uint nb = 0;
	for (uint i=0; i<current.matrix().width(); i++) {
		for (int j=current.firstColumnBlock(i)-1; j>=0; j--)
			if ( current.matrix()(i, j)==0 ) nb++;
	}
	return nb;
}

int AI::peakToPeak(const Board &, const Board &current)
{
	int min = current.matrix().height()-1;
	for (uint i=0; i<current.matrix().width(); i++)
		min = QMIN(min, current.firstColumnBlock(i));
	return (int)current.firstClearLine()-1 - min;
}

int AI::mean(const Board &, const Board &current)
{
	uint mean = 0;
	for (uint i=0; i<current.matrix().width(); i++)
		mean += current.firstColumnBlock(i);
	return mean / current.matrix().width();
}

int AI::nbSpaces(const Board &main, const Board &current)
{
	int nb = 0;
	int m = mean(main, current);
	for (uint i=0; i<current.matrix().width(); i++) {
		int j = current.firstColumnBlock(i);
		if ( j<m ) nb += m - j;
	}
	return nb;
}

int AI::nbRemoved(const Board &main, const Board &current)
{
	return current.nbRemoved() - main.nbRemoved();
}

//-----------------------------------------------------------------------------
const char *AI_GRP       = "AI";

AIElement::AIElement(const QString &d, const AIElementInfo &i,
					 int (*f)(const Board &, const Board &))
	: description(d), info(i), func(f)
{
	ASSERT( info.tmax>=info.tmin
			&& info.tdef<=info.tmax && info.tdef>=info.tmin );
	ASSERT( info.cmax>=info.cmin && info.cmin>=0
			&& info.cdef<=info.cmax && info.cdef>=info.cmin );

	readConfig();
}

void AIElement::readConfig()
{
	kapp->config()->setGroup(AI_GRP);
	trigger = info.tdef;
	if (info.triggered) {
		QString s = QString("%1 [%2]").arg(info.key).arg("TRIG");
		trigger = kapp->config()->readNumEntry(s, info.tdef);
		trigger = QMAX(QMIN(trigger, info.tmax), info.tmin);
	}
	QString s = QString("%1 [%2]").arg(info.key).arg("COEF");
	coeff = kapp->config()->readDoubleNumEntry(s, info.cdef);
	coeff = QMAX(QMIN(coeff, info.cmax), info.cmin);
}

bool AIElement::normal() const
{
	return info.cmin!=0 || info.cmin!=info.cmax;
}

void AIElement::addToLayout(QGrid *grid, int spacing,
							const char *slotChanged, QObject *receiver)
{
	QLabel *lab = new QLabel(description, grid);
	lab->setFrameStyle(QFrame::Panel | QFrame::Sunken);

	QString s = " ";
	if ( normal() ) s = info.negative ? "-" : "+";
	lab = new QLabel(s, grid);
	lab->setFrameStyle(QFrame::Panel | QFrame::Sunken);
	
	QVBox *vb = new QVBox(grid);
	vb->setMargin(spacing);
	vb->setSpacing(spacing);
	vb->setFrameStyle(QFrame::Panel | QFrame::Sunken);

	KIntNumInput *ki = 0;
	if (info.triggered) {
		QHBox *hb2 = 0;
		if ( normal() ) {
			hb2 = new QHBox(vb);
			hb2->setSpacing(spacing);
			(void)new QLabel(i18n("Trigger"), hb2);
		}
		ki = new KIntNumInput(trigger, normal() ? hb2 : vb);
		ki->setRange(info.tmin, info.tmax);
		connect(ki, SIGNAL(valueChanged(int)),
				SLOT(slotTriggerChanged(int)));
		connect(this, SIGNAL(triggerChanged(int)),
				ki, SLOT(setValue(int)));
	}

	if ( normal() ) {
		KDoubleNumInput *kd	= new KDoubleNumInput(coeff, vb);
		kd->setRange(info.cmin, info.cmax, 1);
		kd->setSpecialValueText(i18n("Off"));
		connect(kd, SIGNAL(valueChanged(double)),
				SLOT(slotCoeffChanged(double)));
		connect(this, SIGNAL(coeffChanged(double)),
				kd, SLOT(setValue(double)));
	}

	connect(this, SIGNAL(changed()), receiver, slotChanged);
}

void AIElement::writeConfig()
{
	KConfig *conf = kapp->config();
	conf->setGroup(AI_GRP);
	if (info.triggered) {
		QString s = QString("%1 [%2]").arg(info.key).arg("TRIG");
		conf->writeEntry(s, trigger);
	}
	if ( normal() ) {
		QString s = QString("%1 [%2]").arg(info.key).arg("COEF");
		conf->writeEntry(s, coeff);
	}
}

void AIElement::setDefault()
{
	coeff = info.cdef;
	emit coeffChanged(coeff);
	trigger = info.tdef;
	emit triggerChanged(trigger);
}

bool AIElement::isDefault() const
{
	return ( coeff==info.cdef && trigger==info.tdef );
}

double AIElement::points(const Board &main, const Board &current) const
{
	if ( func==0 || coeff==0 ) return 0;
	int v = func(main, current);
	if ( info.triggered && v<trigger ) return 0;
	return v * coeff * (info.negative ? -1 : 1);
}

//-----------------------------------------------------------------------------
AIConfigDialog::AIConfigDialog(QList<AIElement> &e,
							   QWidget *parent, const char *name)
: DialogBase(i18n("Configure AI"), Ok | Cancel | Default, Cancel,
			  parent, name), elts(e)
{
	QGrid *grid = new QGrid(3, plainPage());
	grid->setSpacing(spacingHint());
	top->addWidget(grid);
	
	QListIterator<AIElement> it(elts);
	for (; it.current(); ++it)
		it.current()->addToLayout(grid, spacingHint(),
								  SLOT(computeDefault()), this);
	computeDefault();
}

void AIConfigDialog::accept()
{
	QListIterator<AIElement> it(elts);
	for (; it.current(); ++it) it.current()->writeConfig();
	kapp->config()->sync();
	KDialogBase::accept();
}

void AIConfigDialog::slotDefault()
{
	QListIterator<AIElement> it(elts);
	for (; it.current(); ++it) it.current()->setDefault();
}

void AIConfigDialog::reject()
{
	QListIterator<AIElement> it(elts);
	for (; it.current(); ++it) it.current()->readConfig();
	KDialogBase::reject();
}

void AIConfigDialog::computeDefault()
{
	bool def = TRUE;
	QListIterator<AIElement> it(elts);
	for (; it.current(); ++it)
		if ( !it.current()->isDefault() ) {
			def = FALSE;
			break;
		}
	enableButton(Default, !def);
}
