/*
	MatchIt - A Gameboy Advance puzzle game
	Copyright (C) 2005  Avelino Herrera Morales

	This program is free software; you can redistribute it and/or modify
	it under the terms of the GNU General Public License as published by
	the Free Software Foundation; either version 2 of the License, or
	(at your option) any later version.

	This program is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.

	You should have received a copy of the GNU General Public License
	along with this program; if not, write to the Free Software
	Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include "gba.h"
#include "fondo_barranco_192x128.h"
#include "fondo_playa_192x128.h"
#include "bloques_4x16x8.h"
#include "font_8x8.h"

#define  PRESSED(k)  (!(REG_KEYPAD & (k)))

/* modo gráfico */
#define BG0_ENABLE      0x100
#define BG1_ENABLE      0x200
#define BG2_ENABLE      0x400
#define BG3_ENABLE      0x800
#define OBJ_ENABLE      0x1000
#define WIN0_ENABLE     0x2000
#define WIN1_ENABLE     0x4000
#define WINOBJ_ENABLE   0x8000

#define BACK_BUFFER     0x10

#define MODE0           0x0
#define MODE1           0x1
#define MODE2           0x2
#define MODE3           0x3
#define MODE4           0x4
#define MODE5           0x5

#define  VIDEO_BUFFER_0  ((u16 *) 0x6000000)
#define  VIDEO_BUFFER_1  ((u16 *) 0x600A000)
#define  BG_PALETTE_MEM  ((u16 *) 0x5000000)

#define  TOP_LEFT      0
#define  TOP_RIGHT     239
#define  BOTTOM_LEFT   38060
#define  BOTTOM_RIGHT  38299

#define RGB16(r,g,b)  ((r) + ((g) << 5) + ((b) << 10))

#define  WIDTH_BLOCK   12
#define  HEIGHT_BLOCK  16

#define  BLOCK_WIDTH   16
#define  BLOCK_HEIGHT  8

#define  BLOCK_TOKEN_CHANGED  1
#define  BACKGROUND_CHANGED   2
#define  ALL_CHANGED          3

#define  KEY_REPEAT_TIMEOUT       75

#define  BLOCK_DESCENT_TIMEOUT_0  250
#define  BLOCK_DESCENT_TIMEOUT_1  200
#define  BLOCK_DESCENT_TIMEOUT_2  150

#define  LIMIT_SCORE_LEVEL_1  50
#define  LIMIT_SCORE_LEVEL_2  100

int block_descent_timeouts[3] = {BLOCK_DESCENT_TIMEOUT_0, BLOCK_DESCENT_TIMEOUT_1, BLOCK_DESCENT_TIMEOUT_2};

#define  GET_DESCENT_TIMEOUT(le)  block_descent_timeouts[(le) - 1];
#define  GET_LEVEL(sc)           ((sc) >= LIMIT_SCORE_LEVEL_1) ? (((sc) >= LIMIT_SCORE_LEVEL_2) ? 3 : 2) : 1

u8 block_token[WIDTH_BLOCK] = {0, 1, 2, 3, 3, 1, 0, 2, 0, 1, 1};
int block_token_y = 0;

s16 token_table[WIDTH_BLOCK][HEIGHT_BLOCK];
s16 token_table_min_y[WIDTH_BLOCK];

u16 *current_background_image = (u16 *) playaData;
u16 *current_video_buffer = VIDEO_BUFFER_1;

u16 level = 1;
u16 score = 0;
char score_message[80];
char level_message[80];

#define  ERASE_SCREEN(scr)  memset(scr, 0, 240 * 160)

int falling_block_token_y[WIDTH_BLOCK];
int falling_block_token_max_y[WIDTH_BLOCK];


void InterruptProcess(void) __attribute__ ((section(".iwram")));   // La ISR debe declararse así obligatoriamente

 
void InterruptProcess(void) {
	REG_IF |= REG_IF;
}


void flip_and_show(void) {
	if (current_video_buffer == VIDEO_BUFFER_1) {
		REG_DISPCNT |= BACK_BUFFER;
		current_video_buffer = VIDEO_BUFFER_0;
	}
	else {
		REG_DISPCNT &= ~BACK_BUFFER;
		current_video_buffer = VIDEO_BUFFER_1;
	}
}


