/*	FLTetris v1.0 , open source Yippee! */
/*	Public domain / Johan Alm mars 2007. */
//
/*	Include , define */
//
// We are using the FLTK, for grapics.
#include <FL/Fl.H>
#include <FL/Fl_Double_Window.H>
#include <FL/Fl_Box.H>
// C++ library
#include <iostream> //(std::cout)
#include <ctime> //(std::random)
#include <string>
#include <stdlib.h>
// For this program.
#define XSTART 0
#define YSTART 0
#define SIZE 19 // Box-size
#define COL10 12 // colum in playField.
#define ROWS15 12 // rows in playField.
#define BREAK_NR 3 // When you get the ":)"
#define YDOWN ROWS15
#define PF150 200
//
/*	Deklaration of objekt - the interface. (.h-file..)*/
//
class Window : public Fl_Double_Window {

	public:
		Window( int w , int h , const char *l=0 ) ;
		void keyBase( int );
		void gameOver( void );
	
	private:
		int handle( int );
		void keyRight( void );
		void keyLeft( void );
		void keyDown( void );
		void keyUp( void );
		
		int game_paus , game_over , quick_down;
		bool down1 , down2 , rotate;
	
};
//
class Brick {
	
	friend class PlayField;
	
	public:
		Brick( int );
		~Brick();
		void tryLeftRight( int , bool & ); //Reply
		void moveLeftRight( int , bool ); //Recive
		void down( bool & );
		void rotate( bool & );
	
	private:
		class Fl_Box* myBox;
		int my_x , my_y , my_type , my_rotate;
	
};
//
class PlayField {
	
	friend class Brick;
	
	public:
		PlayField( int );
		void initPtr( void );
		void find( void );
		void remove( void );
		void down( void );
	
	private:		
		class Brick* myBrick;
		class PlayField *ptr_up , *ptr_down , *ptr_left , *ptr_right;
		
		int my_nr , my_find;
		static int pf_count;		
		
		void find( int );
	
};
//
class ScoreLabel {
	
	public:
		ScoreLabel( void );
		void tick( void );
		void gamePause( void );
		void gameOver( void );
	
