/***************************************************************************
 sfx_save.cfsml Copyright (C) 1999 Christoph Reichenbach, TU Darmstadt


 This program may be modified and copied freely according to the terms of
 the GNU general public license (GPL), as long as the above copyright
 notice and the licensing information contained herein are preserved.

 Please refer to www.gnu.org for licensing details.

 This work is provided AS IS, without warranty of any kind, expressed or
 implied, including but not limited to the warranties of merchantibility,
 noninfringement, and fitness for a specific purpose. The author will not
 be held liable for any damage caused by this work or derivatives of it.

 By using this source code, you agree to the licensing terms as stated
 above.


 Please contact the maintainer for bug reports or inquiries.

 Current Maintainer:

    Christoph Reichenbach (CJR) [jameson@linuxgames.com]

***************************************************************************/
/* CFSML file providing persistance to the sound system
** Note that this is only useful if the default sound library implementation
** is in use.
*/

#include <stdio.h>
#include <sound.h>
#include <soundserver.h>
#include <sci_memory.h>

#ifdef _WIN32
#include <direct.h>
#define chdir _chdir
#define getcwd _getcwd
#endif

#ifdef _DREAMCAST
#include <dc.h>
#endif

#ifndef O_BINARY
#define O_BINARY 0
#endif  /* !O_BINARY */

#define SOUND_SAVEGAME_VERSION 2;


typedef struct {

	int sound_version;

	int number_of_songs;
	song_t *songs; /* All songs in order */
	int active_song;

	SOUND_SERVER_STATE_SAVED_MEMBERS	/* Defined in sound_server_state_t */

} sound_lib_file_ver2_t;


typedef struct {

	int sound_version;

	int songs_nr; /* Number of songs */
	song_t *songs; /* All songs in order */

	int active_song;

	int master_volume; /* duh.. */
	int soundcue; /* Cumulative sound cue */
	long usecs_to_sleep; /* Microseconds until the next tick is due */
	long ticks_to_wait; /* Ticks until the next sound command has to be interpreted */
	long ticks_to_fade; /* Ticks until a fade-out is complete */

} sound_lib_file_ver1_t;



%CFSML

TYPE word "word" LIKE int
TYPE long "long" LIKE int

RECORD song_t "song_t" {

	int flags[STATIC MIDI_CHANNELS];
	int polyphony[STATIC MIDI_CHANNELS];
	int instruments[STATIC MIDI_CHANNELS];
	int velocity[STATIC MIDI_CHANNELS];
	int pressure[STATIC MIDI_CHANNELS];
	int pitch[STATIC MIDI_CHANNELS];
	int channel_map[STATIC MIDI_CHANNELS];

	int size;
	int pos;
	int loopmark;
	int fading;
	int maxfade;

	int reverb;

	int file_nr;

	int priority;
	int loops;
	int status;

	int resetflag;
	int handle;

}

RECORD sound_lib_file_ver2_t "sound_lib_file_ver2_t" {

	int sound_version;

	song_t songs[DYNAMIC number_of_songs];

	int active_song;

	int master_volume;
	int sound_cue;

}

RECORD sound_lib_file_ver1_t "sound_lib_file_ver1_t" {

	int sound_version;

	song_t songs[DYNAMIC songs_nr];

	int active_song;

	int soundcue;
	long usecs_to_sleep;
	long ticks_to_wait;
	long ticks_to_fade;

}

%END CFSML