void put_character(char c, int x, int y, u16 *video_buffer, int erase) {
	int font_y = (((int) c) - 32) / 16;
	int font_x = (((int) c) - 32) % 16;
	int i, j;
	int font_offset = (font_y * 64 * 8) + (font_x * 4);
	int offset = ((y * 240) + x) >> 1;
	for (i = 0; i < 8; i++, offset += 120, font_offset += 64) {
		if (erase) {
			for (j = 0; j < 4; j++)
				video_buffer[offset + j] = 0x0000;
		}
		for (j = 0; j < 4; j++) {
			register u16 d = fontData[font_offset + j];
			register u16 prev = video_buffer[offset + j];
			register u16 to_write = 0;
			if ((d & 0x00FF) != fontTransparentIndex)
				to_write |= (d & 0x00FF);
			else
				to_write |= (prev & 0x00FF);
			if ((d >> 8) != fontTransparentIndex)
				to_write |= (d & 0xFF00);
			else
				to_write |= (prev & 0xFF00);
			video_buffer[offset + j] = to_write;
		}
	}
}


void put_string(char *s, int x, int y, u16 *video_buffer, int erase) {
	int i = 0;
	while (s[i] != 0) {
		put_character(s[i], x, y, video_buffer, erase);
		x += 8;
		i++;
	}
}


void put_image(u16 *source, int width, int height, int x, int y, u16 *video_buffer) {
	int i;
	int offset = (y * 240) + x;
	for (i = 0; i < height; i++, offset += 240) {
		u8 *dest = ((u8 *) video_buffer) + offset;
		memcpy(dest, &(source[i * (width >> 1)]), width);
	}
}


void put_block_fine(u8 block_id, int xb, int y, u16 *screen) {
	int i;
	int offset = ((16 + y) * 240) + 24 + (xb * BLOCK_WIDTH);
	int block_offset = (block_id * (16 * 8)) >> 1;
	for (i = 0; i < 8; i++, offset += 240) {
		u8 *dest = ((u8 *) screen) + offset;
		memcpy(dest, &(bloquesData[block_offset + (i * (16 >> 1))]), 16);
	}
}


void put_block(u8 block_id, int xb, int yb, u16 *screen) {
	int i;
	int offset = ((16 + (yb * BLOCK_HEIGHT)) * 240) + 24 + (xb * BLOCK_WIDTH);
	int block_offset = (block_id * (16 * 8)) >> 1;
	for (i = 0; i < 8; i++, offset += 240) {
		u8 *dest = ((u8 *) screen) + offset;
		memcpy(dest, &(bloquesData[block_offset + (i * (16 >> 1))]), 16);
	}
}


void init_block_token() {
	u16 x;

	for (x = 0; x < WIDTH_BLOCK; x++)
		block_token[x] = (u8) (rand() & 0x03);
	block_token_y = 0;
}


void init_token_table() {
	u16 x, y;

	for (y = 0; y < (HEIGHT_BLOCK - 1); y++)
		for (x = 0; x < WIDTH_BLOCK; x++)
			token_table[x][y] = -1;
	for (x = 0; x < WIDTH_BLOCK; x++) {
		token_table[x][HEIGHT_BLOCK - 1] = (s16) (rand() & 0x03);
		token_table_min_y[x] = HEIGHT_BLOCK - 1;
	}
}


void put_token_table(u16 *screen) {
	u16 x, y;

	for (y = 0; y < HEIGHT_BLOCK; y++) {
		for (x = 0; x < WIDTH_BLOCK; x++) {
			s16 token = token_table[x][y];
			if (token >= 0)
				put_block(token, x, y, screen);
		}
	}
}


int modify_token_table_with_block_token() {
	u16 x;
	int num_coincidences = 0;

	for (x = 0; x < WIDTH_BLOCK; x++) {
		s16 peak_token_y = token_table_min_y[x];
		if ((peak_token_y < 0) || (peak_token_y >= HEIGHT_BLOCK)) {
			// En esa columna no hay nada, ponemos el token al final
			token_table[x][HEIGHT_BLOCK - 1] = block_token[x];
			token_table_min_y[x] = peak_token_y - 1;
		}
		else {
			s16 peak_token = token_table[x][peak_token_y];
			if (peak_token == block_token[x]) {
				// Coincidencia, se elimina el token de la cima
				token_table[x][peak_token_y] = -1;
				token_table_min_y[x] = peak_token_y + 1;
				num_coincidences++;
			}
			else {
				// Es diferente, se acumula (falta comprobar si hay desbordamiento para terminar el juego)
				token_table[x][peak_token_y - 1] = block_token[x];
				token_table_min_y[x] = peak_token_y - 1;
			}
		}
	}
	return num_coincidences;
}


