/* bricks.c - provided by briks <briks@riseup.net> */

#include "basic/basic.h"
#include "usetable.h"

#define SCREEN_WIDTH  96
#define SCREEN_HEIGHT 67
#define FIELD_WIDTH   8
#define FIELD_HEIGHT  7
#define BRICK_WIDTH   11
#define BRICK_HEIGHT  4
#define BRICK_SPACING 1

#define PADDLE_WIDTH  20
#define PADDLE_Y      66
#define PADDLE_SPEED  3

#define PAUSE_INITIAL 30

#define LIVES_INITIAL 5

#define LEVELS        3

int levels[LEVELS][FIELD_HEIGHT][FIELD_WIDTH] = {
	{
		{0,0,0,0,0,0,0,0},
		{0,1,1,0,0,1,1,0},
		{0,1,1,0,0,1,1,0},
		{0,0,0,0,0,0,0,0},
		{0,0,0,0,0,0,0,0},
		{0,1,0,0,0,0,1,0},
		{0,0,1,1,1,1,0,0}
	},
	{
		{0,1,0,1,0,1,0,1},
		{1,0,1,0,1,0,1,0},
		{0,1,0,1,0,1,0,1},
		{1,0,1,0,1,0,1,0},
		{0,1,0,1,0,1,0,1},
		{1,0,1,0,1,0,1,0},
		{0,0,0,0,0,0,0,0}
	},
	{
		{1,1,1,1,1,1,1,1},
		{1,1,1,1,1,1,1,1},
		{1,1,1,1,1,1,1,1},
		{1,1,1,1,1,1,1,1},
		{1,1,1,1,1,1,1,1},
		{1,1,1,1,1,1,1,1},
		{0,0,0,0,0,0,0,0}
	}
};

int playLevel(int levelNo, int pause);
void drawBricks(int bricks[FIELD_HEIGHT][FIELD_WIDTH]);
void drawPxChk(int x, int y, int color);
void drawBall(int ball[2], int color);
void drawPaddle(int paddleX, int color);
int fieldIsCleared(int bricks[FIELD_HEIGHT][FIELD_WIDTH]);
int abs(int x);

void ram(void) {

	lcdClear();
	lcdPrintln("");
	lcdPrintln("");
	lcdPrintln("    BRICKS");
	lcdPrintln("");
	lcdPrintln("");
	lcdPrintln("");
	lcdPrintln("   by briks");
	lcdRefresh();
	delayms(1000);

	int pause = PAUSE_INITIAL;
	for (int i = 1; true; i++) {
		lcdClear();
		lcdPrintln("");
		lcdPrintln("");
		lcdPrintln("");
		lcdPrintln("    Level");
		lcdPrintln("");
		lcdPrint("      ");
		lcdPrintln(IntToStr(i, 2, 0));
		lcdRefresh();
		delayms(1000);		
		if (playLevel(i % LEVELS, pause) == 0) {
			return;
		} 
		pause = pause - (pause / 4); // shorten pause (increases speed)
	}
}

