/***
The `audio` module.
An audio channel can play only one audio object at a time.
There are 24 audio channels available, numbered from 0 to 23.
@module ctr.audio
@usage local audio = require("ctr.audio")
*/

#include <3ds.h>

#include <malloc.h>
#include <string.h>
#include <errno.h>

#include <lua.h>
#include <lauxlib.h>

#include <vorbis/codec.h>
#include <vorbis/vorbisfile.h>

// Audio object type
typedef enum {
	TYPE_UNKNOWN = -1,
	TYPE_OGG = 0,
	TYPE_WAV = 1
} filetype;

// Audio object userdata
typedef struct {
	filetype type; // file type

	// OGG Vorbis specific
	OggVorbis_File vf; // ogg vorbis file

	// Needed for playback
	float rate; // sample rate (per channel) (Hz)
	u32 channels; // channel count
	u32 encoding; // data encoding (NDSP_ENCODING_*)
	u32 nsamples; // numbers of samples in the audio (per channel, not the total)
	u32 size; // number of bytes in the audio (total, ie data size)
	char* data; // raw audio data

	// Playing parameters (type-independant)
	float mix[12]; // mix parameters
	ndspInterpType interp; // interpolation type
	double speed; // playing speed
} audio_userdata;

// Indicate if NDSP was initialized or not.
// NDSP doesn't work on citra yet.
// Please only throw an error related to this when using a ndsp function, so other parts of the
// audio module are still available on citra, like audio.load() and audio:info().
bool isAudioInitialized = false;

// Array of the last audio_userdata sent to each channel; channels range from 0 to 23
audio_userdata* channels[24];