/* Sound state saving reference implementation */
int
soundsrv_save_state(FILE *debugstream, char *dir, sound_server_state_t *sss)
{
	sound_lib_file_ver2_t write_rec;
	song_t *seeker;		/* song seeker */
	int number_of_songs = 0;
	int curr_songs_nr;	/* current song number */
	int songctr;		/* song counter */
	FILE *fh;

	if (dir && chdir(dir)) {
		char *cwd;

		cwd = getcwd(NULL, 0);
		fprintf(debugstream, "Failed to enter '%s', cwd is '%s'\n", dir, cwd);
		sci_free(cwd);
		return 1;
	}

#ifdef DEBUG_SOUND_SERVER
	fprintf(debugstream, "Saving game...\n");
#endif

	write_rec.sound_version = SOUND_SAVEGAME_VERSION;

	write_rec.master_volume = sss->master_volume;
	write_rec.sound_cue = sss->sound_cue;

#ifdef DEBUG_SOUND_SERVER
	fprintf(debugstream, " Save game version: %i\n", write_rec.sound_version);
	fprintf(debugstream, " Master volume    : %i\n", write_rec.master_volume);
	fprintf(debugstream, " Sound cue        : %i\n", write_rec.sound_cue);
#endif

	/* Determine number of songs */
	number_of_songs = song_lib_count(sss->songlib);
	seeker = *sss->songlib;

	for (curr_songs_nr = 0; curr_songs_nr < number_of_songs; curr_songs_nr++)
	{
		if (seeker == sss->current_song)
			break;

		seeker = seeker->next;
	}

	write_rec.number_of_songs = number_of_songs;
	write_rec.active_song = curr_songs_nr;

#ifdef DEBUG_SOUND_SERVER
	fprintf(debugstream, " Number of songs  : %i\n", write_rec.number_of_songs);
	if (seeker)
		fprintf(debugstream, " Active song      : %04x (%i)\n", seeker->handle, write_rec.active_song);
	else
		fprintf(debugstream, " No active song   : (%i)\n", write_rec.active_song);
#endif

	write_rec.songs = sci_malloc(sizeof(song_t) * number_of_songs);


	/* Now memcpy those songs into a row and write their song data to a file */
	songctr = 0;
	seeker = *sss->songlib;

	while (seeker) {
		char filename[10];
		FILE *fh;

		memcpy(&(write_rec.songs[songctr]), seeker, sizeof(song_t));
		write_rec.songs[songctr].file_nr = songctr;

		/* Now write to an external file */
		sprintf(filename, "song.%d", songctr);
		fh = fopen(filename, "w" FO_BINARY);

		if (!fh) {
			fprintf(debugstream, "Error creating file: %s while creating '%s/%s'\n",
			      strerror(errno), dir, filename);
			sci_free(write_rec.songs);
			if (dir)
				chdir ("..");
			return 1;
		}

#ifdef DEBUG_SOUND_SERVER
	fprintf(debugstream, " Writing song     : %04x\n", seeker->handle);
#endif

		if (fwrite(seeker->data, sizeof(byte), seeker->size, fh) < seeker->size) {
			fprintf(debugstream, "Write error: Failed to write to '%s/%s'\n", dir, filename);
			sci_free(write_rec.songs);
			if (dir)
				chdir ("..");
			return 1;
		}
		fclose(fh);

		songctr++;
		seeker = seeker->next;
	}

	fh = fopen("sound", "w" FO_TEXT);

	%CFSMLWRITE sound_lib_file_ver2_t &write_rec INTO fh;

	fclose(fh);
	fprintf(debugstream, "Finished all writing.\n");

	sci_free(write_rec.songs);

	if (dir)
		chdir ("..");

	return 0;
}

static void
recover_version0(sound_lib_file_ver1_t *rec)
{
	int i;

	for (i = 0; i < rec->songs_nr; i++) {
		int j;
		song_t *song = rec->songs + i;

		song->reverb = 0;
		song->maxfade = 16;
		song->resetflag = 0;

		song->shared = 0;

		for (j = 0; j < MIDI_CHANNELS; j++)
			song->polyphony[j] = 1;
	}
}

