//THIS FILE MUST BE SAVED AS OEM PAGE 437
//Also, please set to use Multibyte strings NOT unicode.
#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <process.h>
const int boardW = 10;
const int boardH = 13;
const int boardX = 3;
const int boardY = 3;
const int startX = 4;
const int startY = 0;
const int backupX = 18;
const int backupY = 14;
typedef char TetBuffer[4][4];


#define screenW 25
#define screenH 21

enum 
{
	REST = 0,
	Note_G2sharp = 104,
	Note_A2 = 110,
	Note_B2 = 123,
	Note_C3 = 131,
	Note_D3 = 147,
	Note_E3 = 165,
	Note_F3 = 175,
	Note_G3 = 196,
	Note_G3sharp = 185,
	Note_A3 = 220,
	Note_A4sharp = 233,
	Note_B3 = 247,
	Note_C4 = 262,
	Note_C4sharp = 277,
	Note_D4 = 294,
	Note_D4sharp = 311,
	Note_E4 = 330,
	Note_F4 = 349,
	Note_F4sharp = 370,
	Note_G4 = 392,
	Note_G4sharp = 415,
	Note_A4 = 440,
	Note_B4 = 494,
	Note_C5 = 523,
	Note_D5 = 587,
	Note_E5 = 659,
	Note_F5 = 698,
	Note_G5 = 784,
	Note_A5 = 880,
};
#define MUSIC_TEMPO 100
enum
   {
      Note_Whole = 16,
      Note_Half = 8,
	  Note_QuarterDot = 6,
      Note_Quarter = 4,
      Note_Eighth = 2,
      Note_Sixteenth = 1,
   };

