#include "gamescene.h"
#include "player.h"
#include "zoneviewwidget.h"
#include "zoneviewzone.h"
#include "phasestoolbar.h"
#include "settingscache.h"
#include <math.h>
#include <QAction>
#include <QGraphicsSceneMouseEvent>
#include <QSet>
#include <QBasicTimer>
#include <QGraphicsView>

GameScene::GameScene(PhasesToolbar *_phasesToolbar, QObject *parent)
	: QGraphicsScene(parent), phasesToolbar(_phasesToolbar)
{
	animationTimer = new QBasicTimer;
	addItem(phasesToolbar);
	connect(settingsCache, SIGNAL(minPlayersForMultiColumnLayoutChanged()), this, SLOT(rearrange()));
}

GameScene::~GameScene()
{
	delete animationTimer;
}

void GameScene::retranslateUi()
{
	for (int i = 0; i < zoneViews.size(); ++i)
		zoneViews[i]->retranslateUi();
}

void GameScene::addPlayer(Player *player)
{
	qDebug("GameScene::addPlayer");
	players << player;
	addItem(player);
	connect(player, SIGNAL(sizeChanged()), this, SLOT(rearrange()));
	connect(player, SIGNAL(gameConceded()), this, SLOT(rearrange()));
}

void GameScene::removePlayer(Player *player)
{
	qDebug("GameScene::removePlayer");
	players.removeAt(players.indexOf(player));
	removeItem(player);
	rearrange();
}

void GameScene::rearrange()
{
	playersByColumn.clear();

	QList<Player *> playersPlaying;
	int firstPlayer = -1;
	for (int i = 0; i < players.size(); ++i)
		if (!players[i]->getConceded()) {
			playersPlaying.append(players[i]);
			if ((firstPlayer == -1) && (players[i]->getLocal()))
				firstPlayer = playersPlaying.size() - 1;
		}
	if (firstPlayer == -1)
		firstPlayer = 0;
	const int playersCount = playersPlaying.size();
	const int columns = playersCount < settingsCache->getMinPlayersForMultiColumnLayout() ? 1 : 2;
	const int rows = ceil((qreal) playersCount / columns);
	qreal sceneHeight = 0, sceneWidth = -playerAreaSpacing;
	QList<int> columnWidth;
	int firstPlayerOfColumn = firstPlayer;
	for (int col = 0; col < columns; ++col) {
		playersByColumn.append(QList<Player *>());
		columnWidth.append(0);
		qreal thisColumnHeight = -playerAreaSpacing;
		const int rowsInColumn = rows - (playersCount % columns) * col; // only correct for max. 2 cols
		for (int j = 0; j < rowsInColumn; ++j) {
			Player *player = playersPlaying[(firstPlayerOfColumn + j) % playersCount];
			if (col == 0)
				playersByColumn[col].prepend(player);
			else
				playersByColumn[col].append(player);
			thisColumnHeight += player->boundingRect().height() + playerAreaSpacing;
			if (player->boundingRect().width() > columnWidth[col])
				columnWidth[col] = player->boundingRect().width();
		}
		if (thisColumnHeight > sceneHeight)
			sceneHeight = thisColumnHeight;
		sceneWidth += columnWidth[col] + playerAreaSpacing;

		firstPlayerOfColumn += rowsInColumn;
	}

	phasesToolbar->setHeight(sceneHeight);
	qreal phasesWidth = phasesToolbar->getWidth();
	sceneWidth += phasesWidth;

	qreal x = phasesWidth;
	for (int col = 0; col < columns; ++col) {
		qreal y = 0;
		for (int row = 0; row < playersByColumn[col].size(); ++row) {
			Player *player = playersByColumn[col][row];
			player->setPos(x, y);
			player->setMirrored(row != rows - 1);
			y += player->boundingRect().height() + playerAreaSpacing;
		}
		x += columnWidth[col] + playerAreaSpacing;
	}

	setSceneRect(sceneRect().x(), sceneRect().y(), sceneWidth, sceneHeight);
	processViewSizeChange(viewSize);
}

void GameScene::toggleZoneView(Player *player, const QString &zoneName, int numberCards)
{
        for (int i = 0; i < zoneViews.size(); i++) {
                ZoneViewZone *temp = zoneViews[i]->getZone();
                if ((temp->getName() == zoneName) && (temp->getPlayer() == player)) { // view is already open
                        zoneViews[i]->close();
                        if (temp->getNumberCards() == numberCards)
                                return;
                }
        }

	ZoneViewWidget *item = new ZoneViewWidget(player, player->getZones().value(zoneName), numberCards, false);
        zoneViews.append(item);
        connect(item, SIGNAL(closePressed(ZoneViewWidget *)), this, SLOT(removeZoneView(ZoneViewWidget *)));
	addItem(item);
	item->setPos(50, 50);
}

