#include "gtetris.h"

#include "gpiece.h"

GenericTetris::GenericTetris(uint width, uint height, bool graphic)
: _nextPiece(new Piece), _currentPiece(new Piece),
  _graphic(graphic), _matrix(width, height)
{
	_matrix.fill(0);
}

void GenericTetris::copy(const GenericTetris &g)
{
	// copy to non graphic
	_score         = g._score;
	_level         = g._level;
	_nbRemoved     = g._nbRemoved;
	_nbClearLines  = g._nbClearLines;
	_currentCol    = g._currentCol;
	_currentLine   = g._currentLine;
	_nextPiece->copy(g._nextPiece, TRUE);
	_currentPiece->copy(g._currentPiece, TRUE);
	for (uint i=0; i<_matrix.width(); i++)
		for (uint j=0; j<_matrix.height(); j++) {
			if ( g._matrix(i, j) )
				_matrix(i, j) = new Block(g._matrix(i, j)->value());
			else _matrix(i, j) = 0;
		}
}	

void GenericTetris::clear()
{
	_currentCol   = 0;
	_currentLine  = -1;
	_nbClearLines = _matrix.height();
	for (uint i=0; i<_matrix.width(); i++)
		for (uint j=0; j<_matrix.height(); j++) removeBlock(i, j);
}

GenericTetris::~GenericTetris()
{
	delete _currentPiece;
	delete _nextPiece;
	clear();
}

void GenericTetris::setBlockInfo(BlockInfo *main, BlockInfo *next)
{
	ASSERT( _graphic );
	_nextPiece->setBlockInfo(next);
	_currentPiece->setBlockInfo(main);
	clear(); // on destruction : prevent segfault
}

void GenericTetris::init(int seed)
{
	ASSERT( _graphic );
	_random.setSeed(seed);
	_nextPiece->setRandomSequence(&_random);
}

void GenericTetris::start()
{
	ASSERT( _graphic );
	updateScore(0);
    updateLevel(1);
	updateRemoved(0);
	clear();
	_nextPiece->generateNext();
	newPiece();
}

void GenericTetris::dropDown()
{
	int dropHeight = moveTo(0, -_currentLine);
	if ( dropHeight>=0 ) pieceDropped(dropHeight);
}

void GenericTetris::oneLineDown()
{
	if ( moveTo(0, -1)==0 ) pieceDropped(0);
}

void GenericTetris::newPiece()
{
	_currentLine  = _matrix.height() - 1 + _nextPiece->minY();
    _currentCol   =	(_matrix.width() - _nextPiece->width())/2
		- _nextPiece->minX();
	if ( _graphic ) {
		if ( !canPosition(_currentCol, _currentLine, _nextPiece)) {
			_currentLine = -1;
			gameOver();
			return;
		}
		showPieceConfig(FALSE);
	}
	_currentPiece->copy(_nextPiece, FALSE);
	if ( _graphic ) {
		_currentPiece->move(toX(_currentCol), toY(_currentLine));
		showPieceConfig(TRUE);
		showNextPiece(FALSE);
		_nextPiece->generateNext();
		_nextPiece->moveCenter();
		showNextPiece(TRUE);
	}
}

bool GenericTetris::canPosition(uint col, uint line, const Piece *piece) const
{
    for(uint k=0; k<piece->nbBlocks(); k++) {
        uint i = piece->col(k, col);
        uint j = piece->line(k, line);
        if ( i>=_matrix.width() || j>=_matrix.height() || _matrix(i, j)!=0 )
			return FALSE; // outside or something in the way
    }
    return TRUE;
}

int GenericTetris::moveTo(int decX, int decY)
{
	if ( _currentLine==-1 ) return -1;
	ASSERT(decX==0 || decY==0);
	int newCol  = _currentCol;
	int newLine = _currentLine;
	int dx = 0, dy = 0;
	uint n, i;

	if (decX) {
		dx = (decX<0 ? -1 : 1);
		n  = (uint)(decX<0 ? -decX : decX);
	} else {
		dy = (decY<0 ? -1 : 1);
		n  = (uint)(decY<0 ? -decY : decY);
	}

	for (i=0; i<n; i++) {
		if ( !canPosition(newCol + dx, newLine + dy, _currentPiece) ) break;
		newCol  += dx;
		newLine += dy;
	}
	if ( i!=0 ) { // piece can be moved
		_currentCol  = newCol;
		_currentLine = newLine;
		if (_graphic) {
			showPieceConfig(FALSE);
			_currentPiece->move(toX(newCol),toY(newLine));
			showPieceConfig(TRUE);
		}
	}
	return i;
}

void GenericTetris::rotate(bool left)
{
	if ( _currentLine==-1 ) return;
	Piece tmp;
	tmp.copy(_currentPiece, TRUE);
    tmp.rotate(left, 0, 0);
    if ( canPosition(_currentCol, _currentLine, &tmp) ) {
		int x = 0, y = 0;
		if (_graphic) {
			showPieceConfig(FALSE);
			x = toX(_currentCol);
			y = toY(_currentLine);
		}
		_currentPiece->rotate(left, x, y);
		if (_graphic) showPieceConfig(TRUE);
	}
}

void GenericTetris::computeClearLines()
{
	_nbClearLines = 0;
	for (uint j=_matrix.height(); j>0; j--) {
		for (uint i=0; i<_matrix.width(); i++)
			if ( _matrix(i, j-1)!=0 ) return;
		_nbClearLines++;
	}
}

void GenericTetris::setBlock(uint i, uint j, Block *b)
{
	ASSERT( b && _matrix(i, j)==0 );
	_matrix(i, j) = b;
	if (_graphic) b->move(toX(i), toY(j));
}

void GenericTetris::removeBlock(uint i, uint j)
{
	delete _matrix(i, j);
	_matrix(i, j) = 0;
}

void GenericTetris::moveBlock(uint srcI, uint srcJ, uint destI, uint destJ)
{
	ASSERT( _matrix(destI, destJ)==0 );
	if ( _matrix(srcI, srcJ) ) {
		setBlock(destI, destJ, _matrix(srcI, srcJ));
		_matrix(srcI, srcJ) = 0;
	}
}

int GenericTetris::toX(uint i) const
{
	return _currentPiece->toX(i);
}

int GenericTetris::toY(uint j) const
{
	return _currentPiece->toY(_matrix.height() - 1 - j);
}

void GenericTetris::gluePiece()
{
	for(uint k=0; k<_currentPiece->nbBlocks(); k++) {
		int i = _currentPiece->col(k, currentCol());
		int j = _currentPiece->line(k, currentLine());
		setBlock(i, j, _currentPiece->takeBlock(k));
	}
	computeClearLines();
}

void GenericTetris::bumpCurrentPiece(int dec)
{
	ASSERT( graphic() );
	_currentPiece->move(toX(_currentCol), toY(_currentLine) + dec);
}

void GenericTetris::partialMoveBlock(uint i, uint j, int dec)
{
	ASSERT( graphic() );
	if ( _matrix(i, j)==0 ) return;
	_matrix(i, j)->move(toX(i), toY(j) + dec);
}
