#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
#include <time.h>
#include <sys/select.h>
#include <sys/time.h>
#include <unistd.h>
#include <assert.h>
#include "termio.h"

#define WID 10
#define HEI 22


int delay_msec=700;


const int stones[7]={
	0b0000000001110001,
	0b0000000001110100,
	0b0000000000110011,
	0b0000000000110110,
	0b0000000001110010,
	0b0000000001100011,
	0b0000000000001111,
};

static int rotateR(int bl){
	int dest=0;
	for(int y=0;y<4;y++){
		for(int x=0;x<4;x++){
			dest|=(!!(bl&(1<<(4*y+x))))<<(4*x+3-y);
		}
	}
	return dest;
}

static int rotateL(int bl){
	return rotateR(rotateR(rotateR(bl)));
}


struct board {
	int lines[HEI];
	int stone,stx,sty;
};

static void persist(struct board *bd){
	// fprintf(stderr,"persist: stone=%d\n",bd->stone);
	// usleep(1000000);
	for(int y=0;y<4;y++){
		for(int x=0;x<4;x++){
			bd->lines[bd->sty+y]|=(!!(bd->stone&(1<<(4*y+x))))<<(bd->stx+x);
		}
	}
}

static void spawn(struct board *bd){
	int idx=rand()%7;
	// fprintf(stderr,"%d\n",idx);
	// usleep(1000000);
	bd->stone=stones[idx];
	bd->stx=WID/2-1;
	bd->sty=0;
}

static void show(const struct board *bd){
	struct board bd2;
	memcpy(&bd2,bd,sizeof bd2);
	persist(&bd2);
	printf("\x1B[H\x1B[2J");
	for(int y=0;y<HEI;y++){
		putchar('|');
		for(int x=0;x<WID;x++){
			if(bd2.lines[y]&(1<<x))printf("█");
			else putchar(' ');
		}
		putchar('|');
		putchar('\n');
	}
	putchar('+');
	for(int x=0;x<WID;x++){
		putchar('-');
	}
	putchar('+');
	putchar('\n');
	fflush(stdout);
}

static bool boardvalid(const struct board *bd){
	for(int y=0;y<4;y++){
		for(int x=0;x<4;x++){
			if((bd->stone&(1<<(4*y+x)))){
				if(bd->sty+y>=HEI)return false;
				if(bd->stx+x<0||bd->stx+x>=WID)return false;
			}
			if(bd->lines[bd->sty+y]&((!!(bd->stone&(1<<(4*y+x))))<<(bd->stx+x)))return false;
		}
	}
	return true;
}

static void advance(struct board *bd){
	fprintf(stderr,"advance: stone=%d\n",bd->stone);
	if(bd->stone==0){
		spawn(bd);
		return;
	}
	bd->sty++;
	if(!boardvalid(bd)){
		bd->sty--;
		persist(bd);
		spawn(bd);
		return;
	}
}

static void move(struct board *bd,int dir){
	bd->stx+=dir;
	if(!boardvalid(bd)){
		bd->stx-=dir;
		return;
	}
}

static void rotR(struct board *bd){
	int st=bd->stone;
	bd->stone=rotateR(bd->stone);
	if(!boardvalid(bd))bd->stone=st;
}


int main(void){
	srand(time(NULL));

	initkeyboard(false);
	atexit(endkeyboard);

	struct board bd;
	memset(&bd,0,sizeof bd);
	show(&bd);

	struct timeval timeleft;
	timeleft.tv_sec=0;
	timeleft.tv_usec=0;

	while(true){
		show(&bd);

		struct timeval start;
		gettimeofday(&start,NULL);

		fd_set inset;
		FD_ZERO(&inset);
		FD_SET(0,&inset);
		struct timeval timeout;
		memcpy(&timeout,&timeleft,sizeof timeout);

		int ret=select(1,&inset,NULL,NULL,&timeout);
		assert(ret>=0);

		struct timeval end;
		gettimeofday(&end,NULL);

		if(ret==0){
			advance(&bd);
			timeleft.tv_sec=delay_msec/1000;
			timeleft.tv_usec=delay_msec%1000*1000;
			continue;
		}
		end.tv_sec-=start.tv_sec;
		end.tv_usec-=start.tv_usec;
		if(end.tv_usec<0){
			end.tv_sec--;
			end.tv_usec+=1000000;
		}

		timeleft.tv_sec-=end.tv_sec;
		timeleft.tv_usec-=end.tv_usec;
		if(timeleft.tv_usec<0){
			timeleft.tv_sec--;
			if(timeleft.tv_sec<0){
				timeleft.tv_sec=timeleft.tv_usec=0;
			} else {
				timeleft.tv_usec+=1000000;
			}
		}

		assert(FD_ISSET(0,&inset));

		int key=tgetkey();
		if(key=='Q'||key=='q')break;
		else if(key=='S'||key=='s'||key==KEY_DOWN){
			advance(&bd);
			timeleft.tv_sec=delay_msec/1000;
			timeleft.tv_usec=delay_msec%1000*1000;
		} else if(key=='A'||key=='a'||key==KEY_LEFT)move(&bd,-1);
		else if(key=='D'||key=='d'||key==KEY_RIGHT)move(&bd,1);
		else if(key=='W'||key=='w'||key==KEY_UP)rotR(&bd);
		else bel();
	}
}