void GameScene::addRevealedZoneView(Player *player, CardZone *zone, const QList<ServerInfo_Card *> &cardList)
{
	ZoneViewWidget *item = new ZoneViewWidget(player, zone, -2, true, cardList);
	zoneViews.append(item);
        connect(item, SIGNAL(closePressed(ZoneViewWidget *)), this, SLOT(removeZoneView(ZoneViewWidget *)));
	addItem(item);
	item->setPos(50, 50);
}

void GameScene::removeZoneView(ZoneViewWidget *item)
{
        zoneViews.removeAt(zoneViews.indexOf(item));
	removeItem(item);
}

void GameScene::clearViews()
{
	while (!zoneViews.isEmpty())
		zoneViews.first()->close();
}

void GameScene::closeMostRecentZoneView()
{
	if (!zoneViews.isEmpty())
		zoneViews.last()->close();
}

QTransform GameScene::getViewTransform() const
{
	return views().at(0)->transform();
}

QTransform GameScene::getViewportTransform() const
{
	return views().at(0)->viewportTransform();
}

void GameScene::processViewSizeChange(const QSize &newSize)
{
	viewSize = newSize;
	
	qreal newRatio = ((qreal) newSize.width()) / newSize.height();
	qreal minWidth = 0;
	QList<qreal> minWidthByColumn;
	for (int col = 0; col < playersByColumn.size(); ++col) {
		minWidthByColumn.append(0);
		for (int row = 0; row < playersByColumn[col].size(); ++row) {
			qreal w = playersByColumn[col][row]->getMinimumWidth();
			if (w > minWidthByColumn[col])
				minWidthByColumn[col] = w;
		}
		minWidth += minWidthByColumn[col];
	}
	minWidth += phasesToolbar->getWidth();
	
	qreal minRatio = minWidth / sceneRect().height();
	qreal newWidth;
	if (minRatio > newRatio) {
		// Aspect ratio is dominated by table width.
		newWidth = minWidth;
	} else {
		// Aspect ratio is dominated by window dimensions.
		newWidth = newRatio * sceneRect().height();
	}
	setSceneRect(0, 0, newWidth, sceneRect().height());

	qreal extraWidthPerColumn = (newWidth - minWidth) / playersByColumn.size();
	for (int col = 0; col < playersByColumn.size(); ++col)
		for (int row = 0; row < playersByColumn[col].size(); ++row)
			playersByColumn[col][row]->processSceneSizeChange(minWidthByColumn[col] + extraWidthPerColumn);
}

void GameScene::updateHover(const QPointF &scenePos)
{
	QList<QGraphicsItem *> itemList = items(scenePos, Qt::IntersectsItemBoundingRect, Qt::DescendingOrder, getViewTransform());
	
	// Search for the topmost zone and ignore all cards not belonging to that zone.
	CardZone *zone = 0;
	for (int i = 0; i < itemList.size(); ++i)
		if ((zone = qgraphicsitem_cast<CardZone *>(itemList[i])))
			break;
	
	CardItem *maxZCard = 0;
	if (zone) {
		qreal maxZ = -1;
		for (int i = 0; i < itemList.size(); ++i) {
			CardItem *card = qgraphicsitem_cast<CardItem *>(itemList[i]);
			if (!card)
				continue;
			if (card->getAttachedTo()) {
				if (card->getAttachedTo()->getZone() != zone)
					continue;
			} else if (card->getZone() != zone)
				continue;
			
			if (card->getRealZValue() > maxZ) {
				maxZ = card->getRealZValue();
				maxZCard = card;
			}
		}
	}
	if (hoveredCard && (maxZCard != hoveredCard))
		hoveredCard->setHovered(false);
	if (maxZCard && (maxZCard != hoveredCard))
		maxZCard->setHovered(true);
	hoveredCard = maxZCard;
}

bool GameScene::event(QEvent *event)
{
	if (event->type() == QEvent::GraphicsSceneMouseMove)
		updateHover(static_cast<QGraphicsSceneMouseEvent *>(event)->scenePos());
	
	return QGraphicsScene::event(event);
}

void GameScene::timerEvent(QTimerEvent * /*event*/)
{
	QMutableSetIterator<CardItem *> i(cardsToAnimate);
	while (i.hasNext()) {
		i.next();
		if (!i.value()->animationEvent())
			i.remove();
	}
	if (cardsToAnimate.isEmpty())
		animationTimer->stop();
}

void GameScene::registerAnimationItem(AbstractCardItem *card)
{
	cardsToAnimate.insert(static_cast<CardItem *>(card));
	if (!animationTimer->isActive())
		animationTimer->start(50, this);
}

void GameScene::unregisterAnimationItem(AbstractCardItem *card)
{
	cardsToAnimate.remove(static_cast<CardItem *>(card));
	if (cardsToAnimate.isEmpty())
		animationTimer->stop();
}

void GameScene::startRubberBand(const QPointF &selectionOrigin)
{
	emit sigStartRubberBand(selectionOrigin);
}

void GameScene::resizeRubberBand(const QPointF &cursorPoint)
{
	emit sigResizeRubberBand(cursorPoint);
}

void GameScene::stopRubberBand()
{
	emit sigStopRubberBand();
}