/***
Load an audio file.
OGG Vorbis and PCM WAV file format are currently supported.
(Most WAV files use the PCM encoding).
@function load
@tparam string path path to the file
@tparam[opt=detect] string type file type, `"ogg"` or `"wav"`.
                                If set to `"detect"`, will try to deduce the type from the filename.
@treturn[1] audio the loaded audio object
@treturn[2] nil if a error happened
@treturn[2] string error message
*/
static int audio_load(lua_State *L) {
	const char *path = luaL_checkstring(L, 1);
	const char* argType = luaL_optstring(L, 2, "detect");

	// Create userdata
	audio_userdata *audio = lua_newuserdata(L, sizeof(*audio));
	luaL_getmetatable(L, "LAudio");
	lua_setmetatable(L, -2);
	for (int i=0; i<12; i++) audio->mix[i] = 1;
	audio->interp = NDSP_INTERP_LINEAR;
	audio->speed = 1;

	// Get file type
	filetype type = TYPE_UNKNOWN;
	if (strcmp(argType, "detect") == 0) {
		const char *dot = strrchr(path, '.');
		if (!dot || dot == path) dot = "";
		const char *ext = dot + 1;
		if (strncmp(ext, "ogg", 3) == 0) type = TYPE_OGG;
		else if (strncmp(ext, "wav", 3) == 0) type = TYPE_WAV;
	} else if (strcmp(argType, "ogg") == 0) {
		type = TYPE_OGG;
	} else if (strcmp(argType, "wav") == 0) {
		type = TYPE_WAV;
	}

	// Open and read file
	if (type == TYPE_OGG) {
		audio->type = TYPE_OGG;

		// Load audio file
		if (ov_fopen(path, &audio->vf) < 0) {
			lua_pushnil(L);
			lua_pushstring(L, "input does not appear to be a valid ogg vorbis file or doesn't exist");
			return 2;
		}

		// Some Ogg Vorbis decoding parameters
		bool bigendianp = false; // bigendianness
		u8 word = 2; // word size; 1 or 2 (8bits/sample or 16bits/sample)
		bool sgned = true; // signed data

		// Decoding Ogg Vorbis bitstream
		vorbis_info* vi = ov_info(&audio->vf, -1);
		if (vi == NULL) luaL_error(L, "could not retrieve ogg audio stream informations");

		audio->rate = vi->rate;
		audio->channels = vi->channels;
		audio->encoding = NDSP_ENCODING_PCM16;
		audio->nsamples = ov_pcm_total(&audio->vf, -1);
		audio->size = audio->nsamples * word;

		if (linearSpaceFree() < audio->size) luaL_error(L, "not enough linear memory available");
		audio->data = linearAlloc(audio->size);

		// Decoding loop
		int offset = 0;
		int eof = 0;
		int current_section;
		while (!eof) {
			long ret = ov_read(&audio->vf, &audio->data[offset], 4096, bigendianp, word, sgned, &current_section);

			if (ret == 0) {
				eof = 1;
			} else if (ret < 0) {
				ov_clear(&audio->vf);
				linearFree(audio->data);
				luaL_error(L, "error in the ogg vorbis stream");
				return 0;
			} else {
				// TODO handle multiple links (http://xiph.org/vorbis/doc/vorbisfile/decoding.html)
				offset += ret;
			}
		}

		return 1;

	} else if (type == TYPE_WAV) {
		audio->type = TYPE_WAV;

		// Used this as a reference for the WAV format: http://www-mmsp.ece.mcgill.ca/Documents/AudioFormats/WAVE/WAVE.html

		// Load file
		FILE *file = fopen(path, "rb");
		if (file) {
			bool valid = true; // if something goes wrong, this will be false

			char buff[8];

			// Master chunk
			fread(buff, 4, 1, file); // ckId
			if (strncmp(buff, "RIFF", 4) != 0) valid = false;

			fseek(file, 4, SEEK_CUR); // skip ckSize

			fread(buff, 4, 1, file); // WAVEID
			if (strncmp(buff, "WAVE", 4) != 0) valid = false;

			// fmt Chunk
			fread(buff, 4, 1, file); // ckId
			if (strncmp(buff, "fmt ", 4) != 0) valid = false;

			fread(buff, 4, 1, file); // ckSize
			if (*buff != 16) valid = false; // should be 16 for PCM format

			fread(buff, 2, 1, file); // wFormatTag
			if (*buff != 0x0001) valid = false; // PCM format

			u16 channels;
			fread(&channels, 2, 1, file); // nChannels
			audio->channels = channels;
			
			u32 rate;
			fread(&rate, 4, 1, file); // nSamplesPerSec
			audio->rate = rate;

			fseek(file, 4, SEEK_CUR); // skip nAvgBytesPerSec

			u16 byte_per_block; // 1 block = 1*channelCount samples
			fread(&byte_per_block, 2, 1, file); // nBlockAlign

			u16 byte_per_sample;
			fread(&byte_per_sample, 2, 1, file); // wBitsPerSample
			byte_per_sample /= 8; // bits -> bytes

			// There may be some additionals chunks between fmt and data
			// TODO handle some usefull chunks that may be here
			fread(&buff, 4, 1, file); // ckId
			while (strncmp(buff, "data", 4) != 0) {
				u32 size;
				fread(&size, 4, 1, file); // ckSize

				fseek(file, size, SEEK_CUR); // skip chunk

				int i = fread(&buff, 4, 1, file); // next chunk ckId

				if (i < 4) { // reached EOF before finding a data chunk
					valid = false;
					break;
				}
			}

			// data Chunk (ckId already read)
			u32 size;
			fread(&size, 4, 1, file); // ckSize
			audio->size = size;

			audio->nsamples = audio->size / byte_per_block;

			if (byte_per_sample == 1) audio->encoding = NDSP_ENCODING_PCM8;
			else if (byte_per_sample == 2) audio->encoding = NDSP_ENCODING_PCM16;
			else luaL_error(L, "unknown encoding, needs to be PCM8 or PCM16");

			if (!valid) {
				fclose(file);
				luaL_error(L, "invalid PCM wav file");
				return 0;
			}

			// Read data
			if (linearSpaceFree() < audio->size) luaL_error(L, "not enough linear memory available");
			audio->data = linearAlloc(audio->size);

			fread(audio->data, audio->size, 1, file);

			fclose(file);
			return 1;

		} else {
			lua_pushnil(L);
			lua_pushfstring(L, "error while opening wav file: %s", strerror(errno));;
			return 2;
		}
	}

	luaL_error(L, "unknown audio type");
	return 0;
}