int playLevel(int levelNo, int pause) {

	lcdClear();

	// load level
	int bricks[FIELD_HEIGHT][FIELD_WIDTH];
	memcpy(bricks, levels[levelNo], sizeof(bricks));

	// initialisation
	int ball[2] = {SCREEN_WIDTH / 2, FIELD_HEIGHT * (BRICK_HEIGHT + BRICK_SPACING) + 1};
	int direction[2] = {1,1};
	int paddleX = SCREEN_WIDTH / 2 - PADDLE_WIDTH / 2;
	int lives = LIVES_INITIAL;

	drawBricks(bricks);

	while ( 1 ) {

		// ball
		drawBall(ball, 0);
		ball[0] += direction[0];
		ball[1] += direction[1];

		// paddle / user input
		drawPaddle(paddleX, 0);
		int key = getInputRaw();
		switch (key) {
			case BTN_ENTER:
				// exit
				return 0;
			case BTN_LEFT:
				paddleX -= PADDLE_SPEED;
				if (paddleX < 0)
					paddleX = 0;
				break;
			case BTN_RIGHT:
				paddleX += PADDLE_SPEED;
				if (paddleX + PADDLE_WIDTH > SCREEN_WIDTH)
					paddleX = SCREEN_WIDTH - PADDLE_WIDTH;
				break;
		}
		drawPaddle(paddleX, 1);

		// collisions

		// bricks
		int x = ball[0] / ((BRICK_WIDTH + BRICK_SPACING));
		int y = ball[1] / ((BRICK_HEIGHT + BRICK_SPACING));
		if (0 <= x && x < FIELD_WIDTH && 0 <= y && y < FIELD_HEIGHT) {
			if (bricks[y][x] == 1) {
				// collision with brick
				int xRel = ball[0] - x * (BRICK_WIDTH + BRICK_SPACING);
				int yRel = ball[1] - y * (BRICK_HEIGHT + BRICK_SPACING);
				if (xRel == 0 || xRel == BRICK_WIDTH)
					direction[0] *= -1; // hit top or bottom
				if (yRel == 0 || yRel == BRICK_HEIGHT)
					direction[1] *= -1; // hit left or right
				bricks[y][x] = 0;
				if (fieldIsCleared(bricks))
					return 1; // next level
			}
		}

		// paddle / bottom
		if (direction[1] > 0) {
			// moving to the bottom 
			if (ball[1] >= PADDLE_Y) {
				if (paddleX <= ball[0] && ball[0] <= paddleX + PADDLE_WIDTH) {
					// collision with paddle
					direction[1] = - abs(direction[1]);
					if (key == BTN_LEFT)
						direction[0] = -2;
					else if (key == BTN_RIGHT)
						direction[0] = 2;
					else
						direction[0] = (direction[0] > 0) ? 1 : -1;
				}
				else {
					// ball lost
					lives--;
					if (lives == 0) {
						lcdClear();
						lcdPrintln("");
						lcdPrintln("");
						lcdPrintln("");
						lcdPrintln("");
						lcdPrintln("  GAME OVER");
						lcdRefresh();
						delayms(2000);
						return 0;
					}
					ball[0] = SCREEN_WIDTH / 2;
					ball[1] = FIELD_HEIGHT * (BRICK_HEIGHT + BRICK_SPACING) + 1;
					direction[0] = (paddleX + PADDLE_WIDTH / 2 < ball[0]) ? -1 : 1;
					direction[1] = 1;
				}
			}
		}

		// walls

		if (ball[1] <= 0)
			direction[1] = abs(direction[1]);
		if (ball[0] <= 0)
			direction[0] = abs(direction[0]);
		else if (ball[0] >= SCREEN_WIDTH - 1)
			direction[0] = - abs(direction[0]);

		drawBricks(bricks);
		drawBall(ball, 1);

		lcdRefresh();
		delayms(pause);
	}
	return 0;
}

void drawBricks(int bricks[FIELD_HEIGHT][FIELD_WIDTH]) {
	for (int x = 0; x < FIELD_WIDTH; x++)
		for (int y = 0; y < FIELD_HEIGHT; y++)
			for (int i = 0; i < BRICK_WIDTH; i++)
				for (int j = 0; j < BRICK_HEIGHT; j++)
					lcdSetPixel(x * (BRICK_WIDTH + BRICK_SPACING) + i, y * (BRICK_HEIGHT + BRICK_SPACING) + j, bricks[y][x]);
}

void drawPxChk(int x, int y, int color) {
	if (x < 0 || x >= SCREEN_WIDTH || y < 0 || y >= SCREEN_HEIGHT)
		return;
	lcdSetPixel(x, y, color);
}

void drawBall(int ball[2], int color) {
	drawPxChk(ball[0] - 1, ball[1] - 1, color);
	drawPxChk(ball[0], ball[1] - 1, color);
	drawPxChk(ball[0] + 1, ball[1] - 1, color);
	drawPxChk(ball[0] - 1, ball[1], color);
	drawPxChk(ball[0], ball[1], color);
	drawPxChk(ball[0] + 1, ball[1], color);
	drawPxChk(ball[0] - 1, ball[1] + 1, color);
	drawPxChk(ball[0], ball[1] + 1, color);
	drawPxChk(ball[0] + 1, ball[1] + 1, color);
}

void drawPaddle(int paddleX, int color) {
	for (int x = 0; x < PADDLE_WIDTH; x++) {
		lcdSetPixel(paddleX + x, PADDLE_Y, color);
		lcdSetPixel(paddleX + x, PADDLE_Y + 1, color);
	}
}

int fieldIsCleared(int bricks[FIELD_HEIGHT][FIELD_WIDTH]) {
	for (int x = 0; x < FIELD_WIDTH; x++)
		for (int y = 0; y < FIELD_HEIGHT; y++)
			if (bricks[y][x] == 1)
				return 0;
	return true;
}

int abs(int x) {
	if (x < 0)
		return x * -1;
	return x;
}