void put_block_token_fine(int *y, u16 *screen) {
	u16 i;
	for (i = 0; i < WIDTH_BLOCK; i++)
		put_block_fine(block_token[i], i, y[i], screen);
}


void put_block_token(int yb, u16 *screen) {
	u16 i;
	for (i = 0; i < WIDTH_BLOCK; i++)
		put_block(block_token[i], i, yb, screen);
}


void rotate_block_token(int amount) {
	u8 temp[WIDTH_BLOCK];
	int i;

	if (amount > 0) {
		amount = amount % WIDTH_BLOCK;
		for (i = 0; i < WIDTH_BLOCK; i++) {
			int source_index = (i + (WIDTH_BLOCK - 1)) % WIDTH_BLOCK;
			int dest_index = (i + (WIDTH_BLOCK - 1) + amount) % WIDTH_BLOCK;
			temp[dest_index] = block_token[source_index];
		}
		for (i = 0; i < WIDTH_BLOCK; i++)
			block_token[i] = temp[i];
	}
	else if (amount < 0) {
		int aux = -amount;
		aux = aux % WIDTH_BLOCK;
		for (i = 0; i < WIDTH_BLOCK; i++) {
			int source_index = i % WIDTH_BLOCK;
			int dest_index = (i - aux + WIDTH_BLOCK) % WIDTH_BLOCK;
			temp[dest_index] = block_token[source_index];
		}
		for (i = 0; i < WIDTH_BLOCK; i++)
			block_token[i] = temp[i];
	}
}


void put_background_image(u16 *source, u16 *screen) {
	int i;
	int offset = (16 * 240) + 24;
	for (i = 0; i < 128; i++, offset += 240) {
		u8 *dest = ((u8 *) screen) + offset;
		memcpy(dest, &(source[i * (192 >> 1)]), 192);
	}
}


void wait_for_vsync() {
	while (((volatile u16) REG_VCOUNT) != 160)
		;
}


int is_block_token_over() {
	u16 x;

	for (x = 0; x < WIDTH_BLOCK; x++) {
		if ((block_token_y >= token_table_min_y[x]) && (token_table_min_y[x] > 0))
			return 1;
	}
	return 0;
}


int is_game_over() {
	u16 x;

	for (x = 0; x < WIDTH_BLOCK; x++) {
		if (token_table_min_y[x] <= 1)
			return 1;
	}
	return 0;
}

#define  GET_X_CENTER(width)  (120 - ((width) * 4))

void show_initial_screen() {
	ERASE_SCREEN(current_video_buffer);
	put_string("M A T C H I T", GET_X_CENTER(13), 30, current_video_buffer, 0);
	put_string("@ 2005", GET_X_CENTER(6), 50, current_video_buffer, 0);
	put_string("Avelino Herrera Morales", GET_X_CENTER(23), 59, current_video_buffer, 0);
	put_string("PRESS START", GET_X_CENTER(11), 100, current_video_buffer, 0);
	put_string("TO PLAY", GET_X_CENTER(7), 120, current_video_buffer, 0);
	flip_and_show();
	while (!PRESSED(BUTTON_START))
		;
	ERASE_SCREEN(current_video_buffer);
	flip_and_show();
}


void show_game_over_screen() {
	int i;

	ERASE_SCREEN(current_video_buffer);
	put_string("- GAME OVER -", 68, 71, current_video_buffer, 0);
	put_string(score_message, GET_X_CENTER(strlen(score_message)), 90, current_video_buffer, 0);
	flip_and_show();
	for (i = 0; i < 4000; i++)
		wait_for_vsync();
	ERASE_SCREEN(current_video_buffer);
	flip_and_show();
}


void init_block_token_falling() {
	u16 i;

	for (i = 0; i < WIDTH_BLOCK; i++) {
		falling_block_token_y[i] = block_token_y * BLOCK_HEIGHT;
		falling_block_token_max_y[i] = (token_table_min_y[i] - 1) * BLOCK_HEIGHT;
	}
}


int falling_block_token_work() {
	u16 i;

	int is_still_falling = 0;
	for (i = 0; i < WIDTH_BLOCK; i++) {
		if (falling_block_token_y[i] >= falling_block_token_max_y[i])
			continue;
		is_still_falling = 1;
		falling_block_token_y[i] += 4;
	}
	return is_still_falling;
}


