/*
	Implementación de Tremor en la Gameboy Advance (un decodificador OggVorbis en punto fijo)


    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 <string.h>
#include "gba.h"
#include "font_8x8.h"
#include "palette.h"
#include "libtremor/ivorbisfile.h"
#include "libtremor/ivorbiscodec.h"

#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 OBJ_MAP_LINEAR  0x40
#define OBJ_MAP_2D      0x0
#define BACK_BUFFER     0x10

#define  BG_PALETTE_MEM  ((u16 *) 0x5000000)

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

u16 *screen_buffer = (u16 *) 0x6000000;

#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  SAMPLE_RATE  11025

// #define  BUFFER_SIZE    2048
#define  BUFFER_SIZE    512
#define  BUFFER_SIZE_2  (BUFFER_SIZE / 2)
#define  BUFFER_SIZE_4  (BUFFER_SIZE / 4)
#define  BUFFER_SIZE_8  (BUFFER_SIZE / 8)



/* fichero ogg-vorbis a reproducir */
extern const u8 ogg_vorbis_file_data[];
int file_pointer = 0;
int file_size = 561426;



u32 buffer[BUFFER_SIZE_4];  /* la muestra, 8 bits con signo a 16KHz */
u32 *buffer_middle = &(buffer[BUFFER_SIZE_8]);  /* puntero a la mitad del buffer */

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

int sub_buffer = 0;
int last_written = 1;


void InterruptProcess(void) {
	/* se acabó el sample */
	if (sub_buffer == 0) {
		/* Terminó de reproducirse la primera mitad del buffer */
		REG_DMA1CNT_H = 0; /* paramos la transferencia por DMA */
		REG_DMA1SAD = (unsigned long) buffer_middle; /* nuevo buffer origen para el DMA1 (la segunda mitad del buffer) */
		REG_DMA1CNT_H = 0xb600; /* reactivamos el DMA1 + FIFO + 32 bits + con repetición */
		sub_buffer = 1;
	}
	else {
		/* terminó de reproducirse la segunda mitad del buffer */
		REG_DMA1CNT_H = 0; /* paramos la transferencia por DMA */
		REG_DMA1SAD = (unsigned long) buffer; /* nuevo buffer origen para el DMA1 (la primera mitad del buffer) */
		REG_DMA1CNT_H = 0xb600; /* reactivamos el DMA1 + FIFO + 32 bits + con repetición */
		sub_buffer = 0;
	}
	/* borramos siempre el flag de interrupción */
	REG_IF |= REG_IF;
}


void dma_stop_sound(void) {
	REG_TM0CNT_H = 0; /* deshabilitamos el timer 0 */
	REG_DMA1CNT_H = 0; /* paramos la transferencia por DMA */
}

 
void dma_play_sound(void) {
	REG_TM0CNT_H = 0; /* deshabilitamos el timer 0*/
	/* reproducimos un sonido mono a 16KHz en modo DMA */
	/* utilizamos el timer 0 como generador del período de muestreo */
	/* y el timer 1 en cascada para contar los samples que se van enviando a la FIFO del chip de sonido */
	REG_SOUNDCNT_L = 0;
	REG_SOUNDCNT_H = 0x0b0f; /* Direct Sound A&B + reiniciar la FIFO + timer0 + volumen máximo a los canales L y R */
	REG_SOUNDCNT_X = 0x0080; /* activamos el chip de sonido */
	
	REG_DMA1SAD = (unsigned long) buffer; /* buffer origen para el DMA1 */
	REG_DMA1DAD = 0x040000a0; /* buffer destino = dirección del puerto FIFO del chip de sonido */
	REG_DMA1CNT_H = 0xb600; /* activamos el DMA1 + FIFO + 32 bits + con repetición */
	
	REG_TM1CNT_L = 0xffff - BUFFER_SIZE_2; /* 0xffff - número de muestras (la primera mitad del buffer) */
	REG_TM1CNT_H = 0xC4; /* habilitamos el timer 1 + irq y en cascada con el timer 0 */

	REG_IE = 0x10; /* habilitar interrupción al desbordarse el timer 1 */
	REG_IME = 1; /* habilitar las interrupciones */
	
	/* La fórmula para obtener el valor del timer 0: 65536 - round(frecCPU / frecMuestreo) siendo frecCPU = 2^24 Hz (aprox. 16MHz) */
	//REG_TM0CNT_L = 0xFBE8; /* 16kHz */
	REG_TM0CNT_L = 0xFA0E; /* 11025 Hz */
	REG_TM0CNT_H = 0x0080; /* habilitar timer 0 a la frecuencia de la CPU */
	/* indicamos que estamos reproduciendo la primera mitad del buffer */
	sub_buffer = 0;
}