void __cdecl MusicPlay(int* MuteMusic )
{
	int Music[][2] = {
	//First time
	{Note_E5, Note_Quarter},
	{Note_B4, Note_Eighth},
	{Note_C5, Note_Eighth},
	{Note_D5, Note_Quarter},
	{Note_C5, Note_Eighth},
	{Note_B4, Note_Eighth},

	{Note_A4, Note_Quarter},
	{Note_A4, Note_Eighth},
	{Note_C5, Note_Eighth},
	{Note_E5, Note_Quarter},
	{Note_D5, Note_Eighth},
	{Note_C5, Note_Eighth},

	{Note_B4, Note_Quarter},
	{REST, Note_Eighth},
	{Note_C5, Note_Eighth},
	{Note_D5, Note_Quarter},
	{Note_E5, Note_Quarter},

	{Note_C5, Note_Quarter},
	{Note_A4, Note_Quarter},
	{Note_A4, Note_Half},
	//{REST, Note_Quarter},

	{REST, Note_Eighth},
	{Note_D5, Note_Quarter},
	{Note_F5, Note_Eighth},
	{Note_A5, Note_Quarter},
	{Note_G5, Note_Eighth},
	{Note_F5, Note_Eighth},

	{Note_E5, Note_Quarter},
	{REST, Note_Eighth},
	{Note_C5, Note_Eighth},
	{Note_E5, Note_Quarter},
	{Note_D5, Note_Eighth},
	{Note_C5, Note_Eighth},

	{Note_B4, Note_Quarter},
	{Note_B4, Note_Eighth},
	{Note_C5, Note_Eighth},
	{Note_D5, Note_Quarter},
	{Note_E5, Note_Quarter},

	{Note_C5, Note_Quarter},
	{Note_A4, Note_Quarter},
	{Note_A4, Note_Half},

	//Second Time
		{Note_E5, Note_Quarter},
	{Note_B4, Note_Eighth},
	{Note_C5, Note_Eighth},
	{Note_D5, Note_Quarter},
	{Note_C5, Note_Eighth},
	{Note_B4, Note_Eighth},

	{Note_A4, Note_Quarter},
	{Note_A4, Note_Eighth},
	{Note_C5, Note_Eighth},
	{Note_E5, Note_Quarter},
	{Note_D5, Note_Eighth},
	{Note_C5, Note_Eighth},

	{Note_B4, Note_Quarter},
	{REST, Note_Eighth},
	{Note_C5, Note_Eighth},
	{Note_D5, Note_Quarter},
	{Note_E5, Note_Quarter},

	{Note_C5, Note_Quarter},
	{Note_A4, Note_Quarter},
	{Note_A4, Note_Half},
	//{REST, Note_Quarter},

	{REST, Note_Eighth},
	{Note_D5, Note_Quarter},
	{Note_F5, Note_Eighth},
	{Note_A5, Note_Quarter},
	{Note_G5, Note_Eighth},
	{Note_F5, Note_Eighth},

	{Note_E5, Note_Quarter},
	{REST, Note_Eighth},
	{Note_C5, Note_Eighth},
	{Note_E5, Note_Quarter},
	{Note_D5, Note_Eighth},
	{Note_C5, Note_Eighth},

	{Note_B4, Note_Quarter},
	{Note_B4, Note_Eighth},
	{Note_C5, Note_Eighth},
	{Note_D5, Note_Quarter},
	{Note_E5, Note_Quarter},

	{Note_C5, Note_Quarter},
	{Note_A4, Note_Quarter},
	{Note_A4, Note_Half},

	//"Bridge"!
	{Note_E4, Note_Half},
	{Note_C4, Note_Half},

	{Note_D4, Note_Half},
	{Note_B3, Note_Half},
	
	{Note_C4, Note_Half},
	{Note_A3, Note_Half},
	
	{Note_G3sharp, Note_Half},
	{Note_B3, Note_QuarterDot},
	{REST, Note_Eighth},

	{Note_E4, Note_Half},
	{Note_C4, Note_Half},

	{Note_D4, Note_Half},
	{Note_B3, Note_Half},

	{Note_C4, Note_Quarter},
	{Note_E4, Note_Quarter},
	{Note_A4, Note_Half},

	{Note_G4sharp, Note_Half + Note_Quarter },
	{REST, Note_Quarter},
	{0,0},
	};
	int Note = 0;
	
	while(1)
	{
		while( *MuteMusic )
		{
			Sleep(1);
		}
		Beep(Music[Note][0], Music[Note][1] * MUSIC_TEMPO);
		
		Note++;
		if(Music[Note][1] == 0) Note = 0;
	}
}
/*
void __cdecl MusicPlayHarmony(void)
{
	int Music[][2] = {
	{Note_B4, Note_Quarter},
	{Note_A4sharp, Note_Eighth},
	{Note_A4, Note_Eighth},
	{Note_B4, Note_Eighth},
	{Note_E5, Note_Sixteenth},
	{Note_D5, Note_Sixteenth},
	{Note_A4, Note_Eighth},
	{Note_G4, Note_Eighth},
	{Note_E4, Note_Quarter},
	{Note_E4, Note_Eighth},
	{Note_A4, Note_Eighth},
	{Note_C5, Note_Quarter},
	{Note_B4, Note_Eighth},
	{Note_A4, Note_Eighth},
	
	{Note_G4sharp, Note_Eighth},
	{Note_E4, Note_Eighth},
	{Note_G4sharp, Note_Eighth},

	{Note_A4, Note_Eighth},
	{Note_B4, Note_Quarter},
	{Note_C5, Note_Quarter},
	{Note_B4, Note_Quarter},
	{Note_E4, Note_Quarter},
	{Note_E4, Note_Half},
	{0,0}
	};
	int Note = 0;
	
	while(Music[Note][0])
	{
		Beep(Music[Note][0], Music[Note][1] * MUSIC_TEMPO);
		
		Note++;
	}
}*/
	/*
		Beep(Note_E, Note_Quarter);
	Beep(Note_B, Note_Eighth);
	Beep(Note_C, Note_Eighth);
	Beep(Note_D, Note_Quarter);
	Beep(Note_C, Note_Eighth);
	Beep(Note_B, Note_Eighth);
	Beep(Note_A, Note_Quarter);*/
