// AUTHOR 130050037
#include <FL/Fl.H>
#include <FL/Fl_Window.H>
#include <FL/Fl_Box.H>
#include <FL/fl_draw.H>
#include <iostream>
#include <cstdlib>
#include <sstream>
using namespace std;

static const int tilesize = 30;
static const int xmaxtiles= 14;
static const int ymaxtiles= 20;
static const int bgcolor=56;
static const int brick_color[6]={80,69,93,73,97,230};

class Tile{   //class tile defining each tile in the tetris board
	public:
	int x,y;
	void set(int a,int b){x=a*tilesize;y=b*tilesize;}  //set function for class tile
};

void ts_down(Tile part[4]){
	for(int i=0;i<4;i++){part[i].y=part[i].y+tilesize;}
}

void ts_left(Tile part[4]){
	for(int i=0;i<4;i++){part[i].x=part[i].x-tilesize;}
}

void ts_right(Tile part[4]){
	for(int i=0;i<4;i++){part[i].x=part[i].x+tilesize;}
}

void ts_rotate(Tile part[4],int centre,int dir){
	int xcc=part[centre].x;
	int ycc=part[centre].y;
	for(int i=0;i<4;i++){
	int xc=part[i].x;
	int yc=part[i].y;
	part[i].x=xcc+dir*(yc-ycc);
	part[i].y=ycc-dir*(xc-xcc);
	}
}

void copy(Tile org[],Tile cop[]){
	for(int i=0;i<4;i++){
		cop[i]=org[i];
	}
}

void ts_up(Tile part[4]){
	for(int i=0;i<4;i++){part[i].y=part[i].y-tilesize;}
}

class Board;

class apiece{
	friend class Board;
	Tile ts[4];
	bool rota;
	int midpointTile;
	public:
	Board*b;
	int color;
	void set(Board * a);
	apiece(int co);
	bool left();
	bool right();
	bool down();
	bool up();
	bool rotate(int dir);
	bool cdown();
};


apiece::apiece(int co){  //constructor for apiece
		color=co;
		int c=rand()%7;
		switch(c){
		case 0:            //square
		{ts[0].set(xmaxtiles/2,0);
		ts[1].set((xmaxtiles/2+1),0);
		ts[2].set(xmaxtiles/2,1);
		ts[3].set((xmaxtiles/2+1),1);
		rota=false;
		break;
		}
		case 1:            //T shape
			{
		ts[0].set(xmaxtiles/2+1,1);
		ts[1].set((xmaxtiles/2),1);
		ts[2].set(xmaxtiles/2,2);
		ts[3].set((xmaxtiles/2-1),1);
		rota=true;
		break;
		}
		case 2:           //inverted L
		{
		ts[0].set((xmaxtiles/2+1),1);
		ts[1].set((xmaxtiles/2+1),0);
		ts[2].set(xmaxtiles/2,0);
		ts[3].set((xmaxtiles/2-1),0);
		rota=true;
		break;
		}
		case 3:          // L shape
		{
		ts[0].set((xmaxtiles/2),1);
		ts[1].set((xmaxtiles/2),0);
		ts[2].set((xmaxtiles/2+1),0);
		ts[3].set((xmaxtiles/2+2),0);
		rota=true;
		break;
		}
		case 4:           //        horizontal
		{
		ts[0].set((xmaxtiles/2-1),0);
		ts[1].set((xmaxtiles/2),0);
		ts[2].set((xmaxtiles/2+1),0);
		ts[3].set((xmaxtiles/2+2),0);
		rota=true;
		break;
		}
		case 5:           //     z shape
		{
		ts[0].set((xmaxtiles/2-1),0);
		ts[1].set(xmaxtiles/2,0);
		ts[2].set(xmaxtiles/2,1);
		ts[3].set((xmaxtiles/2+1),1);
		rota=true;
		break;
		}
		case 6:           // n shape
		{
		ts[0].set((xmaxtiles/2+1),0);
		ts[1].set(xmaxtiles/2,0);
		ts[2].set(xmaxtiles/2,1);
		ts[3].set((xmaxtiles/2-1),1);
		rota=true;
		break;
		}
	
	}
	}

class Board : public Fl_Widget {
	Fl_Box*scorebox;
	int score;
	char* scoreLabel;
public:
	void setScoreBox(Fl_Box** sb);
	apiece*pie;
	void init();
	int occup[xmaxtiles][ymaxtiles];
	int periodic();
	Board(apiece*p);
	void draw();
	int handle (int e);
	bool check();
	bool inboard();
	bool terminate();
};

bool Board::check(){
	Tile cop[4];
	copy(pie->ts,cop);
	for(int i=0;i<4;i++){
	if(occup[cop[i].x/tilesize][cop[i].y/tilesize]!=bgcolor)
		return false;
	}
	return true;
}

bool Board::inboard(){
	Tile cop[4];
	copy(pie->ts,cop);
	for(int i=0;i<4;i++){
	if(cop[i].x/tilesize<0||cop[i].x/tilesize>=14||cop[i].y/tilesize<0||cop[i].y/tilesize>=20)
		return false;
	}
	return true;
}

void Board::setScoreBox(Fl_Box** sb){
	scorebox = *sb ;
	score=0;
	scoreLabel = (char*) malloc(sizeof(char)*10);
}

void timeractions(void *p) {
	((Board *)p)->periodic ();
}

int Board::periodic() {

	Fl::repeat_timeout (1,timeractions,this);
	pie->down();
	redraw();
}

