/***************************************************************************
                          board_2d.cpp  -  description
                             -------------------
    begin                : Fri Feb 28 2003
    copyright            : (C) 2003 by The Knights Project
    email                : knights-general@lists.sourceforge.net
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 ***************************************************************************/

#include "board_2d.moc"
#include "resource.h"
#include "logic.h"
#include "knightspixcache.h"
#include <kiconeffect.h>
#include <tqpainter.h>
#include <tqtimer.h>

static int MAX_STEPS = 18;

board_2d::board_2d(TQWidget *parent, const char *name, resource *Rsrc, logic *Lgc  ) : board_base(parent,name,Rsrc,Lgc)
{
	updateX1 = updateY1 = 4000;
	updateX2 = updateY2 = -4000;
	init = true;
	premoveFrom = Null;
	premoveTo = Null;
	DragSprite = NULL;
	lastMoveWasDrag = false;
	cache = myResource->pixCache;
	int size = 8 + ( 1 * ( myResource->ThemeBorder == true ) );
	sprites.setAutoDelete( true );

	/* Setup Pixmaps */
	myself.setOptimization( TQPixmap::BestOptim );
	myself.resize( myResource->ThemeSize * size, myResource->ThemeSize * size);
	myself.fill();

	/* Setup self */
	setBackgroundMode( TQt::NoBackground );
	show();
}
board_2d::~board_2d()
{
}
///////////////////////////////////////
//
//  board_2d::coords
//
///////////////////////////////////////
TQPoint board_2d::coords( const int &rank, const int &file )
{
	TQPoint tmp;
	if( orientation == 0 )
	{
		tmp.setX( myResource->ThemeSize * file );
		tmp.setY( myResource->ThemeSize * ( 7 - rank ) );
	}
	else
	{
		tmp.setX( myResource->ThemeSize * ( 7 - file ) );
		tmp.setY( myResource->ThemeSize * rank );
	}
	if( myResource->ThemeBorder )
	{
		tmp.setX( tmp.x() + ( myResource->ThemeSize >> 1 ) );
		tmp.setY( tmp.y() + ( myResource->ThemeSize >> 1 ) );
	}
	return tmp;
}
///////////////////////////////////////
//
//	board_2d::position
//
///////////////////////////////////////
int board_2d::position( const TQPoint &_point )
{
	int file, rank;
	int themeSize = myResource->ThemeSize;
	TQPoint point( _point );

	if( myResource->ThemeBorder )
	{
		point.setX( point.x() - ( themeSize >> 1 ) );
		point.setY( point.y() - ( themeSize >> 1 ) );
	}
	if( !orientation )
	{
		file = point.x() / themeSize;
		rank = 7 - ( point.y() / themeSize );
	}
	else
	{
		file = 7 - ( point.x() / themeSize );
		rank = point.y() / themeSize;
	}
	return ( ( rank << 3 ) + file );
}
///////////////////////////////////////
//
//	board_2d::drawMove
//
///////////////////////////////////////
void board_2d::drawMove( const ChessMove &chessMove, const bool &reverse )
{
	char fromPtr, toPtr, takenPtr(Null);
	if( reverse )
	{
		fromPtr = ( chessMove.toRank << 3 ) + chessMove.toFile;
		toPtr = ( chessMove.fromRank << 3 ) + chessMove.fromFile;
	}
	else
	{
		fromPtr = ( chessMove.fromRank << 3 ) + chessMove.fromFile;
		toPtr = ( chessMove.toRank << 3 ) + chessMove.toFile;
	}

	/* Position where Man was taken != Target Position; ie. en passant */
	if( chessMove.ManTaken != Null )
	{
		takenPtr = myLogic->Pointer( myLogic->chessman[ chessMove.ManTaken ].File, myLogic->chessman[ chessMove.ManTaken ].Rank );
	}

	/* Show Highlights */
	if( myResource->OPTION_Show_Last_Move )
	{
		myLogic->current[ fromPtr ].Note = NOTE_MOVE;
		if( chessMove.ManTaken != Null )
		{
			myLogic->current[ toPtr ].Note = NOTE_ATTACK;
		}
		else
		{
			myLogic->current[ toPtr ].Note = NOTE_MOVE;
		}
	}

	/* Show Animation */
	if( myResource->OPTION_Animate_Moves && !lastMoveWasDrag && isVisible() )
	{
		TQTimer::singleShot( 0, this, TQ_SLOT( updateSprites() ) );
		sprite *spritePtr = new sprite;
		spritePtr->Steps = 1;
		spritePtr->Restore = false;
		spritePtr->POSITION_Origin = fromPtr;
		spritePtr->POSITION_Destination = toPtr;
		spritePtr->POSITION_TargetTaken = takenPtr;
		if( !reverse )
		{
			spritePtr->POINT_Origin = coords( chessMove.fromRank, chessMove.fromFile );
			spritePtr->POINT_Destination = coords( chessMove.toRank, chessMove.toFile );
			spritePtr->PIXMAP_Sprite = getChessman( spritePtr->POSITION_Destination );
		}
		else
		{
			spritePtr->POINT_Origin = coords( chessMove.toRank, chessMove.toFile );
			spritePtr->POINT_Destination = coords( chessMove.fromRank, chessMove.fromFile );
			spritePtr->PIXMAP_Sprite = getChessman( spritePtr->POSITION_Origin );
		}
		spritePtr->POINT_Current = spritePtr->POINT_Origin;
		spritePtr->PIXMAP_FlipFrame.resize( spritePtr->PIXMAP_Sprite.size() );
		sprites.append( spritePtr );
	}
	else
	{
		/* Draw this position only if we're not animating */
		drawPosition( toPtr );
		if( takenPtr != Null )
		{
			drawPosition( takenPtr );
		}
	}
	/* Draw the originating position */
	drawPosition( fromPtr );

	if( TQString( chessMove.SAN ).contains( "o-o", false ) )
	{
		/* This is a castle */
		ChessMove newMove;
		strcpy( newMove.SAN, TQString( "no" ).latin1() );
		newMove.fromRank = chessMove.fromRank;
		newMove.toRank = chessMove.toRank;
		newMove.ManTaken = Null;
		if( TQString( chessMove.SAN ).contains( "o-o-o", false ) )
		{
			/* Queenside */
			newMove.fromFile = 0;
			newMove.toFile = 3;
		}
		else
		{
			/* Kingside */
			newMove.fromFile = 7;
			newMove.toFile = 5;
		}
		drawMove( newMove, reverse );
	}
	lastMoveWasDrag = false;
}
///////////////////////////////////////
//
//  board_2d::resizeBoard( TQ_SLOT )
//
///////////////////////////////////////
void board_2d::resizeBoard( void )
{
	int size = 8 + ( 1 * ( myResource->ThemeBorder == true ) );
	init = false;

	/* Resize myself */
	myself.resize( myResource->ThemeSize * size, myResource->ThemeSize * size);
	myself.fill();

	/* Finish up */
	setFixedSize( myResource->ThemeSize * size, myResource->ThemeSize * size);
	redrawAll();

	int inverseSize = IMAGE_MAX - myResource->ThemeSize;
	MAX_STEPS = ( ( inverseSize * inverseSize ) / IMAGE_MAX ) + 7;
}
///////////////////////////////////////
//
//	board_2d::redrawAll
//
///////////////////////////////////////
void board_2d::redrawAll( void )
{
	char tmp(0);

	if( init )
		return;

	/* Set Orientation */
	orientation = myResource->OPTION_Board_Orientation;
	if( localArmy != WHITE )
		orientation = !orientation;
	if( flip )
		orientation = !orientation;

	/* Set Border */
	if( myResource->ThemeBorder )
	{
		if( orientation )
		{
			TQWMatrix matrix;
			matrix.rotate( 180.0 );
			myself = cache->Border.xForm( matrix );
		}
		else
			myself = cache->Border;
	}

	/* Redraw All Positions */
	while( tmp < 64 )
		drawPosition( tmp++ );
	redrawLights();

	/* Make sure everything is repainted */
	updateX1 = updateY1 = 0;
	updateX2 = updateY2 = IMAGE_MAX * 9;

	commit();
}
///////////////////////////////////////
//
//	board_2d::redrawLights
//
///////////////////////////////////////
void board_2d::redrawLights( void )
{
	int half, four;
	TQPoint black, white;
	if( myResource->ThemeBorder )
	{
		half = myResource->ThemeSize >> 1;
		four = myResource->ThemeSize << 2;
		if( !orientation )
		{
			black.setX( 0 );
			black.setY( four );
			white.setX( 0 );
			white.setY( four + half );
		}
		else
		{
			black.setX( myself.width() - half );
			black.setY( four + half );
			white.setX( myself.width() - half );
			white.setY( four );
		}
		if( myLogic->OnMove != WHITE )
		{
			myBlit( black, &cache->BorderLightOn, cache->BorderLightOn.rect() );
			myBlit( white, &cache->BorderLightOff, cache->BorderLightOff.rect() );
		}
		else
		{
			myBlit( black, &cache->BorderLightOff, cache->BorderLightOff.rect() );
			myBlit( white, &cache->BorderLightOn, cache->BorderLightOn.rect() );
		}
	}
}
///////////////////////////////////////
//
//	board_2d::drawPosition
//
///////////////////////////////////////
void board_2d::drawPosition( const int &pos )
{
	int rank = pos >> 3;
	int file = pos % 8;
	TQPixmap buffer;
	TQString cacheName = TQString::number( myResource->ThemeSize );
	TQImage tempImage;

	if( ( pos < 0 ) || ( pos > 63 ) ) return;
	/*
			Build the cache ref name
	*/
	if( color( rank, file ) )
		cacheName += "L";
	else
		cacheName += "D";
	if( !paused )
	{
		switch( myLogic->current[pos].Note )
		{
			case NOTE_SELECT:
			case NOTE_HIGHLIGHT:
				cacheName += "S";
				break;
			case NOTE_MOVE:
			case NOTE_CASTLE:
			case NOTE_PAWN_DOUBLE:
				cacheName += "M";
				break;
			case NOTE_ATTACK:
			case NOTE_ENPASSANT:
				cacheName += "A";
				break;
			default:
				cacheName += " ";
		}
		if( ( myLogic->current[pos].ManPtr != Null ) && ( !isSprite( pos ) ) )
		{
			if( myLogic->chessman[ myLogic->current[pos].ManPtr ].Army == WHITE )
				cacheName += "W";
			else
				cacheName += "B";
			switch( myLogic->chessman[myLogic->current[pos].ManPtr].Type )
			{
				case King:
							cacheName += "K";
					break;
				case Queen:
							cacheName += "Q";
					break;
				case Bishop:
							cacheName += "B";
					break;
				case Knight:
							cacheName += "N";
					break;
				case Rook:
							cacheName += "R";
					break;
				case Pawn:
							cacheName += "P";
					break;
				default:
					break;
			}
			if( ( pos == premoveFrom ) || ( pos == premoveTo ) )
				cacheName += "t"; // The means it's transparent
		}
	}
	else
		cacheName += " ";
	if( cache->find( cacheName, buffer ) )
	{
		/*
				Cache Hit... no need to redraw
		*/
		if( myResource->OPTION_Show_Coord )
			drawCoords( &buffer, pos );
		myBlit( coords( rank, file ), &buffer, buffer.rect() );
		return;
	}

	/*
			Cache miss
			Draw the pixmap
	*/
	if( color( rank, file ) )
		buffer = cache->SquareLight;
	else
		buffer = cache->SquareDark;

	switch( myLogic->current[pos].Note )
	{
		case NOTE_HIGHLIGHT:	// Fall Through
		case NOTE_SELECT:
			bitBlt( &buffer, 0, 0, &cache->HighlightSelect, 0, 0, -1, -1, TQt::CopyROP, false);
			break;
		case NOTE_MOVE:				// Fall Through
		case NOTE_CASTLE:			// Fall Through
		case NOTE_PAWN_DOUBLE:
			bitBlt( &buffer, 0, 0, &cache->HighlightMove, 0, 0, -1, -1, TQt::CopyROP, false);
			break;
		case NOTE_ATTACK:			// Fall Through
		case NOTE_ENPASSANT:
			bitBlt( &buffer, 0, 0, &cache->HighlightAttack, 0, 0, -1, -1, TQt::CopyROP, false);
			break;
		default:
		break;
	}
	if( !isSprite(pos) )
	{
		TQPixmap chessman = getChessman( pos );
		bitBlt( &buffer, 0, 0, &chessman, 0, 0, -1, -1, TQt::CopyROP, false);
	}
	/* Now add this pixmap to the cache */
	cache->add( cacheName, buffer );
	/*  */
	if( myResource->OPTION_Show_Coord )
		drawCoords( &buffer, pos );
	myBlit( coords( rank, file ), &buffer, buffer.rect() );
}
///////////////////////////////////////
//
//	board_2d::drawCoords
//
///////////////////////////////////////
void board_2d::drawCoords( TQPixmap *pic, const int &pos )
{
	TQPainter painter;
	TQString letter;
	int themeSize = myResource->ThemeSize - 4;
	int targetRank, targetFile;

	if( orientation == 0 )
	{
		targetRank = 0;
		targetFile = 0;
	}
	else
	{
		targetRank = 7;
		targetFile = 7;
	}
	/* Draw Rank */
	if( ( pos >> 3 ) == targetRank )
	{
		letter = TQString( TQString("abcdefgh").at( pos % 8 ) );
		painter.begin( pic );
		painter.setFont( myResource->FONT_Standard );
		painter.setPen( myResource->COLOR_Notation_Shadow );
		painter.drawText( 3, 3, themeSize, themeSize,
												TQt::AlignRight | TQt::AlignBottom, letter );
		painter.setPen( myResource->COLOR_Notation );
		painter.drawText( 2, 2, themeSize, themeSize,
												TQt::AlignRight | TQt::AlignBottom, letter );
		painter.end();
	}
	/* Draw File */
	if( ( pos % 8 ) == targetFile )
	{
		letter = TQString( TQString("12345678").at( pos >> 3 ) );
		painter.begin( pic );
		painter.setFont( myResource->FONT_Standard );
		painter.setPen( myResource->COLOR_Black );
		painter.drawText( 3, 3, themeSize, themeSize,
												TQt::AlignLeft | TQt::AlignTop, letter );
		painter.setPen( myResource->COLOR_White );
		painter.drawText( 2, 2, themeSize, themeSize,
												TQt::AlignLeft | TQt::AlignTop, letter );
		painter.end();
	}
}
///////////////////////////////////////
//
//	board_2d::mouseReleaseEvent
//
///////////////////////////////////////
void board_2d::mouseReleaseEvent( TQMouseEvent *event )
{
	event->accept();

	if( DragSprite != NULL )
	{
		/* Destroy any sprites being dragged */
		int tmp = DragSprite->POSITION_Origin;
		myBlit( DragSprite->POINT_LastUpdate,
						&DragSprite->PIXMAP_FlipFrame,
						DragSprite->PIXMAP_FlipFrame.rect() );
		sprites.removeRef( DragSprite );
		DragSprite = NULL;
		drawPosition( tmp );
		commit();
		lastMoveWasDrag = true;
	}

	if(event->button() == TQt::LeftButton)
	{
		emit leftClick( position( event->pos() ) );
	}
	if(event->button() == TQt::RightButton)
	{
		emit rightClick( position( event->pos() ) );
	}
}
///////////////////////////////////////
//
//	board_2d::mousePressEvent
//
///////////////////////////////////////
void board_2d::mousePressEvent( TQMouseEvent *event )
{
	pressPoint = event->pos();
	event->accept();
}
///////////////////////////////////////
//
//	board_2d::mouseMoveEvent
//
///////////////////////////////////////
void board_2d::mouseMoveEvent( TQMouseEvent *event )
{
	event->accept();
	if( DragSprite == NULL )
	{
		if( event->state() & TQt::LeftButton )
		{
			if( abs( pressPoint.x() - event->pos().x() ) + abs( pressPoint.y() - event->pos().y() ) > 6 )
			{
				/* Begin Dragging a piece */
				DragSprite = new sprite;
				sprites.append( DragSprite );
				DragSprite->POINT_Origin = pressPoint;
				DragSprite->POSITION_Origin = position( pressPoint );
				emit leftClick( DragSprite->POSITION_Origin ); // Tell match that we just clicked on this piece

				if( myLogic->current[ DragSprite->POSITION_Origin ].Note != NOTE_SELECT )
				{
					/* The selection didn't take.. back out. */
					sprites.removeRef( DragSprite );
					DragSprite = NULL;
					drawPosition( position( pressPoint ) );
					commit();
					return;
				}

				/* Get the piece image and store it in dragPix */
				DragSprite->PIXMAP_Sprite = getChessman( DragSprite->POSITION_Origin );
				DragSprite->PIXMAP_FlipFrame.resize( DragSprite->PIXMAP_Sprite.size() );
				DragSprite->Restore = false;
			}
			else
				/* Not enough dragging */
				return;
		} // End ( event->state() & TQt::LeftButton )
		else
			return; /* No dragging. Most events should end up here */
	}

	int halfSize = myResource->ThemeSize >> 1;
	DragSprite->POINT_Current.setX( event->x() - halfSize );
	DragSprite->POINT_Current.setY( event->y() - halfSize );
	commit();
}
///////////////////////////////////////
//
//	board_2d::getChessman
//
///////////////////////////////////////
TQPixmap board_2d::getChessman( const int &pos )
{
	TQPixmap tmp;
	char type, army;

	if( pos == premoveTo )
	{
		type = myLogic->chessman[myLogic->current[premoveFrom].ManPtr].Type;
		army = myLogic->chessman[myLogic->current[premoveFrom].ManPtr].Army;
	}
	else
	{
		type = myLogic->chessman[myLogic->current[pos].ManPtr].Type;
		army = myLogic->chessman[myLogic->current[pos].ManPtr].Army;
	}
	switch( type )
	{
		case King:
			if( army == WHITE )
				tmp = cache->WhiteKing;
			else
				tmp = cache->BlackKing;
			break;
		case Queen:
			if( army == WHITE )
				tmp = cache->WhiteQueen;
			else
				tmp = cache->BlackQueen;
			break;
		case Bishop:
			if( army == WHITE )
				tmp = cache->WhiteBishop;
			else
				tmp = cache->BlackBishop;
			break;
		case Knight:
			if( army == WHITE )
				tmp = cache->WhiteKnight;
			else
				tmp = cache->BlackKnight;
			break;
		case Rook:
			if( army == WHITE )
				tmp = cache->WhiteRook;
			else
				tmp = cache->BlackRook;
			break;
		case Pawn:
			if( army == WHITE )
				tmp = cache->WhitePawn;
			else
				tmp = cache->BlackPawn;
			break;
		default:
			break;
	}
	if( ( pos == premoveFrom ) || ( pos == premoveTo ) )
	{
		TDEIconEffect::semiTransparent( tmp );
	}
	return tmp;
}
///////////////////////////////////////
//
//	board_2d::setPremovePositions
//
///////////////////////////////////////
void board_2d::setPremovePositions( const int &posF, const int &posT )
{
	premoveFrom = posF;
	premoveTo = posT;
	if( ( posF != Null ) && ( posT != Null ) )
	{
		myLogic->current[posF].Note = NOTE_NONE;
		myLogic->current[posT].Note = NOTE_NONE;
		drawPosition( posF );
		drawPosition( posT );
		commit();
	}
}
///////////////////////////////////////
//
//	board_2d::commit
//
///////////////////////////////////////
void board_2d::commit( void )
{
	drawSprites();
	bitBlt( this, updateX1, updateY1, &myself, updateX1, updateY1, updateX2, updateY2, TQt::CopyROP );
	updateX1 = updateY1 = 4000;
	updateX2 = updateY2 = -4000;
}
///////////////////////////////////////
//
//	board_2d::paintEvent
//
///////////////////////////////////////
void board_2d::paintEvent( TQPaintEvent *event )
{
	/* Paint the Widget */
	bitBlt( this, event->rect().topLeft(), &myself, event->rect(), TQt::CopyROP );
}
///////////////////////////////////////
//
//	board_2d::drawSprites
//
///////////////////////////////////////
void board_2d::drawSprites( void )
{
	int index;
	sprite *spritePtr;

	/* Remove all sprites from the pixmap */
	for( index = (signed int)sprites.count() - 1; index > -1; index-- )
	{
		spritePtr = sprites.at(index);
		if( spritePtr->Restore == false )
		{
			spritePtr->Restore = true;
		}
		else
		{
			myBlit( spritePtr->POINT_LastUpdate,
							&spritePtr->PIXMAP_FlipFrame,
							spritePtr->PIXMAP_FlipFrame.rect() );
		}
	}
	/* Redraw all sprites */
	for( index = 0; index < (signed int)sprites.count(); index++ )
	{
		spritePtr = sprites.at(index);

		if( ( spritePtr == DragSprite ) || ( spritePtr->Steps < MAX_STEPS ) )
		{
			/* Redraw Sprite */
			bitBlt( &spritePtr->PIXMAP_FlipFrame,
							0,
							0,
							&myself,
							spritePtr->POINT_Current.x(),
							spritePtr->POINT_Current.y(),
							spritePtr->PIXMAP_FlipFrame.width(),
							spritePtr->PIXMAP_FlipFrame.height(),
							TQt::CopyROP );
			myBlit( spritePtr->POINT_Current,
							&spritePtr->PIXMAP_Sprite,
							spritePtr->PIXMAP_Sprite.rect() );
			spritePtr->POINT_LastUpdate = spritePtr->POINT_Current;
		}
		else
		{
			/* Animation finished */
			int origin = spritePtr->POSITION_Origin;
			int destination = spritePtr->POSITION_Destination;
			int target = spritePtr->POSITION_TargetTaken;
			sprites.removeRef( spritePtr );
			drawPosition( origin );
			drawPosition( destination );
			drawPosition( target );
			index = -1;
		}
	}
}
///////////////////////////////////////
//
//	board_2d::updateSprites
//
///////////////////////////////////////
void board_2d::updateSprites( void )
{
	if( myResource->OPTION_Animate_Moves )
	{
		for( int index = 0; index < (signed int)sprites.count(); index++ )
		{
			sprite *spritePtr = sprites.at( index );
			if( spritePtr == DragSprite )
				continue;

			TQTimer::singleShot( 0, this, TQ_SLOT( updateSprites() ) );
			if( spritePtr->Steps < MAX_STEPS )
			{
				double factor = ( 1.0 / (double)MAX_STEPS ) * (double)spritePtr->Steps;
				int newX = (int)( ( spritePtr->POINT_Destination.x() - spritePtr->POINT_Origin.x() ) * factor );
				int newY = (int)( ( spritePtr->POINT_Destination.y() - spritePtr->POINT_Origin.y() ) * factor );
				spritePtr->POINT_Current = spritePtr->POINT_Origin + TQPoint( newX, newY );
				spritePtr->Steps += sprites.count();
			}
		}
		commit();
	}
}
///////////////////////////////////////
//
//	board_2d::isSprite
//
///////////////////////////////////////
bool board_2d::isSprite( const int &pos )
{
	for( unsigned int index = 0; index < sprites.count(); index++ )
	{
		sprite *tmpSprite = sprites.at( index );
		if( tmpSprite->POSITION_Origin == pos )
		{
			return true;
		}
	}
	return false;
}
///////////////////////////////////////
//
//	board_2d::myBlit
//
///////////////////////////////////////
void board_2d::myBlit( const TQPoint &dp, const TQPaintDevice *src, const TQRect &sr )
{
	bitBlt( &myself, dp.x(), dp.y(), src, sr.x(), sr.y(), sr.width(), sr.height(), TQt::CopyROP );
	if( dp.x() < updateX1 )
		updateX1 = dp.x();
	if( dp.y() < updateY1 )
		updateY1 = dp.y();
	if( dp.x() + sr.width() > updateX2 )
		updateX2 = dp.x() + sr.width();
	if( dp.y() + sr.height() > updateX2 )
		updateX2 = dp.y() + sr.height();
}