enum
{
	T_I,
	T_L,
	T_J,
	T_S,
	T_Z,
	T_O,
	T_T,
	MAX_T,
};

const TetBuffer Tetrominos[MAX_T] = 
{
	{
		'Û',' ',' ',' ',
		'Û',' ',' ',' ',
		'Û',' ',' ',' ',
		'Û',' ',' ',' ',
	},
	{
		'Û',' ',' ',' ',
		'Û',' ',' ',' ',
		'Û','Û',' ',' ',
		' ',' ',' ',' ',
	},
	{
		' ','Û',' ',' ',
		' ','Û',' ',' ',
		'Û','Û',' ',' ',
		' ',' ',' ',' ',
	},
	{
		'Û',' ',' ',' ',
		'Û','Û',' ',' ',
		' ','Û',' ',' ',
		' ',' ',' ',' ',
	},
	{
		' ','Û',' ',' ',
		'Û','Û',' ',' ',
		'Û',' ',' ',' ',
		' ',' ',' ',' ',
	},
	{
		'Û','Û',' ',' ',
		'Û','Û',' ',' ',
		' ',' ',' ',' ',
		' ',' ',' ',' ',
	},
	{
		'Û',' ',' ',' ',
		'Û','Û',' ',' ',
		'Û',' ',' ',' ',
		' ',' ',' ',' ',
	},
};



void DisplayScreenBuffer( char Buffer[screenH][screenW] )
{
	int i;
	system("cls");
	for( i = 0; i < screenH; i++ )
		printf( Buffer[i] );
}

void CopyTetromino( TetBuffer To, TetBuffer From )
{
	memcpy(To, From, sizeof(TetBuffer) );
}

void DrawTetOnBoard( int X, int Y, TetBuffer Tet, char Buffer[screenH][screenW]  )
{
	int i, j;
	for(i = 0; i < 4; i++)
		for(j = 0; j < 4; j++)
		{
			char TetC = (Tet[i][j]);
			char* BufC = &Buffer[boardY + Y + i][boardX + X + j];
			if( TetC != ' ' ) *BufC = TetC;
		}
}

void RemoveTetFromBoard( int X, int Y, TetBuffer Tet, char Buffer[screenH][screenW]  )
{
	int i, j;
	for(i = 0; i < 4; i++)
		for(j = 0; j < 4; j++)
		{
			char TetC = (Tet[i][j]);
			char* BufC = &Buffer[boardY + Y + i][boardX + X + j];
			if( TetC != ' ' ) *BufC = ' ';
		}
}

int CollideTet( int X, int Y, TetBuffer Tet, char Buffer[screenH][screenW]  )
{
	int i, j;
	if(Y < 0 || X < 0 ) return 1;
	for(i = 0; i < 4; i++)
		for(j = 0; j < 4; j++)
		{
			char TetC = (Tet[i][j]);
			char BufC = Buffer[boardY + Y + i][boardX + X + j];
			if( TetC != ' ' && BufC != ' ')
			{
				return 1;
			}
		}
		return 0;
}

int GetInput()
{
	if(GetAsyncKeyState(VK_LEFT)) return VK_LEFT;
	if(GetAsyncKeyState(VK_RIGHT)) return VK_RIGHT;
	if(GetAsyncKeyState(VK_UP)) return VK_UP;
	if(GetAsyncKeyState(VK_DOWN)) return VK_DOWN;
	return 0;
}

int FindEmptyRowInTet( TetBuffer Tet )
{
	int i;
	for(i = 0; i < 4; i++ )
	{
		if(Tet[i][0] == ' '
		&& Tet[i][1] == ' '
		&& Tet[i][2] == ' '
		&& Tet[i][3] == ' ')
		break;
	}
	return i;
}

