/* music.m,
 *
 * This file handles music stuff using AllegroMP3.
 */

#include <allegro.h>
#include <assert.h>
#include <pthread.h>
#include <sys/stat.h>
#include "almp3/include/almp3.h"
#include "music-ad.h"
#include "music.h"
#include "sound.h"
#include "seborrhea/container-lump.h"
#include "seborrhea/seborrhea-mp3.h"


/* Music settings. */
#define PAN		128
#define SPEED		1000

static char *music_data;
static off_t music_data_size_allocated;
static ALMP3_MP3 *music;
static SebLump *music_list;
static SebMP3 *last_music_file;
static enum {
    MUSIC_NOT_LOADING,
    MUSIC_LOADING_WAITING_FOR_THREAD,
    MUSIC_LOADING_THREAD_DONE
}  music_loading;

int music_vol = 255;

/*--------------------------------------------------------------*/
/* In-game volume adjusting.					*/
/*--------------------------------------------------------------*/

#define KEY_VOLUME_UP		KEY_F3
#define KEY_VOLUME_DOWN		KEY_F2

#ifdef ITOUCH
/* These work for me. :) */
# define KEY_VOLUME_UP2		95
# define KEY_VOLUME_DOWN2	96
#else
# define KEY_VOLUME_UP2		KEY_VOLUME_UP
# define KEY_VOLUME_DOWN2	KEY_VOLUME_DOWN
#endif

/*--------------------------------------------------------------*/

static pthread_t music_loader_thread;

static BOOL allocate_memory_for(const char *filename)
{
    struct stat stats;
    void *p;
    
    if (stat(filename, &stats) != 0) {
	fprintf(stderr, "[Music] Error statting %s.", filename);
	return NO;
    }

    /* Already allocated enough memory. */
    if (stats.st_size+1 <= music_data_size_allocated)
	return YES;

    p = realloc(music_data, stats.st_size+1);
    if (!p) {
	fprintf(stderr, "[Music] Not enough memory to load %s.", filename);
	return NO;
    }

    music_data = p;
    music_data_size_allocated = stats.st_size+1;
    return YES;
}

static void *music_loader(void *arg)
{
    ALMP3_MP3 *work = NULL;
    SebMP3 *new_music_file;
    assert(music_loading == MUSIC_LOADING_WAITING_FOR_THREAD);
    (void)arg;

#ifndef NO_ID3TAGS
    music_ad = [music_ad free];
#endif

    almp3_destroy_mp3(music);

    do {
	/* Don't pick the same file twice in a row.  Just pick a new
	   file the next time around. */
	new_music_file = (SebMP3 *)[music_list getRandomSebum];
	if ([music_list numElements] == 1)
	    break;
    } while (new_music_file == last_music_file);

    /* Get the file size and reallocate if necessary. */
    if (not allocate_memory_for([new_music_file getMP3Filename]))
	goto end;

    work = [new_music_file loadMP3IntoMemory:music_data :music_data_size_allocated];
    if (work) {
	/* If only one file in directory, loop to avoid reloading. */
	BOOL loop = ([music_list numElements] == 1) ? YES : NO;

	almp3_play_ex_mp3(work, 32768, music_vol, PAN, SPEED, loop);
	last_music_file = new_music_file;

#ifndef NO_ID3TAGS
	if (music_ad_enabled)
	    music_ad = [[MusicAd alloc]
			   initWithTagsFrom:[new_music_file getMP3Filename]];
#endif
    }
    else {
	fprintf(stderr, "[Music] %s is not a MP3 file.\n",
		[new_music_file getMP3Filename]);
    }

 end:
    music = work;
    music_loading = MUSIC_LOADING_THREAD_DONE;
    return NULL;
}

/*--------------------------------------------------------------*/

void play_music(const char *directory)
{
    if (music_list) {
	music_list = [music_list free];
	almp3_destroy_mp3(music);
	music = NULL;
	music_loading = MUSIC_NOT_LOADING;

	/* Don't check for playing the same file twice. */
	last_music_file = nil;
    }

    /* Load the directory.  No need to load the file yet.  Polling
       will pick a random file. */
    music_list = (SebLump *)[SebLump new];
    if (not [music_list loadSebumDirectory:directory]) {
	/* SebLump will free itself when it fails.  */
	music_list = nil;
	return;
    }

    if ([music_list numElements] == 0)
	music_list = [music_list free];
}

void poll_music(void)
{
    int old_vol = music_vol;

    /* In game volume adjusting. */
    if (key[KEY_VOLUME_UP] || key[KEY_VOLUME_UP2])
	music_vol += 5;
    if (key[KEY_VOLUME_DOWN] || key[KEY_VOLUME_DOWN2])
	music_vol -= 5;
    music_vol = MID(0, music_vol, 255);

    if (music_vol != old_vol)
	adjust_music_volume();


    if (music_vol == 0)
	return;

    if (music) {
	if (almp3_poll_mp3(music) == ALMP3_OK)
	    return;
    }

    /* Somehow out music has stopped playing.  Load a new file in
       another thread. */
    if (music_loading != MUSIC_LOADING_WAITING_FOR_THREAD && music_list) {
	music_loading = MUSIC_LOADING_WAITING_FOR_THREAD;
	pthread_create(&music_loader_thread, NULL, music_loader, NULL);
    }
    else if (music_loading == MUSIC_LOADING_THREAD_DONE) {
	pthread_join(music_loader_thread, NULL);
    }
}

void free_music(void)
{
    if (music) {
	almp3_destroy_mp3(music);
	music = NULL;
    }

    music_list = [music_list free];

#ifndef NO_ID3TAGS
    music_ad = [music_ad free];
#endif
}

void adjust_music_volume(void)
{
    if (music && music_list) {
	BOOL loop;
	if ([music_list numElements] == 1)
	    loop = YES;
	else
	    loop = NO;

	almp3_adjust_mp3(music, music_vol, PAN, SPEED, loop);
    }
}

/*--------------------------------------------------------------*/
/* REdit needs these.						*/
/*--------------------------------------------------------------*/

static ALMP3_MP3 *load_music_into_memory(const char *filename)
{
    PACKFILE *fp;
    long music_size;

    if (not allocate_memory_for(filename))
	return NULL;

    fp = pack_fopen(filename, F_READ);
    if (not fp) {
	fprintf(stderr, "Warning: %s not found.\n", filename);
	return NULL;
    }

    music_size = pack_fread(music_data, music_data_size_allocated, fp);
    pack_fclose(fp);
    return almp3_create_mp3(music_data, music_size);
}

void play_mp3(const char *filename)
{
    free_music();
    music = load_music_into_memory(filename);
    if (music)
	almp3_play_ex_mp3(music, 32768, music_vol, PAN, SPEED, YES);
}

void start_autopoll_music(void)
{
    if (music_vol != 0 && music)
	almp3_start_autopoll_mp3(music, 50);
}

void stop_autopoll_music(void)
{
    if (music_vol != 0 && music)
	almp3_stop_autopoll_mp3(music);
}

/*--------------------------------------------------------------*/

void music_init(void)
{
    if (not sound_initialized)
	music_vol = 0;
}

void music_shutdown(void)
{
    free_music();
    free(music_data);
    music_data = NULL;
}