static int
recover_version1(FILE *fh, sound_lib_file_ver1_t *rec,
	sound_server_state_t *sss, FILE *ds)
{
	song_t *seeker, *next = NULL;
	int i;
	int error;

	rec->songs = NULL;

	/* Backwards capability crap */
	rec->sound_version = 0;
	rec->master_volume = sss->master_volume;

	/* Read all the records */
	%CFSMLREAD-ATOMIC sound_lib_file_ver1_t rec FROM fh ERRVAR error;
	fclose(fh);

	if (error) {
		fprintf(ds, "Failed to parse file 'sound'\n");
		return 1;
	}

	if (rec->sound_version == 0)
		recover_version0(rec);

	/* Read in each song's data */
	for (i = 0; i < rec->songs_nr; i++) {
		int size = rec->songs[i].size;
		char filename[10];

		sprintf(filename, "song.%d", rec->songs[i].file_nr);
		fh = fopen(filename, "r" FO_BINARY);

		if (!fh) {
			fprintf(ds, "Opening %s failed: %s\n", filename, strerror(errno));
			return 1;
		}

		rec->songs[i].data = (byte *) sci_malloc(size);
		if (fread(rec->songs[i].data, sizeof(byte), size, fh) < size) {
			int j;
			for (j = 0; j < i; j++)
				sci_free(rec->songs[i].data);
			fprintf(ds, "Reading %d bytes from %s failed\n", size, filename);
			return 1;
		}

		fclose(fh);
	}


	/* Everything is well, so free all songs to make way for the new ones */
	song_lib_free(sss->songlib);
	sss->current_song = NULL;

#ifdef DEBUG_SOUND_SERVER
	fprintf(ds, " Number of songs  : %i\n", rec->songs_nr);
#endif

	for (i = 0; i < rec->songs_nr; i++) {

		next = sci_malloc(sizeof(song_t));
		memcpy(next, &(rec->songs[i]), sizeof(song_t));
		next->next = NULL;
		next->shared = 0;
		if (i > 0)
			seeker->next = next;

#ifdef DEBUG_SOUND_SERVER
	fprintf(ds, " Reading song     : %04x\n", next->handle);
#endif

		seeker = next;


		if (i == 0)
			*sss->songlib = seeker;

		if (i == rec->active_song)
			sss->current_song = seeker;
	}

#ifdef DEBUG_SOUND_SERVER
	if (sss->current_song)
		fprintf(ds, " Active song      : %04x (%i)\n", sss->current_song->handle, rec->active_song);
	else
		fprintf(ds, " No active song   : (%i)\n", rec->active_song);
#endif

#ifdef DEBUG_SOUND_SERVER
	fprintf(ds, " Master volume    : %i\n", rec->master_volume);
	fprintf(ds, " Sound cue        : %i\n", rec->soundcue);
#endif

	/* sss->usecs_to_sleep = rec->usecs_to_sleep; */
	/* sss->ticks_to_wait = rec->ticks_to_wait; */
	/* sss->ticks_to_fade = rec->ticks_to_fade; */
	set_master_volume((guint8)((float)rec->master_volume / 100.0 * 15.0), sss);
	sss->sound_cue = rec->soundcue;

	if (rec->songs_nr)
		sci_free(rec->songs);

	return 0;
}