/*
void write_sound(u32 *data) {
	if (last_written == 0) {
		while (sub_buffer == 1)
			;
		// escribimos en la segunda mitad del buffer 
		memcpy(buffer_middle, data, BUFFER_SIZE_2);
		last_written = 1;
	}
	else {
		while (sub_buffer == 0)
			;
		// escribimos en la primera mitad del buffer
		memcpy(buffer, data, BUFFER_SIZE_2);
		last_written = 0;
	}
}
*/

/* callbacks para el decodificador tremor */
size_t read_ogg_vorbis(void *ptr, size_t size, size_t nmemb, void *datasource) {
	register int n = size * nmemb;
	register u8 *p = (u8 *) datasource;
	if ((file_pointer + n) > file_size) {
		n = file_size - file_pointer;
		if (n > 0)
			memcpy(ptr, &(p[file_pointer]), n);
		else
			n = 0;
		file_pointer = file_size;
	}
	else {
		memcpy(ptr, &(p[file_pointer]), n);
		file_pointer += n;
	}
	return n;
}

int seek_ogg_vorbis(void *datasource, ogg_int64_t offset, int whence) {
	/*
	if (whence == SEEK_SET)
		file_pointer = offset;
	else if (whence == SEEK_CUR)
		file_pointer += offset;
	else if (whence == SEEK_END)
		file_pointer = file_size - offset;
	return file_pointer;
	*/
	return -1;
}

int close_ogg_vorbis(void *datasource) {
	file_pointer = 0;
	return 0;
}

long tell_ogg_vorbis(void *datasource) {
	return file_pointer;
}

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++;
	}
}


int AgbMain(void) {
	OggVorbis_File vf;
	ov_callbacks callbacks;
	signed short aux_buffer[BUFFER_SIZE];
	int current_section, i, j;
	char aux_str[20];

	REG_DISPCNT = MODE4 | BG2_ENABLE;
	for (i = 0; i < 256; i++)
		BG_PALETTE_MEM[i] = my_palette[i];
	callbacks.read_func = read_ogg_vorbis;
	callbacks.seek_func = seek_ogg_vorbis;
	callbacks.close_func = close_ogg_vorbis;
	callbacks.tell_func = tell_ogg_vorbis;
	put_string("Opening OggVorbis stream...", 0, 0, screen_buffer, 0);
	if (0 != ov_open_callbacks((void *)ogg_vorbis_file_data, &vf, NULL, 0, callbacks)) {
		// error
		put_string("ERROR opening OggVorbis", 0, 8, screen_buffer, 0);
		while (1)
			;
	}
	put_string("OggVorbis opened!", 0, 8, screen_buffer, 0);
	put_string("Playing...", 0, 16, screen_buffer, 0);
	dma_play_sound();
	j = 0;
	while (1) {
		// sprintf(aux_str, "%d", j++);
		register int ret = ov_read(&vf, (char *) aux_buffer, BUFFER_SIZE_2 * sizeof(signed short), &current_section);
		// put_string(aux_str, 0, 16, screen_buffer, 1);
		if (ret == 0)
			break;
		else if (ret < 0) {
			// stream error
			put_string("Stream ERROR", 0, 24, screen_buffer, 1);
		}
		else {
			register signed short *source = aux_buffer;
			register signed char *dest;
			register int i;
			if (last_written == 0) {
				while (sub_buffer == 1)
					;
				// escribir segunda mitad del buffer
				dest = (signed char *) buffer_middle;
				last_written = 1;
			}
			else {
				while (sub_buffer == 0)
					;
				// escribir primera mitad del buffer
				dest = (signed char *) buffer;
				last_written = 0;
			}
			for (i = 0; i < BUFFER_SIZE_2; i++, source++, dest++)
				*dest = (signed char) (*source >> 8);
		}
	}
	put_string("Stream end", 0, 32, screen_buffer, 0);
	dma_stop_sound();
	while (1)
		;
}