/***
Check if audio is currently playing on a channel.
@function playing
@tparam[opt] integer channel number; if `nil` will search the first channel playing an audio
@treturn boolean `true` if the channel is currently playing the audio, `false` otherwise.
                 If channel is not set, `false` means no audio is playing at all.
*/
static int audio_playing(lua_State *L) {
	if (!isAudioInitialized) {
		lua_pushboolean(L, false);
		return 1;
	}

	int channel = luaL_optinteger(L, 1, -1);
	if (channel < -1 || channel > 23) luaL_error(L, "channel number must be between 0 and 23");

	// Search a channel playing audio
	if (channel == -1) {
		for (int i = 0; i <= 23; i++) {
			if (ndspChnIsPlaying(i)) {
				lua_pushboolean(L, true);
				return 1;
			}
		}

		lua_pushboolean(L, false);
		return 1;

	} else {
		lua_pushboolean(L, ndspChnIsPlaying(channel));
		return 1;
	}

	return 0;
}

/***
Set the mix parameters (volumes) of a channel.
Volumes go from 0 (0%) to 1 (100%).
Note that when a new audio object will play on this channel, theses parameters will be
reset with the new audio object defaults (set in `audio:mix()`).
@function mix
@tparam[opt] integer channel the channel number, if `nil` will change the mix parmaters of all channels
@tparam[opt=1] number frontLeft front left volume
@tparam[opt=frontLeft] number frontRight front right volume
@tparam[opt=frontLeft] number backLeft back left volume
@tparam[opt=frontRight] number backRight back right volume
*/
static int audio_mix(lua_State *L) {
	if (!isAudioInitialized) luaL_error(L, "audio wasn't initialized correctly");

	int channel = luaL_optinteger(L, 1, -1);
	if (channel < -1 || channel > 23) luaL_error(L, "channel number must be between 0 and 23");

	float mix[12];
	mix[0] = luaL_optnumber(L, 2, 1);
	mix[1] = luaL_optnumber(L, 3, mix[0]);
	mix[2] = luaL_optnumber(L, 4, mix[0]);
	mix[3] = luaL_optnumber(L, 5, mix[2]);

	if (channel == -1) {
		for (int i=0; i<=23; i++) ndspChnSetMix(i, mix);
	} else {
		ndspChnSetMix(channel, mix);
	}

	return 0;
}

/***
Set the interpolation type of a channel.
Note that when a new audio object will play on this channel, this parameter will be
reset with the new audio object default (set in `audio:interpolation()`).
@function interpolation
@tparam[opt] integer channel stop playing audio on this channel; if `nil` will change interpolation type on all channels
@tparam[opt=linear] string "none", "linear" or "polyphase"
*/
static int audio_interpolation(lua_State *L) {
	if (!isAudioInitialized) luaL_error(L, "audio wasn't initialized correctly");

	int channel = luaL_optinteger(L, 1, -1);
	if (channel < -1 || channel > 23) luaL_error(L, "channel number must be between 0 and 23");

	const char* interpArg = luaL_optstring(L, 2, "linear");

	ndspInterpType interp;
	if (strcmp(interpArg, "none") == 0)
		interp = NDSP_INTERP_NONE;
	else if (strcmp(interpArg, "linear") == 0)
		interp = NDSP_INTERP_LINEAR;
	else if (strcmp(interpArg, "polyphase") == 0)
		interp = NDSP_INTERP_POLYPHASE;
	else {
		luaL_error(L, "unknown interpolation type");
		return 0;
	}

	if (channel == -1) {
		for (int i=0; i<=23; i++) ndspChnSetInterp(i, interp);
	} else {
		ndspChnSetInterp(channel, interp);
	}

	return 0;
}