static int
recover_version2(FILE *fh, sound_lib_file_ver2_t *rec,
	sound_server_state_t *sss, FILE *ds)
{
	song_t *seeker, *next = NULL;
	int i;
	int error;

	rec->songs = NULL;

	/* Read all the records */
	%CFSMLREAD-ATOMIC sound_lib_file_ver2_t rec FROM fh ERRVAR error;
	fclose(fh);

	if (error) {
		fprintf(ds, "Failed to parse file 'sound'\n");
		return 1;
	}

	/* Read in each song's data */
	for (i = 0; i < rec->number_of_songs; i++) {
		int song_data_size = rec->songs[i].size;
		char filename[10];

		sprintf(filename, "song.%d", rec->songs[i].file_nr);
		fh = fopen(filename, "r" FO_BINARY);

		if (!fh) {
			fprintf(ds, "Opening %s failed: %s\n", filename, strerror(errno));
			return 1;
		}

		rec->songs[i].data = (byte *) sci_malloc(song_data_size);
		rec->songs[i].shared = 0;
		if (fread(rec->songs[i].data, sizeof(byte), song_data_size, fh) < song_data_size) {
			int j;
			for (j = 0; j < i; j++)
				sci_free(rec->songs[i].data);
			fprintf(ds, "Reading %d bytes from %s failed\n", song_data_size, filename);
			return 1;
		}

		fclose(fh);
	}


	/* Everything is well, so free all songs to make way for the new ones */
	song_lib_free(sss->songlib);
	if (sss->current_song)
		sss->current_song->data = NULL;
	sss->current_song = NULL;

#ifdef DEBUG_SOUND_SERVER
	fprintf(ds, " Number of songs  : %i\n", rec->number_of_songs);
#endif

	for (i = 0; i < rec->number_of_songs; i++) {

		next = sci_malloc(sizeof(song_t));
		memcpy(next, &(rec->songs[i]), sizeof(song_t));
		next->next = NULL;
		if (i > 0)
			seeker->next = next;

#ifdef DEBUG_SOUND_SERVER
	fprintf(ds, " Reading song     : %04x\n", next->handle);
#endif

		seeker = next;

		if (i == 0)
			*sss->songlib = seeker;

		if (i == rec->active_song)
			sss->current_song = seeker;
	}

#ifdef DEBUG_SOUND_SERVER
	if (sss->current_song)
		fprintf(ds, " Active song      : %04x (%i)\n", sss->current_song->handle, rec->active_song);
	else
		fprintf(ds, " No active song   : (%i)\n", rec->active_song);
#endif

	if (song_lib_count(sss->songlib) != rec->number_of_songs)
	{
		fprintf(ds, "Number of songs read does not match number of songs saved\n");
		return 1;
	}

#ifdef DEBUG_SOUND_SERVER
	fprintf(ds, " Master volume    : %i\n", rec->master_volume);
	fprintf(ds, " Sound cue        : %i\n", rec->sound_cue);
#endif

	set_master_volume((guint8)((float)rec->master_volume / 100.0 * 15.0), sss);
	sss->sound_cue = rec->sound_cue;

	if (rec->number_of_songs)
		sci_free(rec->songs);

	return 0;
}


/* Sound state restore complement for the saving reference implementation */
int
soundsrv_restore_state(FILE *debugstream, char *dir, sound_server_state_t *sss)
{
	FILE *fh;
	int ch = 0;
	int sound_version;

	if (dir && chdir(dir)) {
		fprintf(debugstream, "Failed to enter '%s'\n", dir);
		return 1;
	}

	fh = fopen("sound", "r" FO_TEXT);

	if (!fh) {
		fprintf(debugstream, "'%s/sound' not found!\n", dir);
		if (dir)
			chdir ("..");
		return 1;
	}

	/* find sound version */
	while ( (ch < '0') || (ch > '9') )
		ch = fgetc(fh);
	ungetc(ch, fh);

#ifdef _DREAMCAST
	/* FIXME (Dreamcast) */
	sound_version = 2;
#else
	if ( (fscanf(fh, "%d", &sound_version)) < 0 ) {
		fprintf(debugstream, "Error reading saved game sound version!\n");
		if (dir)
			chdir ("..");
		return 1;
	}
#endif
	rewind(fh);

#ifdef DEBUG_SOUND_SERVER
	fprintf(debugstream, "Restoring game %s...\n", dir);
	fprintf(debugstream, " Save game version: %i\n", sound_version);
#endif

	if ( (sound_version == 0) || (sound_version == 1) ) {
		sound_lib_file_ver1_t read_rec;

		if (recover_version1(fh, &read_rec, sss, debugstream) != 0) {
			if (dir)
				chdir ("..");
			return 1;
		}

	} else if (sound_version == 2) {
		sound_lib_file_ver2_t read_rec;

		if (recover_version2(fh, &read_rec, sss, debugstream) != 0) {
			if (dir)
				chdir ("..");
			return 1;
		}

	}
	if (dir)
	  chdir ("..");

	return 0;
}