void RotateTetrimino(char *X, char *Y, TetBuffer CurrentTet, char ScreenBuffer[screenH][screenW] )
{
	int i, j, DoRotate = 0, XOffset = 0, YOffset = 0, YAdjust = 0, OldYSpace = 0, StuckToBottom = 0;
	TetBuffer TempTet;
	
	//Rotate the tet into a new buffer
	for(i = 0; i < 4; i++)
	{
		for(j = 0; j < 4; j++)
		{
			TempTet[j][3-i] = CurrentTet[i][j];
		}
	}

	//Hide the current tet so we can collisiontest
	RemoveTetFromBoard( *X, *Y, CurrentTet, ScreenBuffer );

	//If we are stuck to the bottom we must continue to stick after we rotate, so we need to adjust a few units down
	if(CollideTet( *X, *Y + 1, CurrentTet, ScreenBuffer ))
	{
		StuckToBottom = 1;
		OldYSpace = FindEmptyRowInTet(CurrentTet);
	}

	//Adjust X coord of Tet within the Buffer
	while( TempTet[0][0] == ' '
		&& TempTet[0][1] == ' '
		&& TempTet[0][2] == ' '
		&& TempTet[0][3] == ' ' )
	{
		for( i = 0; i < 3; i++)	
			for( j = 0; j < 4; j++)	
				TempTet[i][j] = TempTet[i+1][j];
		if(StuckToBottom) YAdjust--;
		TempTet[3][0] = TempTet[3][1] = TempTet[3][2] = TempTet[3][3] = ' ';
	}

	//Adjust Y coord of Tet within the Buffer
	while( TempTet[0][0] == ' '
		&& TempTet[1][0] == ' '
		&& TempTet[2][0] == ' '
		&& TempTet[3][0] == ' ' )
	{
		for( i = 0; i < 3; i++)	
			for( j = 0; j < 4; j++)
			{
				TempTet[j][i] = TempTet[j][i+1];

			}

			TempTet[0][3] = TempTet[1][3] = TempTet[2][3] = TempTet[3][3] = ' ';
	}

	//Make sure we don't get stuck in something when we rotate
	for(XOffset = 0; XOffset < 3, *X - XOffset > 0; XOffset++)
	{
		if(!CollideTet( *X - XOffset, *Y, TempTet, ScreenBuffer ))
		{
			*X -= XOffset;
			DoRotate++;
			break;
		}
	}
	for(YOffset = (YAdjust - OldYSpace); YOffset < 3, *Y - YOffset > 0; YOffset++)
	{
		if(!CollideTet( *X, *Y - YOffset, TempTet, ScreenBuffer ))
		{
			*Y -= YOffset;
			DoRotate++;
			break;
		}
	}

	if(DoRotate)
	{
		CopyTetromino( CurrentTet, TempTet );
	}
	else
	{
		//*X += XOffset; *Y += YOffset;
	}
	DrawTetOnBoard( *X, *Y, CurrentTet, ScreenBuffer );
}

int AttemptToMoveTet(char* OldX, char* OldY, char NewX, char NewY, TetBuffer CurrentTet, char ScreenBuffer[screenH][screenW] )
{
	int CollisionTest;
	RemoveTetFromBoard( *OldX, *OldY, CurrentTet, ScreenBuffer ); 
	CollisionTest = CollideTet( NewX, NewY, CurrentTet, ScreenBuffer );
//	StickTetrimino = CollisionTest && (Input == VK_DOWN);
	if(!CollisionTest)
	{
		*OldX = NewX; *OldY = NewY;
	}
	DrawTetOnBoard( *OldX, *OldY, CurrentTet, ScreenBuffer );
	return !CollisionTest;
}

void DrawBackupTet( TetBuffer BackupTet, char ScreenBuffer[screenH][screenW] )
{
		int i, j;
	for(i = 0; i < 4; i++)
		for(j = 0; j < 4; j++)
		{
			ScreenBuffer[backupY + i][backupX + j] = BackupTet[i][j];
		}
}


void FillRow( char ScreenBuffer[screenH][screenW], int Row )
{
	int i;
	for(i = 0; i < boardW; i++)
	{
		ScreenBuffer[boardY + Row][boardX + i] = 'Û';
	}
}