int AgbMain(void) {
	int i;
	int change;
	int flip_buffer;
	int old_key;
	int key_timeout = 0;
	int block_descent_timeout = 0;
	int falling = 0;
	int falling_done = 0;

	REG_DISPCNT = MODE4 | BG2_ENABLE;
	for (i = 0; i < 256; i++)
		BG_PALETTE_MEM[i] = barrancoPalette[i];
	show_initial_screen();
	/*
	REG_IE = 0x0001;
	REG_IME = 1;
	*/
	init_block_token();
	init_token_table();
	flip_buffer = 0;
	change = ALL_CHANGED;
	old_key = 0;
	block_descent_timeout = GET_DESCENT_TIMEOUT(level);
	sprintf(score_message, "Score: %d", score);
	sprintf(level_message, "Level %d", level);
	while (1) {
		if (falling) {
			if (!falling_block_token_work()) {
				falling = 0;
				falling_done = 1;
			}
			change = ALL_CHANGED;
		}
		else {
			if (falling_done) {
				score += modify_token_table_with_block_token();
				sprintf(score_message, "Score: %d", score);
				level = GET_LEVEL(score);
				sprintf(level_message, "Level %d", level);
				init_block_token();
				block_descent_timeout = GET_DESCENT_TIMEOUT(level);
				change = ALL_CHANGED;
				falling_done = 0;
			}
			// Leemos el estado de las teclas
			if (PRESSED(KEYPAD_LEFT)) {
				if (old_key != KEYPAD_LEFT) {
					/* izquierda */
					rotate_block_token(-1);
					change = BLOCK_TOKEN_CHANGED;
					old_key = KEYPAD_LEFT;
					key_timeout = KEY_REPEAT_TIMEOUT;
				}
				else
					key_timeout--;
			}
			else if (PRESSED(KEYPAD_RIGHT)) {
				if (old_key != KEYPAD_RIGHT) {
					/* derecha */
					rotate_block_token(1);
					change = BLOCK_TOKEN_CHANGED;
					old_key = KEYPAD_RIGHT;
					key_timeout = KEY_REPEAT_TIMEOUT;
				}
				else
					key_timeout--;
			}
			else if (PRESSED(KEYPAD_UP)) {
			}
			else if (PRESSED(KEYPAD_DOWN)) {
				if (old_key != KEYPAD_DOWN) {
					/*
					score += modify_token_table_with_block_token();
					sprintf(score_message, "Score: %d", score);
					level = GET_LEVEL(score);
					sprintf(level_message, "Level %d", level);
					init_block_token();
					block_descent_timeout = GET_DESCENT_TIMEOUT(level);
					change = ALL_CHANGED;
					*/

					init_block_token_falling();
					falling = 1;

					old_key = KEYPAD_DOWN;
					key_timeout = KEY_REPEAT_TIMEOUT;
				}
				else
					key_timeout--;
			}
			else
				old_key = 0;
			if (key_timeout < 0)
				old_key = 0;
			// Hacemos descender el bloque de forma automática
			block_descent_timeout--;
			if (block_descent_timeout < 0) {
				block_token_y++;
				change = BLOCK_TOKEN_CHANGED;
				block_descent_timeout = GET_DESCENT_TIMEOUT(level);
			}
			if (is_block_token_over()) {

				init_block_token_falling();
				falling = 1;

				/*
				score += modify_token_table_with_block_token();
				sprintf(score_message, "Score: %d", score);
				level = GET_LEVEL(score);
				sprintf(level_message, "Level %d", level);
				init_block_token();
				block_descent_timeout = GET_DESCENT_TIMEOUT(level);
				change = ALL_CHANGED;
				*/
			}
			// Miramos a ver si hacemos gameover
			if (is_game_over()) {
				show_game_over_screen();
				show_initial_screen();
				init_block_token();
				init_token_table();
				level = 1;
				score = 0;
				block_descent_timeout = GET_DESCENT_TIMEOUT(level);
				sprintf(score_message, "Score: %d", score);
				sprintf(level_message, "Level %d", level);
				change = ALL_CHANGED;
			}
		}
		// Pintamos, si es necesario
		if (change) {
			put_background_image(current_background_image, current_video_buffer);
			put_token_table(current_video_buffer);
			if (falling)
				put_block_token_fine(falling_block_token_y, current_video_buffer);
			else
				put_block_token(block_token_y, current_video_buffer);
			put_string(score_message, 0, 0, current_video_buffer, 1);
			put_string(level_message, 150, 147, current_video_buffer, 1);
			change = 0;
			flip_buffer = 1;
		}
		// Siempre esperamos el retrazo vertical
		wait_for_vsync();
		// Intercambiamos los buffers, si es necesario
		if (flip_buffer) {
			flip_and_show();
			flip_buffer = 0;
		}
	}
}

