#include "fe_board.h"

#include <math.h>
#include "defines.h"
#include "misc_ui.h"
#include "fe_piece.h"

// "width" equals "height" and is an odd number
// (graphism depends on these hypothesis)
const uint BLOCK_WIDTH  = 21;
const uint BLOCK_HEIGHT = BLOCK_WIDTH;

const uint BOARD_WIDTH  = 6;
const uint BOARD_HEIGHT = 15;

const uint NB_LEDS     = 5;
const uint MAX_TO_SEND = 6;

FEBoard::FEBoard(bool graphic, QWidget *parent, const char *name )
: Board(BLOCK_WIDTH, BLOCK_HEIGHT, BOARD_WIDTH, BOARD_HEIGHT,
		graphic, new GiftPool(NB_LEDS, MAX_TO_SEND, parent), parent, name),
  groups(matrix().width(), matrix().height())
{
	info.baseTime         = 1000;
	info.dropDownTime     = 10;
	info.beforeGlueTime   = 10;
	info.afterGlueTime    = 10;
	info.beforeRemoveTime = 150;
	info.afterRemoveTime  = 10;
	info.afterGiftTime    = 10;
}

void FEBoard::copy(const GenericTetris &g)
{
	Board::copy(g);

	const FEBoard &f = (const FEBoard &)g;
//	fragmentation = f.fragmentation;
//	groups        = f.groups;
//	rem           = f.rem;
	nbPuyos       = f.nbPuyos;
//	chained       = f.chained;
//	giftRest      = f.giftRest;
}

void FEBoard::init(int seed)
{
	Board::init(seed);
	nbPuyos  = 0;
	chained  = 0;
	giftRest = 0;
}

bool FEBoard::afterGlue(bool doAll)
{
	bool fragmentation = FALSE;
	for (uint j=1; j<firstClearLine(); j++) // line 0 cannot fall ...
		for (uint i=0; i<matrix().width(); i++) {
		    if ( block(i, j)==0 || block(i, j-1)!=0 ) continue;
			if ( j>=2 && block(i, j-2)==0 ) fragmentation = TRUE;
			moveBlock(i, j, i, j-1);
		}
	if (graphic()) computeNeighbours();
	
	if (doAll && fragmentation) afterGlue(TRUE);
	return fragmentation;
}

bool FEBoard::neighbour(uint i, uint j, uint n, uint &ni, uint &nj) const
{
	switch (n) {
	case 0: ni = i-1; nj = j;   return i>0;
	case 1: ni = i+1; nj = j;   return i<matrix().width()-1;
	case 2: ni = i;   nj = j-1; return j>0;
	case 3: ni = i;   nj = j+1; return j<matrix().height()-1;
	}
	ASSERT(false);
	return FALSE;
}

void FEBoard::removeSurroundingGarbage(uint i, uint j)
{
	uint ni, nj;
	for (uint n=0; n<4; n++) {
		if ( !neighbour(i, j, n, ni, nj) ) continue;
		if ( block(ni, nj)==0 || !block(ni, nj)->isGarbage() ) continue;
		removeBlock(ni, nj);
	}
}

void FEBoard::remove()
{
	for (uint j=0; j<firstClearLine(); j++)
		for (uint i=0; i<matrix().width(); i++)
		    if ( groups(i, j)>=4 ) {
				removeBlock(i, j);
				removeSurroundingGarbage(i, j);
			}
	
    // score calculation from another puyo game
	// not sure it is the "official" way
	uint _nbPuyos = rem.size();
	uint _puyos = 0;
	for (uint k=0; k<rem.size(); k++) _puyos += rem[k];

	nbPuyos += _nbPuyos;
	updateRemoved(nbRemoved() + _puyos);
	chained ++;

	uint base  = 10 * _nbPuyos;
	uint bonus = _puyos - 3;
	if ( _puyos==11 ) bonus += 2;
	if ( _nbPuyos>1 ) bonus += 3*(1 << (_nbPuyos-2));
	if ( chained>1  ) bonus += 1 << (chained+1);
	uint dscore = base*bonus;
	giftRest += dscore;
	updateScore(score() + dscore);

	// update level
	if ( (nbRemoved()/100)!=((nbRemoved()-_puyos)/100) )
		updateLevel(level()+1);
}