void ShiftRowsDown( char ScreenBuffer[screenH][screenW], int Row )
{
		int i, j;
		for(j = Row; j >= 0; j--)
		{
			for(i = 0; i < boardW; i++)
			{
				if(j)
				{
					ScreenBuffer[boardY + j][boardX + i] = ScreenBuffer[boardY + j - 1][boardX + i];
				}
				else
				{
					ScreenBuffer[boardY + j][boardX + i] = ' ';
				}
			}
		}
}


void EmptyRow( char ScreenBuffer[screenH][screenW], int Row )
{
	int i;
	for(i = 0; i < boardW; i++)
	{
		ScreenBuffer[boardY + Row][boardX + i] = ' ';
	}
}

//Could be optimized
int HandleFullRows( char ScreenBuffer[screenH][screenW] )
{
	int i, j;
	int CheckOptimize = 0;
		
		for(j = 0; j < boardH; j++)
		{
			int AllFull = 1;
			for(i = 0; i < boardW; i++)
			{
				if(ScreenBuffer[boardY + j][boardX + i] == ' ') 
				{
					AllFull = 0;
					
					//break;
				}
			}
			if(AllFull) 
			{
				EmptyRow( ScreenBuffer, j );
				ShiftRowsDown( ScreenBuffer, j );
				//j--
				CheckOptimize++;
			}
			else
			{
				if(CheckOptimize) CheckOptimize++;
			}
			//Once we've found a correct row no more than three rows above can be full
			if(CheckOptimize == 4) return 1;
		}
		return 0;
}


void StickTetAndSpawnNew(char* X, char* Y, TetBuffer CurrentTet, TetBuffer BackupTet, char ScreenBuffer[screenH][screenW])
{
	*Y = startY; *X = startX;
	CopyTetromino( CurrentTet, BackupTet );
	
	CopyTetromino( BackupTet, Tetrominos[rand() % MAX_T] );
	DrawBackupTet( BackupTet, ScreenBuffer );
	
	HandleFullRows( ScreenBuffer );
	
	//if(HandleFullRows( ScreenBuffer )) Sleep(250);
	DrawTetOnBoard( *X, *Y, CurrentTet, ScreenBuffer );
	
}

void FillScreenFancy( char ScreenBuffer[screenH][screenW] )
{
	int j;
	
		for(j = boardH - 1; j >= 0; j--)
		{
			FillRow(ScreenBuffer, j);
			DisplayScreenBuffer( ScreenBuffer );
			Sleep(50);
		}
	
	
}


	

