// Author: Denis Kozadaev - (c) 2025

#include <sys/time.h>

#include <cstdlib>

#include <tdeapplication.h>
#include <tdelocale.h>
#include <tqpainter.h>
#include <tqdatetime.h>

#include "gameboard.h"

#define	IS_SET(flags, flag)	(((flags) & (flag)) == (flag))

typedef	enum {
	CFG_TIPS	= 0x01,
	CFG_KNIGHT	= 0x02,
	CFG_RANDOM	= 0x04
} ConfigBits;


GameBoard::GameBoard(TQWidget *parent, const char *name)
	:TQWidget(parent, name), paperColor(0xF0, 0xF0, 0xED)
{
#include "knight_icon.inc"
	struct timeval	tv;

	knight = new TQImage();
	knight->loadFromData(knight_icon, sizeof(knight_icon));

	map = nullptr;
	xpm = kxpm = nullptr;
	map_size = 0;
	config = CFG_RANDOM;

	gettimeofday(&tv, NULL);
	srand(tv.tv_sec * 1000000 + tv.tv_usec);
}

GameBoard::~GameBoard()
{

	if (map != nullptr)
		delete []map;
	if (xpm != nullptr)
		delete xpm;
	if (kxpm != nullptr)
		delete kxpm;

	delete knight;
}


void
GameBoard::resizeEvent(TQResizeEvent *e)
{
	TQWidget::resizeEvent(e);
	if (xpm == nullptr) {
		xpm = new TQPixmap();
		xpm->resize(e->size());
		redrawMap();
	}
}


/*
 * Initializes new game map to the given size
 * (e.g. size X size)
 */
void
GameBoard::initMap(int size)
{
	size_t	mem;

	if (map != nullptr)
		delete []map;

	if ((size != map_size) && (kxpm != nullptr)) {
		delete kxpm;
		kxpm = nullptr;
	}

	map_size = size;
	mem = map_size * map_size;
	map = new int[mem];
	memset(map, 0, mem * sizeof(*map));
	kpos = -1; curr = 0;
}


/*
 * Draw the map into the pixmap
 */
void
GameBoard::redrawMap()
{
	TQPainter	*p;
	int		w, h, x, y, dx, dy, ox, oy, pos, i, size;
	TQFont		fnt;

	if (xpm == nullptr)
		return;

	w = xpm->width();
	h = xpm->height();
	adjustDelta(w, h, dx, dy, ox, oy);
	p = new TQPainter(xpm);
	fnt = p->font();
	fnt.setPointSize(dy * 4 / 11);
	fnt.setItalic(true);
	p->setFont(fnt);
	p->setBrush(TQBrush(paperColor));
	p->setPen(TQt::black);
	h -= oy * 3; w -= ox * 3;
	if ((kpos >= 0) && (kxpm == nullptr))
		kxpm = new TQPixmap(knight->scale(dx - 3, dy - 3));
	for (pos = 0, y = oy; y < h; y += dy) {
		for (x = ox; x < w; x += dx) {
			p->drawRect(x, y, dx, dy);
			if (isEnabled() && (kpos >= 0) && (kpos == pos) &&
				IS_SET(config, CFG_KNIGHT)) {
				/* draw the knight */
				p->drawPixmap(x, y, *kxpm);
			}
			if (map[pos] > 0) {
				if (!isEnabled() && (kpos == pos))
					p->setPen(TQt::red);
				p->drawText(x, y, dx, dy, TQt::AlignCenter,
					TQString::number(map[pos]));
				if (!isEnabled() && (kpos == pos))
					p->setPen(TQt::black);
			}
			pos++;
		}
	}
	if (kpos < 0) {
		fnt.setPointSize(dy * 3 / 5);
		fnt.setBold(true);
		p->setFont(fnt);
		p->setPen(TQColor(0, 0xB3, 0));
		p->drawText(0, 0, w, h, TQt::AlignCenter,
			i18n("Choose a cell"));
	} else {
		predictMoves();
		fromLinear(kpos, x, y);

		if (vmove.size() == 0) {
			/* game over */
			setEnabled(false);
			y = y * dy + 1;
			x = x * dx + 1;
			p->setBrush(TQBrush(TQt::red));
			p->drawRect(x + ox, y + oy, dx - 2, dy - 2);
			p->setPen(TQt::black);
			p->drawText(x + ox, y + oy, dx, dy, TQt::AlignCenter,
				TQString::number(map[kpos]));
		}
		if (isEnabled()) {
			if (IS_SET(config, CFG_TIPS)) {
				ValidMoves::const_iterator it;

				p->setPen(TQPen(TQt::green, 3));
				for (it = vmove.constBegin() ;
					it != vmove.constEnd(); ++it) {
					fromLinear(*it, x, y);
					y = y * dy + 1;
					x = x * dx + 1;
					p->drawRect(x + ox, y + oy,
						dx - 2, dy - 2);
				}
			}
		} else {
			fnt.setPointSize(dy * 3 / 5);
			fnt.setBold(true);
			p->setFont(fnt);
			p->setPen(TQColor(0, 0xB3, 0));
			size = map_size * map_size;
			p->drawText(0, 0, w, h, TQt::AlignCenter,
				(map[kpos] < size)?i18n("Game over")
				:i18n("You passed\nthe tour"));
		}
	}
	delete p;
}