/***
Set the speed of the audio playing in a channel.
Speed is expressed as a percentage of the normal playing speed.
1 is 100% speed and 2 is 200%, etc.
Note that when a new audio object will play on this channel, this parameter will be
reset with the new audio object default (set in `audio:speed()`).
@function speed
@tparam[opt] integer channel stop playing audio on this channel; if `nil` will change interpolation type on all channels
@tparam[opt=1] number speed percentage
*/
static int audio_speed(lua_State *L) {
	if (!isAudioInitialized) luaL_error(L, "audio wasn't initialized correctly");

	int channel = luaL_optinteger(L, 1, -1);
	if (channel < -1 || channel > 23) luaL_error(L, "channel number must be between 0 and 23");

	double speed = luaL_optnumber(L, 2, 1);

	if (channel == -1) {
		for (int i=0; i<=23; i++) {
			if (channels[i]) ndspChnSetRate(i, channels[i]->rate * speed);
		}
	} else {
		if (channels[channel]) ndspChnSetRate(channel, channels[channel]->rate * speed);
	}

	return 0;
}

/***
Stop playing all audio on all channels or a specific channel.
@function stop
@tparam[opt] integer channel stop playing audio on this channel; if `nil` will stop audio on all channels
@treturn integer number of channels where audio was stopped
*/
static int audio_stop(lua_State *L) {
	if (!isAudioInitialized) {
		lua_pushinteger(L, 0);
		return 1;
	}

	int channel = luaL_optinteger(L, 1, -1);

	int n = 0;

	if (channel == -1) {
		for (int i = 0; i <= 23; i++) {
			if (ndspChnIsPlaying(i)) {
				ndspChnWaveBufClear(i);
				n++;
			}
		}
	} else if (channel < 0 || channel > 23) {
		luaL_error(L, "channel number must be between 0 and 23");
	} else {
		if (ndspChnIsPlaying(channel)) {
			ndspChnWaveBufClear(channel);
			n++;
		}
	}
	
	lua_pushinteger(L, n);

	return 1;
}

/***
audio object
@section Methods
*/

/***
Returns the audio object duration.
@function :duration
@treturn number duration in seconds
*/
static int audio_object_duration(lua_State *L) {
	audio_userdata *audio = luaL_checkudata(L, 1, "LAudio");

	lua_pushnumber(L, (double)(audio->nsamples) / audio->rate);

	return 1;
}

/***
Returns the current playing position.
@function :time
@tparam[opt] integer channel number; if `nil` will use the first channel found which played this audio
@treturn number time in seconds
*/
static int audio_object_time(lua_State *L) {
	audio_userdata *audio = luaL_checkudata(L, 1, "LAudio");

	int channel = luaL_optinteger(L, 2, -1);
	if (channel < -1 || channel > 23) luaL_error(L, "channel number must be between 0 and 23");

	// Search a channel playing the audio object
	if (channel == -1) {
		for (int i = 0; i <= 23; i++) {
			if (channels[i] == audio) {
				channel = i;
				break;
			}
		}
	}

	if (channel == -1 || channels[channel] != audio || !isAudioInitialized) // audio not playing
		lua_pushnumber(L, 0);
	else
		lua_pushnumber(L, (double)(ndspChnGetSamplePos(channel)) / audio->rate);

	return 1;
}