	private:
		class Fl_Box* infoBox;
		char char_buf[6];
		std::string str_buf;
		int score_cnt;
	
};
//
/*	Global (the objekt). */
//
class Window* window;
class Brick* brick1;
class Brick* brick2;
class PlayField* playField[ PF150 ];
class ScoreLabel* scoreLabel;
//
/*	Implementation of objekt - the code. */
//
/*	Window; */
//
// Event-handel and base game-logic.
//
Window::Window( int w , int h , const char* l ) : Fl_Double_Window( w , h , l ) {
	
	game_paus = 1;
	down1 = down2 = game_over = rotate = quick_down = 0;
	
};
//
int Window::handle( int e ) {
	
	int ret = Fl_Group::handle( e );
	
	if( e == FL_KEYDOWN )
		keyBase( Fl::event_key() );
	
	return( ret ); // (1=no Esc)
	
}
//
void Window::keyBase( int key ) {
	
	if( !game_paus && !game_over )
	{
		if( key == FL_Right ) keyRight();
		if( key == FL_Left ) keyLeft();
		if( key == FL_Down ) keyDown();
		if( key == FL_Up ) keyUp();
		
		if( key == FL_Enter  || key == ' ' || key == FL_KP_Enter) { quick_down = 1;     while(quick_down) keyDown(); }
		
		if( game_over ) scoreLabel->gameOver();
	}
	if (key == FL_Enter || key == FL_KP_Enter || key == ' ')
          if(!game_over && game_paus == 1 )
            game_paus = 0;
}
//
void Window::keyLeft( void ) {
	
	bool good = 1;
	
	if( !down1 ) brick1->tryLeftRight( -1 , good );
	if( !down2 ) brick2->tryLeftRight( -1 , good );
	if( !down1 ) brick1->moveLeftRight( 1, good );
	if( !down2 ) brick2->moveLeftRight( 1, good );
	
}
//
void Window::keyRight( void ) {
	
	bool good = 1;
	
	if( !down1 ) brick1->tryLeftRight( 1 , good );
	if( !down2 ) brick2->tryLeftRight( 1 , good );
	if( !down1 ) brick1->moveLeftRight( -1, good );
	if( !down2 ) brick2->moveLeftRight( -1, good );
	
}
//
void Window::keyDown( void ) {
	
	scoreLabel->tick();  
	
	if( rotate == 0 )
	{
		if( !down2 ) brick2->down( down2 );
		if( !down1 ) brick1->down( down1 );
	}
	else
	{
		if( !down1 ) brick1->down( down1 );
		if( !down2 ) brick2->down( down2 );
	}
	
	if( down1 || down2 ) quick_down = 0; // Quick down in keyBase.
	
	playField[0]->remove();
	playField[0]->down();
	playField[0]->find(); // find() last, to show ":)"

	if( down1 && down2 )
	{
		brick1 = new Brick( 4 );
		brick2 = new Brick( 5 );
		down1 = down2 = 0;
	}
	
}
//
void Window::keyUp( void ) {
	
	if( !down1 && !down2 ) brick2->rotate( rotate );
	
}
//
void Window::gameOver( void ) {
	game_over = 1;
}
//
/* 	Brick; */
//
// Brick: First as moving Brick, then in PlayField.
//
Brick::Brick( int x ) {
	
	my_x = x;     my_y = 1; my_rotate=-1;
	
	// Fl_Color 1 2 3 4 7 : Red Gren Blue Yelow White.
	my_type = (int) (5.0 * (rand() / (RAND_MAX + 1.0)) );
	if( my_type == 0 ) my_type = 7;
	
	myBox = new Fl_Box( XSTART + (SIZE * my_x) , YSTART + (SIZE * my_y) , SIZE , SIZE , "" );
	window->add(myBox);
	myBox->box( FL_PLASTIC_DOWN_BOX );
	myBox->color( (Fl_Color) my_type );
	Fl::redraw();
	
	// Check for GameOver:
	if( playField[0] != NULL ) // Not at start of program..
	if( playField[ my_x + my_y*COL10 ]->myBrick != NULL )
	window->gameOver();
	
};
//
//Deleted from PlayField
Brick::~Brick( void ) {
	
	window->remove(myBox);
	delete( myBox );
	
}
//
// Both Bricks must try the move.
void Brick::tryLeftRight( int x , bool & ref_good ) {
	
	my_x = my_x + x;
	
	if( playField[ my_x + my_y*COL10 ]->myBrick != NULL || my_x < 0 || my_x == COL10 )
	ref_good = 0;
	
}
//
// Both Bricks OK, then do the move.
void Brick::moveLeftRight( int x , bool good ) {

	if( !good ) my_x = my_x + x;
	
	myBox->position( XSTART + (SIZE * my_x), YSTART + (SIZE * my_y) );
	Fl::redraw();
	
}
//
// 1. Move down.
// 2. If at bottom: place my self at right pos in PlayField.
void Brick::down( bool & down ) {
	
	// This is for playField->down().
	playField[ my_x + my_y*COL10 ]->myBrick = NULL;
	
	my_y++;
	myBox->position( XSTART + (SIZE * my_x), YSTART + (SIZE * my_y) );
	Fl::redraw();
	
	if( my_y > YDOWN || playField[ my_x + my_y*COL10 ]->myBrick != NULL )
	{
		my_y--;
		myBox->position( XSTART + (SIZE * my_x), YSTART + (SIZE * my_y) );
		Fl::redraw();
		
		playField[ my_x + my_y*COL10 ]-> myBrick = this;
		down = 1;
	}
	
}
//
void Brick::rotate( bool & rotate ) {
	
	static int xx[4] = {-1 ,-1 , 1 , 1 };
	static int yy[4] = { 1 ,-1 ,-1 , 1 };
	static int x , y , r;
	
	x = my_x;     y = my_y;     r = my_rotate; // Backup.
	
	my_rotate++; if(my_rotate == 4) my_rotate= 0;  
	my_x = my_x + xx[my_rotate];
	my_y = my_y + yy[my_rotate];
	
	if( playField[ my_x + my_y*COL10 ]->myBrick != NULL || my_y > YDOWN || my_x < 0 || my_x == COL10 )
	{ my_x=x;     my_y=y;     my_rotate = r; } // Undo.
	
	myBox->position( XSTART + (SIZE * my_x), YSTART + (SIZE * my_y) );
	Fl::redraw();
	
	rotate = my_rotate; // Pass value to Window.
	
}
//
/*	PlayField; */
//
// The PlayField is a vektor of COL*ROWS where the Brick kan move
// around, and when it hits bottom it places itself here.
//
int PlayField::pf_count = 0;
//
PlayField::PlayField( int nr ) {
	
	my_nr = nr;     my_find = 0;     myBrick = NULL;
	
};
//
// Init pointers and making sure the are right/fault checked right away, at program start..
void PlayField::initPtr( void ) {
	
	// Point to box above this, upper row points to NULL.
	if( my_nr > COL10-1 ) ptr_up = playField[ my_nr - COL10 ]; else ptr_up = NULL;
	// Point to box under this, last row points to NULL.
	if( my_nr < COL10 * (ROWS15-1) ) ptr_down = playField[ my_nr + COL10 ]; else ptr_down = NULL;
	// Left & Right..
	if( my_nr % COL10 ) ptr_left = playField[ my_nr - 1 ]; else ptr_left = NULL;
	if( (my_nr+COL10+1) % COL10 ) ptr_right = playField[ my_nr + 1 ]; else ptr_right = NULL;
	
}
//
// Scan throu all PlayField's, if there are a Brick let it check around him and send  to
// his naibour to do the same.
// If pf_count > BREAK_NR the are now marked "my_find=1", for easy remove.
void PlayField::find( void ) {
	
	for(int i=0; i<PF150; i++)
	{//MainForLoop
		
		playField[ i ]->find( 0 );
		
		if( playField[ 0 ]->pf_count > BREAK_NR )
		{
			for(int i=0; i<PF150; i++)
			{
				if( playField[ i ]->my_find == 1 )
				  playField[ i ]->myBrick->myBox->label(":)");
				Fl::redraw();
			}
			break;
		}
		
		if( playField[ 0 ]->pf_count > 0 ) // Reset to search again!
		{
			playField[ 0 ]->pf_count = 0;
			for(int i=0; i<PF150; i++) playField[ i ]->my_find = 0;
		}
		
	}//MainForLoop
}
//
//"Find-mate".
void PlayField::find( int t ) {
	
	if( myBrick != NULL && t == 0 )	t = myBrick->my_type; // Start.
	
	// Yes = you have found a box of same type.
	if( myBrick != NULL && my_find == 0 && myBrick->my_type == t )
	{
		my_find = 1;
		pf_count++;
		//std::cout << pf_count << std::endl;
		if( ptr_left != NULL ) ptr_left->find( myBrick->my_type );
		if( ptr_up != NULL ) ptr_up->find( myBrick->my_type );
		if( ptr_right != NULL ) ptr_right->find( myBrick->my_type );
		if( ptr_down != NULL ) ptr_down->find( myBrick->my_type );
	}
	
}
//
// If four or more of same type, remowe tho ones whith "my_find=1".
void PlayField::remove( void ) {
	
	if( pf_count > BREAK_NR )
		if( my_nr == 0 ) // "root"
			for(int i=1; i<PF150; i++) // Start with one(1!).
				playField[i]->remove();  
	
	if( my_find == 1 )
	{
		if( myBrick != NULL ) //Overkill?
			delete( myBrick );
		myBrick = NULL; //Overkill?
		my_find = 0;
	}
	
}
//
// 1. Reset pf_count.
// 2. Scan _backwards_ and ask Brick::down() to try move down as long as posible.
void PlayField::down( void ) {
	
	bool down;
	class Brick* ptr_brick;
	pf_count = 0;
	
	for(int i=PF150-1; i>=0; i--)
		if( playField[ i ]->myBrick != NULL )
		{
			down = 0;
			ptr_brick = playField[ i ]->myBrick;
			while( !down ) ptr_brick->down( down );
		}
}
//
/*	ScoreLabel; */
//
ScoreLabel::ScoreLabel( void ) {
	
	score_cnt = 0;
	str_buf = "Score:        ";
	
	infoBox = new Fl_Box(5, 255, 220, 20, "Press space start/pause");
	infoBox->box(FL_PLASTIC_DOWN_BOX);
	infoBox->color((Fl_Color)10);
	Fl::redraw();
	
};
//
void ScoreLabel::tick( void ) {
	
	score_cnt++;
	
	sprintf(char_buf, "%d", score_cnt); // int to char.
	str_buf.insert(7, char_buf, 6); // Insert the score nr.
	infoBox->label(str_buf.data());	
	Fl::redraw();
	
}
//
void ScoreLabel::gamePause( void ) {
	
	infoBox->label( "Press space start/pause" );
	Fl::redraw();
	
}
//
void ScoreLabel::gameOver( void ) {
	
	str_buf = "Game over, score/ticks:        ";
	
	sprintf(char_buf, "%d", score_cnt); // int to char.
	str_buf.insert(24, char_buf, 6); // Insert the score nr.
	infoBox->label(str_buf.data());	
	Fl::redraw();
	
}
//
/*	Funktions */
//
void tick( void * ) {
	
	window->keyBase( FL_Down );
	Fl::repeat_timeout( 0.8, tick );
	
}
//
/* Create objekt, start graphics. */
//
int main ( int argc , char **argv ) {
	
	srand(time(NULL));
	Fl::add_timeout( 0.8, tick );
	
	// Creat applikations window.
	window = new Window ( 230 , 310 , "FLTetris" );
	window->color( FL_LIGHT2 );
	
	
	// Create the rest of the objekts.
	brick1 = new Brick( 4 );
	brick2 = new Brick( 5 );

	for(int i=0; i<PF150; i++) playField[ i ] = new PlayField( i );
	// Init the pointers, _after_ the above.
	for(int i=0; i<PF150; i++) playField[ i ]->initPtr( );
	
	scoreLabel = new ScoreLabel();
	
	window->show();
	return Fl::run(); // Start Fltk nice!
}