/*
 * Computes deltas by the given width and height of the widget
 */
void
GameBoard::adjustDelta(int w, int h, int &dx, int &dy, int &ox, int &oy)
{

	dx = w / map_size;
	dy = h / map_size;
	ox = (w - dx * map_size) / 2;
	oy = (h - dy * map_size) / 2;
}


/*
 * Fills the vector by valide moves from the current position
 */
void
GameBoard::predictMoves()
{
	int	x, y;

	vmove.clear();
	fromLinear(kpos, x, y);
	ifAppend(vmove, x - 1, y - 2);
	ifAppend(vmove, x + 1, y - 2);
	ifAppend(vmove, x - 2, y -1);
	ifAppend(vmove, x + 2, y - 1);
	ifAppend(vmove, x - 2, y + 1);
	ifAppend(vmove, x + 2, y + 1);
	ifAppend(vmove, x - 1, y + 2);
	ifAppend(vmove, x + 1, y + 2);
}


/*
 * Compute the given arguments are a valid knight's move on the map
 */
void
GameBoard::ifAppend(ValidMoves &vm, int px, int py)
{

	if ((px >= 0) && (px < map_size) && (py >= 0) && (py < map_size)) {
		int	pos = toLinear(px, py);

		if (map[pos] == 0)
			vm.append(pos);
	}
}


/*
 * Converts position to the x and y coordinates on the map
 */
void
GameBoard::fromLinear(int pos, int &x, int &y)
{

	x = pos % map_size;
	y = pos / map_size;
}


/*
 * Generate a starting cell of the knight
 */
void
GameBoard::generateCell()
{
	int	msize = map_size * map_size;

	kpos = rand() % msize;
	if (kpos >= msize)
		kpos -= 2;
	curr = 1;
	map[kpos] = curr;
}


/*
 * Callback to handle mouse button
 */
void
GameBoard::mousePressEvent(TQMouseEvent *e)
{
	int	x = e->x(),
		y = e->y(),
		i;

	i = toMap(x, y);
	if ((kpos < 0) || (mayMove(i) != 0)) {
		curr++;
		kpos = i;
		map[kpos] = curr;
		redrawMap();
		update();
	}
}


void
GameBoard::paintEvent(TQPaintEvent *e)
{

	TQWidget::paintEvent(e);

	if (xpm != nullptr) {
		TQPainter	*p;

		p = new TQPainter(this);
		p->drawPixmap(0, 0, *xpm);
		delete p;
	}
}


/*
 * Returns non-zero if the given cell is allowed to move the knight
 * and 0 otherwise
 */
int
GameBoard::mayMove(int n)
{
	int	res = 0;

	for (ValidMoves::const_iterator it = vmove.constBegin();
		it != vmove.constEnd(); ++it)
		if (n == *it) {
			res++;
			break;
		}

	return (res);
}


/*
 * Returns an index map by the given x and y from the widget
 */
int
GameBoard::toMap(int x, int y)
{
	int	ret = -1;
	int	w, h, dx, dy, ox, oy;

	w = xpm->width();
	h = xpm->height();

	adjustDelta(w, h, dx, dy, ox, oy);
	oy = (y - oy) / dy;
	if (oy >= map_size)
		oy = map_size - 1;
	ox = (x - ox) / dx;
	if (ox >= map_size)
		ox = map_size - 1;
	ret = oy * map_size + ox;

	return (ret);
}


/*
 * Returns linear position by the given coordinates on the map
 */
int
GameBoard::toLinear(int x, int y)
{

	return (y * map_size + x);
}


/*
 * Request a new game for the given board size
 */
void
GameBoard::newGame(int size)
{

	setEnabled(true);
	if (xpm != nullptr) {
		xpm->fill(paperColor);
		update();
	}
	initMap(size);
	if (IS_SET(config, CFG_RANDOM))
		generateCell();
	redrawMap();
	update();
}


/*
 * Toggle highlight tips to move the knight
 */
void
GameBoard::setTips(bool ena)
{

	if (ena)
		config |= CFG_TIPS;
	else
		config &= ~CFG_TIPS;

	redrawMap();
	update();
}


/*
 * Toggle the knight visibility
 */
void
GameBoard::setKnight(bool ena)
{

	if (ena)
		config |= CFG_KNIGHT;
	else
		config &= ~CFG_KNIGHT;

	redrawMap();
	update();
}


/*
 * Toggle random starting cell of the knight
 */
void
GameBoard::setRandomCell(bool ena)
{

	if (ena)
		config |= CFG_RANDOM;
	else
		config &= ~CFG_RANDOM;

	if (curr == 0) {
		generateCell();
		redrawMap();
		update();
	}
}

#include "gameboard.moc"