/***
Check if the audio is currently playing.
@function :playing
@tparam[opt] integer channel channel number; if `nil` will search the first channel playing this audio
@treturn boolean true if the channel is currently playing the audio, false otherwise
*/
static int audio_object_playing(lua_State *L) {
	if (!isAudioInitialized) {
		lua_pushboolean(L, false);
		return 1;
	}

	audio_userdata *audio = luaL_checkudata(L, 1, "LAudio");

	int channel = luaL_optinteger(L, 2, -1);
	if (channel < -1 || channel > 23) luaL_error(L, "channel number must be between 0 and 23");

	// Search a channel playing the audio object
	if (channel == -1) {
		for (int i = 0; i <= 23; i++) {
			if (channels[i] == audio && ndspChnIsPlaying(i)) {
				lua_pushboolean(L, true);
				return 1;
			}
		}

		lua_pushboolean(L, false);
		return 1;

	} else {
		lua_pushboolean(L, channels[channel] == audio && ndspChnIsPlaying(channel));
		return 1;
	}

	return 0;
}

/***
Set the mix parameters (volumes) of the audio.
Volumes go from 0 (0%) to 1 (100%).
@function :mix
@tparam[opt=1] number frontLeft front left volume
@tparam[opt=frontLeft] number frontRight front right volume
@tparam[opt=frontLeft] number backLeft back left volume
@tparam[opt=frontRight] number backRight back right volume
*/
static int audio_object_mix(lua_State *L) {
	audio_userdata *audio = luaL_checkudata(L, 1, "LAudio");

	audio->mix[0] = luaL_optnumber(L, 2, 1);
	audio->mix[1] = luaL_optnumber(L, 3, audio->mix[0]);
	audio->mix[2] = luaL_optnumber(L, 4, audio->mix[0]);
	audio->mix[3] = luaL_optnumber(L, 5, audio->mix[2]);

	return 0;
}

/***
Set the interpolation type of the audio.
@function :interpolation
@tparam[opt=linear] string "none", "linear" or "polyphase"
*/
static int audio_object_interpolation(lua_State *L) {
	audio_userdata *audio = luaL_checkudata(L, 1, "LAudio");

	const char* interp = luaL_optstring(L, 2, "linear");

	if (strcmp(interp, "none") == 0)
		audio->interp = NDSP_INTERP_NONE;
	else if (strcmp(interp, "linear") == 0)
		audio->interp = NDSP_INTERP_LINEAR;
	else if (strcmp(interp, "polyphase") == 0)
		audio->interp = NDSP_INTERP_POLYPHASE;
	else {
		luaL_error(L, "unknown interpolation type");
		return 0;
	}

	return 0;
}

/***
Set the speed of the audio.
Speed is expressed as a percentage of the normal playing speed.
1 is 100% speed and 2 is 200%, etc.
@function :speed
@tparam[opt=1] number speed percentage
*/
static int audio_object_speed(lua_State *L) {
	audio_userdata *audio = luaL_checkudata(L, 1, "LAudio");

	audio->speed = luaL_optnumber(L, 2, 1);

	return 0;
}

/***
Plays the audio file.
@function :play
@tparam[opt=false] boolean loop if the audio should loop or not
@tparam[opt] integer channel the channel to play the audio on (0-23); if `nil` will use the first available channel.
                                If the channel was playing another audio, it will be stopped and replaced by this audio.
                                If not set and no channel is available, will return nil plus an error message.
@treturn[1] integer channel number the audio is playing on
@treturn[2] nil an error happened and the audio was not played
@treturn[2] error the error message
*/
static int audio_object_play(lua_State *L) {
	if (!isAudioInitialized) luaL_error(L, "audio wasn't initialized correctly");

	audio_userdata *audio = luaL_checkudata(L, 1, "LAudio");
	bool loop = lua_toboolean(L, 2);
	int channel = luaL_optinteger(L, 3, -1);

	// Find a free channel
	if (channel == -1) {
		for (int i = 0; i <= 23; i++) {
			if (!ndspChnIsPlaying(i)) {
				channel = i;
				break;
			}
		}
	}
	if (channel == -1) {
		lua_pushnil(L);
		lua_pushstring(L, "no audio channel is currently available");
		return 2;
	}
	if (channel < 0 || channel > 23) luaL_error(L, "channel number must be between 0 and 23");

	// Set channel parameters
	ndspChnWaveBufClear(channel);
	ndspChnReset(channel);
	ndspChnInitParams(channel);
	ndspChnSetMix(channel, audio->mix);
	ndspChnSetInterp(channel, audio->interp);
	ndspChnSetRate(channel, audio->rate * audio->speed); // maybe hackish way to set a different speed, but it works
	ndspChnSetFormat(channel, NDSP_CHANNELS(audio->channels) | NDSP_ENCODING(audio->encoding));

	// Send & play audio data
	ndspWaveBuf* waveBuf = calloc(1, sizeof(ndspWaveBuf));

	waveBuf->data_vaddr = audio->data;
	waveBuf->nsamples = audio->nsamples;
	waveBuf->looping = loop;
	
	DSP_FlushDataCache((u32*)audio->data, audio->size);
	
	ndspChnWaveBufAdd(channel, waveBuf);
	channels[channel] = audio;

	lua_pushinteger(L, channel);

	return 1;
}