Board::AfterRemoveResult FEBoard::afterRemove(bool doAll, bool)
{
	if ( afterGlue(doAll) && !doAll ) return NeedAfterRemove;
	if ( needRemoving() )             return NeedRemoving;
	chained  = 0;
	return Done;
}

bool FEBoard::needRemoving()
{
	// #### optimize this ... it's tricky but it seems doable
	groups.fill(0);
	rem.resize(0);
 	for (uint j=0; j<firstClearLine(); j++)
		for (uint i=0; i<matrix().width(); i++) {
			if ( block(i, j)==0 || block(i, j)->isGarbage() ) continue;
			if ( groups(i, j)!=0 ) continue;
			uint v = block(i, j)->value();
			uint nb = 1;
			treatGroup(i, j, v, nb, FALSE);
			treatGroup(i, j, v, nb, TRUE);
			if ( nb>=4 ) {
				uint s = rem.size();
				rem.resize(s+1);
				rem[s] = nb;
			}
		}
	return rem.size();
}

void FEBoard::checkBlock(uint i, uint j, uint v, uint &nb, bool set)
{
	if ( block(i, j)==0 ) return;
	if ( block(i, j)->value()!=v ) return;
	if ( groups(i, j)!=(set ? -1 : 0) ) return;
	treatGroup(i, j, v, nb, set);
	if (!set) nb++;
}

void FEBoard::treatGroup(uint i, uint j, uint v, uint &nb, bool set)
{
	groups(i, j) = (set ? (int)nb : -1);
	if ( i>=1 )                  checkBlock(i-1,   j, v, nb, set);
	if ( j>=1 )                  checkBlock(  i, j-1, v, nb, set);
	if ( i+1<matrix().width() )  checkBlock(i+1,   j, v, nb, set);
	if ( j+1<matrix().height() ) checkBlock(  i, j+1, v, nb, set);
}

/*****************************************************************************/
// Multiplayers methods
uint FEBoard::gift()
{
	uint n   = giftRest / 70;
	giftRest = giftRest % 70;
	return n;
}

bool FEBoard::putGift(uint n)
{
	QArray<bool> free(matrix().width());

	// garbage blocks are put randomly on conlumns with more than 5 free lines.
	uint nbFree = 0;
	for (uint i=0; i<free.size(); i++) {
		int f = firstColumnBlock(i);
		if ( f==-1 || f>=(int)matrix().height()-5 ) free[i] = false;
		else {
			free[i] = true;
			nbFree++;
		}
	}
	uint nb = QMIN(nbFree, n);
	while (nbFree && nb) {
		uint k = (uint)randomGarbage.getLong(nbFree);
		uint l = 0;
		for (uint i=0; i<free.size(); i++) {
			if ( free[i]==false ) continue;
			if ( k==l ) {
				setBlock(i, matrix().height()-1,
						 currentPiece()->garbageBlock());
				free[i] = false;
				nbFree--;
				nb--;
				break;
			}
			l++;
		}
	}
	computeClearLines();
	return TRUE;
}

/*****************************************************************************/
// Graphic methods
uint FEBoard::drawCode(uint i, uint j) const
{
	uint v = block(i, j)->value();
	uint c = 0;
	if ( i>0 && block(i-1, j) && block(i-1, j)->value()==v ) c |= FE_LEFT;
	if ( i+1<matrix().width() && block(i+1, j)
		 && block(i+1, j)->value()==v ) c |= FE_RIGHT;
	if ( j>0 && block(i, j-1) && block(i, j-1)->value()==v ) c |= FE_DOWN;
	if ( j+1<matrix().height() && block(i, j+1)
		 && block(i, j+1)->value()==v ) c |= FE_UP;
	return c;
}

void FEBoard::computeNeighbours()
{
	for (uint j=0; j<firstClearLine(); j++)
		for (uint i=0; i<matrix().width(); i++) {
			if ( block(i, j)==0 || block(i, j)->isGarbage() ) continue;
			block(i, j)->setMode( drawCode(i, j) );
		}
}

bool FEBoard::beforeRemove(bool first)
{
	if (first) loop = 0;
	else loop++;

	for (uint j=0; j<firstClearLine(); j++)
		for (uint i=0; i<matrix().width(); i++)
			if ( groups(i, j)>=4 ) block(i, j)->toggleLight();

	return ( loop!=3 );
}