int main( int argc, char* argv )
{
	//Game stuff
	char PieceX = startX;
	char PieceY = startY;
	
	TetBuffer CurrentTet;
	TetBuffer BackupTet;

	int MuteMusic = 0;

	int timeUntilNextInput = 0;
	int timeUntilNextMute = 0;
	int timeUntilNextDrop = 500;
	char ScreenBuffer[screenH][screenW] =
	{
		"\n",
		"                TEXTRIS   \n",
		"  º          º \n",
		"  º          º  ÚÄÄÄÄÄ¿\n",
		
		"  º          º  ³Score³\n",
		"  º          º  ³    0³\n",
		"  º          º  ÃÄÄÄÄÄ´\n",
		"  º          º  ³Level³\n",
		"  º          º  ³    1³\n",
		"  º          º  ÃÄÄÄÄÄ´\n",
		"  º          º  ³Lines³\n",
		"  º          º  ³    1³\n",
		"  º          º  ÀÄÄÄÄÄÙ\n",
		"  º          º         \n",
		"  º          º         \n",
		"  º          º         \n",
		"  ÈÍÍÍÍÍÍÍÍÍÍ¼         \n",
		"  [M] to mute          \n",
		"  Made by Kyle Franz   \n",
	};

	//Console stuff
	HANDLE hOut;
    CONSOLE_SCREEN_BUFFER_INFO SBInfo;
    SMALL_RECT DisplayArea = {0, 0, screenW, screenH};
    int count = 1;
	hOut = GetStdHandle(STD_OUTPUT_HANDLE);

	GetConsoleScreenBufferInfo(hOut, &SBInfo);
	SetConsoleWindowInfo(hOut,
		TRUE,
		&DisplayArea);

	//Start music
	_beginthread( MusicPlay, 0, (void*)&MuteMusic  );
//	_beginthread( MusicPlayHarmony, 0, 0 );

	//Game code
	srand ( time(NULL) );
	
	CopyTetromino( BackupTet, Tetrominos[rand() % MAX_T] );
	StickTetAndSpawnNew(&PieceX, &PieceY, CurrentTet, BackupTet, ScreenBuffer);

	DrawTetOnBoard( startX, startY, CurrentTet, ScreenBuffer ); 
	DisplayScreenBuffer( ScreenBuffer );
	//RemoveTetFromBoard( 0, 0, Tetrominos[T_J], ScreenBuffer ); 
	while(1)
	{
		if(timeUntilNextInput == 0)
		{
			int inputSuccess = 0;
			
			if(GetAsyncKeyState(VK_RETURN) || GetAsyncKeyState(' '))
			{
				int i = 0;
				 while(AttemptToMoveTet(&PieceX, &PieceY, PieceX, PieceY + 1, CurrentTet, ScreenBuffer));

					DisplayScreenBuffer( ScreenBuffer );
					StickTetAndSpawnNew(&PieceX, &PieceY, CurrentTet, BackupTet, ScreenBuffer);
				Sleep(200);
				inputSuccess = 1;
				timeUntilNextDrop = 400;
			}
			if(GetAsyncKeyState(VK_UP) || GetAsyncKeyState('W'))
			{
				RotateTetrimino( &PieceX, &PieceY, CurrentTet, ScreenBuffer );
				inputSuccess = 1;
			}
			if(GetAsyncKeyState(VK_DOWN) || GetAsyncKeyState('S'))
			{
				if(!AttemptToMoveTet(&PieceX, &PieceY, PieceX, PieceY + 1, CurrentTet, ScreenBuffer))
				{
					StickTetAndSpawnNew(&PieceX, &PieceY, CurrentTet, BackupTet, ScreenBuffer);
				}
				timeUntilNextDrop = 400;
				inputSuccess = 1;
			}
			if(GetAsyncKeyState(VK_LEFT) || GetAsyncKeyState('A'))
			{
				AttemptToMoveTet(&PieceX, &PieceY, PieceX - 1, PieceY, CurrentTet, ScreenBuffer);
				inputSuccess = 1;
			}
			if(GetAsyncKeyState(VK_RIGHT) || GetAsyncKeyState('D'))
			{
				AttemptToMoveTet(&PieceX, &PieceY, PieceX + 1, PieceY, CurrentTet, ScreenBuffer);
				inputSuccess = 1;
			}
			if(GetAsyncKeyState('M'))
			{
				MuteMusic = MuteMusic ? 0 : 1;
				timeUntilNextMute = 50;
			}
			if(GetAsyncKeyState('F'))
			{
				FillScreenFancy(ScreenBuffer);
			}
			if(inputSuccess)
			{
				DisplayScreenBuffer( ScreenBuffer );
				timeUntilNextInput = 30;
			}
		}
		if(timeUntilNextInput > 0) timeUntilNextInput--;
		if(timeUntilNextMute > 0) timeUntilNextMute--;
		if(timeUntilNextDrop > 0) timeUntilNextDrop--;
		else
		{
			if(!AttemptToMoveTet(&PieceX, &PieceY, PieceX, PieceY + 1, CurrentTet, ScreenBuffer))
			{
				StickTetAndSpawnNew(&PieceX, &PieceY, CurrentTet, BackupTet, ScreenBuffer);
			}
			DisplayScreenBuffer( ScreenBuffer );
			timeUntilNextDrop = 400;
		}
		Sleep(1);
		if(count == 1000) count = 1;
	}
	DisplayScreenBuffer( ScreenBuffer );
	system("Pause");
	return 0;
}