/***
Stop playing an audio object.
@function :stop
@tparam[opt] integer channel stop playing the audio on this channel; if `nil` will stop all channels playing this audio.
                                  If the channel is playing another audio object, this function will do nothing.
@treturn integer number of channels where this audio was stopped
*/
static int audio_object_stop(lua_State *L) {
	if (!isAudioInitialized) {
		lua_pushinteger(L, 0);
		return 1;
	}

	audio_userdata *audio = luaL_checkudata(L, 1, "LAudio");
	int channel = luaL_optinteger(L, 2, -1);

	int n = 0;

	if (channel == -1) {
		for (int i = 0; i <= 23; i++) {
			if (channels[i] == audio && ndspChnIsPlaying(i)) {
				ndspChnWaveBufClear(i);
				n++;
			}
		}
	} else if (channel < 0 || channel > 23) {
		luaL_error(L, "channel number must be between 0 and 23");
	} else {
		if (channels[channel] == audio && ndspChnIsPlaying(channel)) {
			ndspChnWaveBufClear(channel);
			n++;
		}
	}
	
	lua_pushinteger(L, n);

	return 1;
}

/***
Returns the audio object type.
@function :type
@treturn string "ogg" or "wav"
*/
static int audio_object_type(lua_State *L) {
	audio_userdata *audio = luaL_checkudata(L, 1, "LAudio");
	
	if (audio->type == TYPE_OGG)
		lua_pushstring(L, "ogg");
	else if (audio->type == TYPE_WAV)
		lua_pushstring(L, "wav");

	return 1;
}

/***
Unload an audio object.
@function :unload
*/
static int audio_object_unload(lua_State *L) {
	audio_userdata *audio = luaL_checkudata(L, 1, "LAudio");

	// Stop playing the audio
	if (isAudioInitialized) {
		for (int i = 0; i <= 23; i++) {
			if (channels[i] == audio) {
				ndspChnWaveBufClear(i);
			}
		}
	}

	if (audio->type == TYPE_OGG) ov_clear(&audio->vf);

	// Free memory
	linearFree(audio->data);
	
	return 0;
}

/***
audio object (ogg-only).
Ogg Vorbis files specific methods.
Using one of theses methods will throw an error if used on an non-ogg audio object.
@section Methods
*/

/***
Returns basic information about the audio in a vorbis bitstream.
@function :info
@treturn infoTable information table
*/
static int audio_object_info(lua_State *L) {
	audio_userdata *audio = luaL_checkudata(L, 1, "LAudio");

	if (audio->type != TYPE_OGG) luaL_error(L, "only avaible on OGG audio objects");

	vorbis_info* vi = ov_info(&audio->vf, -1);
	if (vi == NULL) luaL_error(L, "could not retrieve audio stream informations");

	lua_createtable(L, 0, 6);

	lua_pushinteger(L, vi->version);
	lua_setfield(L, -2, "version");

	lua_pushinteger(L, vi->channels);
	lua_setfield(L, -2, "channels");

	lua_pushinteger(L, vi->rate);
	lua_setfield(L, -2, "rate");

	lua_pushinteger(L, vi->bitrate_upper);
	lua_setfield(L, -2, "bitrateUpper");

	lua_pushinteger(L, vi->bitrate_nominal);
	lua_setfield(L, -2, "bitrateNominal");

	lua_pushinteger(L, vi->bitrate_lower);
	lua_setfield(L, -2, "bitrateLower");

	return 1;
}