int Board::handle(int e) {
	if (e==8) 
		switch (Fl::event_key()) {
		case 65361 :  {pie->left();break;}
		case 65362 :  {pie->rotate(-1); break;}
		case 65363 :  {pie->right();break;}
		case 65364 :  {pie->rotate(1);break;}
		case 32    :  {for(int i=0;i<20;i++){
							if(occup[pie->ts[0].x/tilesize][pie->ts[0].y/tilesize]==bgcolor&&occup[pie->ts[1].x/tilesize][pie->ts[1].y/tilesize]==bgcolor&&occup[pie->ts[2].x/tilesize][pie->ts[2].y/tilesize]==bgcolor&&occup[pie->ts[3].x/tilesize][pie->ts[3].y/tilesize]==bgcolor)
								{
									pie->down();
								}
							}
							break;
						}
		default : return 55;

	}
	else return 55;
	stringstream strs;
	strs << score;
	string temp_str = strs.str();
	if(!terminate())
		strcpy(scoreLabel,"Score: ");
	else
		strcpy(scoreLabel,"GAME OVER\nScore: ");
	strcat(scoreLabel,(char*) temp_str.c_str());
	scorebox->label(scoreLabel);
	redraw();
}

Board::Board(apiece*p) : Fl_Widget (0,0,xmaxtiles*tilesize,ymaxtiles*tilesize,"TETRIS") {
	pie=p;
	score=0;
	for(int x=0;x<xmaxtiles;x++){
		for(int y=0;y<ymaxtiles;y++){
			occup[x][y]=bgcolor;
			}
	}
}

void Board::draw () {
	int col[14][20];
	for(int y=0;y<20;y++){
		for(int x=0;x<14;x++){
		col[x][y]=occup[x][y];
		if(((x)*tilesize==pie->ts[0].x&&(y)*tilesize==pie->ts[0].y)||((x)*tilesize==pie->ts[1].x&&(y)*tilesize==pie->ts[1].y)||((x)*tilesize==pie->ts[2].x&&(y)*tilesize==pie->ts[2].y)||((x)*tilesize==pie->ts[3].x&&(y)*tilesize==pie->ts[3].y)) {col[x][y]=pie->color;}
		}
	}
	bool test,t1,t2;
	t1=(pie->ts[0].y==19*tilesize||pie->ts[1].y==19*tilesize||pie->ts[2].y==19*tilesize||pie->ts[3].y==19*tilesize);
	t2=pie->cdown();
	test=t1||!t2;
	if(test&&!terminate()){
		for(int x=0;x<14;x++){
			for(int y=0;y<20;y++){
				occup[x][y]=col[x][y];
			}
		}
	}
	if(test&&!terminate()){
		pie=new apiece(brick_color[rand()%6]);
		pie->set(this);
	}
	for(int y=0;y<20;y++){
		for(int x=0;x<14;x++){
			if(col[x][y]!=bgcolor)
			fl_draw_box(FL_DOWN_BOX,(x)*tilesize,(y)*tilesize,tilesize,tilesize,col[x][y]);
			else
			fl_draw_box(FL_UP_BOX,(x)*tilesize,(y)*tilesize,tilesize,tilesize,col[x][y]);
			}
	}
	for(int y=0;y<20;y++){
		bool rem=true;
		for(int x=0;x<14;x++){
			bool chec;
			if(occup[x][y]!=bgcolor) chec=true;
			else chec=false;
			rem=rem&&chec;
		}
		if(rem){
			score=score+50;
			for(int xx=0;xx<14;xx++){
				for(int yy=y;yy>0;yy--){
					occup[xx][yy]=occup[xx][yy-1];
				}
			}
			scorebox->redraw();
		}
	}
}

void apiece:: set(Board * a){b=a;}  // set funtion connecting apiece and board

bool apiece::left(){
		ts_left(ts);
		if(b->check()&&b->inboard())
			{return true;}
		else {
			ts_right(ts);
			return false;
	}
}

bool apiece::rotate(int dir){
	ts_rotate(ts,1,dir);
	if(b->check()&&b->inboard()&&rota)
			return true;
	else {
		ts_rotate(ts,1,-1*dir);
		return false;
	}
}

bool apiece::up(){
		ts_up(ts);
		if(b->check()&&b->inboard())
			{return true;}
		else {
			ts_down(ts);
			return false;
	}
}

bool apiece::down(){
		ts_down(ts);
		if(b->check()&&b->inboard())
			{return true;}
		else {
			ts_up(ts);
			return false;
	}
}

bool apiece::cdown(){
		ts_down(ts);
		if(b->check()&&b->inboard())
			{ts_up(ts);
			return true;}
		else {
			ts_up(ts);
			return false;
	}
}

bool apiece::right(){
		ts_right(ts);
		if(b->check()&&b->inboard())
			{return true;}
		else {
			ts_left(ts);
			return false;
	}
}

bool Board ::terminate(){
	for(int i=0;i<14;i++){
		if(occup[i][1]!=bgcolor)
		return true;
	}
	return false;
}

int main(int argc, char *argv[]) {
	Fl_Window *window = new Fl_Window (700,600,"TETRIS");
	window->color(42);
	apiece *p = new apiece(brick_color[rand()%6]);
	Board *b = new Board(p);
	p->set(b);
	Fl_Box *scorebox = new Fl_Box(tilesize*xmaxtiles+10,50,250,250,"Score: 0\0");
	scorebox->box(FL_UP_BOX);
	scorebox->labelfont(FL_BOLD+FL_ITALIC);
	scorebox->labelsize(36);
	scorebox->labeltype(FL_SHADOW_LABEL);
	b->setScoreBox(&scorebox);
	window->end();  
	window->show();
	Fl::add_timeout(1, timeractions,b);
	return(Fl::run());
}