/***
Returns the Ogg Vorbis bitstream comment.
@function :comment
@treturn commentTable comment table
*/
static int audio_object_comment(lua_State *L) {
	audio_userdata *audio = luaL_checkudata(L, 1, "LAudio");

	if (audio->type != TYPE_OGG) luaL_error(L, "only avaible on OGG audio objects");

	vorbis_comment *vc = ov_comment(&audio->vf, -1);

	if (vc == NULL) luaL_error(L, "could not retrieve audio stream comment");

	lua_createtable(L, 0, 5);

	lua_newtable(L);
	for (int i=0; i<vc->comments; i++) {
		lua_pushstring(L, vc->user_comments[i]);
		lua_seti(L, -2, i+1);
	}
	lua_setfield(L, -2, "userComments");

	lua_pushstring(L, vc->vendor);
	lua_setfield(L, -2, "vendor");

	return 1;
}

/***
Tables return.
The detailled table structures returned by some methods of audio objects.
@section
*/

/***
Vorbis bitstream information, returned by audio:info().
If bitrateLower == bitrateNominal == bitrateUpper, the stream is fixed bitrate.
@table infoTable
@tfield integer version Vorbis encoder version used to create this bitstream
@tfield integer channels number of channels in bitstream
@tfield integer rate sampling rate of the bitstream
@tfield integer bitrateUpper the upper limit in a VBR bitstream; may be unset if no limit exists
@tfield integer bitrateNominal the average bitrate for a VBR bitstream; may be unset
@tfield integer bitrateLower the lower limit in a VBR bitstream; may be unset if no limit exists
*/

/***
Vorbis bitstream comment, returned by audio:comment().
@table commentTable
@tfield table userComments list of all the user comment
@tfield string vendor information about the Vorbis implementation that encoded the file
*/

// Audio object methods
static const struct luaL_Reg audio_object_methods[] = {
	// common
	{ "duration",      audio_object_duration      },
	{ "time",          audio_object_time          },
	{ "playing",       audio_object_playing       },
	{ "mix",           audio_object_mix           },
	{ "interpolation", audio_object_interpolation },
	{ "speed",         audio_object_speed         },
	{ "play",          audio_object_play          },
	{ "stop",          audio_object_stop          },
	{ "type",          audio_object_type          },
	{ "unload",        audio_object_unload        },
	{ "__gc",          audio_object_unload        },
	// ogg only
	{ "info",          audio_object_info          },
	{ "comment",       audio_object_comment       },
	{ NULL, NULL }
};

// Library functions
static const struct luaL_Reg audio_lib[] = {
	{ "load",          audio_load          },
	{ "playing",       audio_playing       },
	{ "mix",           audio_mix           },
	{ "interpolation", audio_interpolation },
	{ "speed",         audio_speed         },
	{ "stop",          audio_stop          },
	{ NULL, NULL }
};

int luaopen_audio_lib(lua_State *L) {
	luaL_newmetatable(L, "LAudio");
	lua_pushvalue(L, -1);
	lua_setfield(L, -2, "__index");
	luaL_setfuncs(L, audio_object_methods, 0);

	luaL_newlib(L, audio_lib);

	return 1;
}

void load_audio_lib(lua_State *L) {
	isAudioInitialized = !ndspInit(); // ndspInit returns 0 in case of success

	luaL_requiref(L, "ctr.audio", luaopen_audio_lib, false);
}

void unload_audio_lib(lua_State *L) {
	if (isAudioInitialized) ndspExit();
}
