/*
 * This file is part of the DXX-Rebirth project <https://www.dxx-rebirth.com/>.
 * It is copyright by its individual contributors, as recorded in the
 * project's Git history.  See COPYING.txt at the top level for license
 * terms and a link to the Git history.
 */
//#define DEBUG

#include "dxxsconf.h"
#include <vector>
#include <string.h>
#include <time.h>
#include <sys/time.h>
#ifdef _WIN32
# include <windows.h>
#else
# include <errno.h>
# include <fcntl.h>
# ifdef macintosh
#  include <types.h>
#  include <OSUtils.h>
# else
#  include <sys/types.h>
#  include <sys/stat.h>
#  include <unistd.h>
# endif // macintosh
#endif // _WIN32

#include <SDL.h>
#if DXX_USE_SDLMIXER
#include <SDL_mixer.h>
#endif

#include "config.h"
#include "mvelib.h"
#include "mve_audio.h"
#include "byteutil.h"
#include "decoders.h"
#include "libmve.h"
#include "args.h"
#include "console.h"
#include "u_mem.h"
#include <memory>

#define MVE_OPCODE_ENDOFSTREAM          0x00
#define MVE_OPCODE_ENDOFCHUNK           0x01
#define MVE_OPCODE_CREATETIMER          0x02
#define MVE_OPCODE_INITAUDIOBUFFERS     0x03
#define MVE_OPCODE_STARTSTOPAUDIO       0x04
#define MVE_OPCODE_INITVIDEOBUFFERS     0x05

#define MVE_OPCODE_DISPLAYVIDEO         0x07
#define MVE_OPCODE_AUDIOFRAMEDATA       0x08
#define MVE_OPCODE_AUDIOFRAMESILENCE    0x09
#define MVE_OPCODE_INITVIDEOMODE        0x0A

#define MVE_OPCODE_SETPALETTE           0x0C
#define MVE_OPCODE_SETPALETTECOMPRESSED 0x0D

#define MVE_OPCODE_SETDECODINGMAP       0x0F

#define MVE_OPCODE_VIDEODATA            0x11

#define MVE_AUDIO_FLAGS_STEREO     1
#define MVE_AUDIO_FLAGS_16BIT      2
#define MVE_AUDIO_FLAGS_COMPRESSED 4

int g_spdFactorNum=0;
static int g_spdFactorDenom=10;
static int g_frameUpdated = 0;

static int16_t get_short(const unsigned char *data)
{
	short value;
	value = data[0] | (data[1] << 8);
	return value;
}

static uint16_t get_ushort(const unsigned char *data)
{
	unsigned short value;
	value = data[0] | (data[1] << 8);
	return value;
}

static int32_t get_int(const unsigned char *data)
{
	int value;
	value = data[0] | (data[1] << 8) | (data[2] << 16) | (data[3] << 24);
	return value;
}

/*************************
 * general handlers
 *************************/
static int end_movie_handler(unsigned char, unsigned char, const unsigned char *, int, void *)
{
	return 0;
}

/*************************
 * timer handlers
 *************************/

/*
 * timer variables
 */
static int timer_created = 0;
static int micro_frame_delay=0;
static int timer_started=0;
static struct timeval timer_expire = {0, 0};

#ifndef DXX_HAVE_STRUCT_TIMESPEC
struct timespec
{
	long int tv_sec;            /* Seconds.  */
	long int tv_nsec;           /* Nanoseconds.  */
};
#endif

#if defined(_WIN32) || defined(macintosh)
int gettimeofday(struct timeval *tv, void *)
{
	static int counter = 0;
#ifdef _WIN32
	DWORD now = GetTickCount();
#else
	long now = TickCount();
#endif
	counter++;

	tv->tv_sec = now / 1000;
	tv->tv_usec = (now % 1000) * 1000 + counter;

	return 0;
}
#endif //  defined(_WIN32) || defined(macintosh)


static int create_timer_handler(unsigned char, unsigned char, const unsigned char *data, int, void *)
{

#if !defined(_WIN32) && !defined(macintosh) // FIXME
	__extension__ long long temp;
#else
	long temp;
#endif

	if (timer_created)
		return 1;
	else
		timer_created = 1;

	micro_frame_delay = get_int(data) * static_cast<int>(get_short(data+4));
	if (g_spdFactorNum != 0)
	{
		temp = micro_frame_delay;
		temp *= g_spdFactorNum;
		temp /= g_spdFactorDenom;
		micro_frame_delay = static_cast<int>(temp);
	}

	return 1;
}

static void timer_stop(void)
{
	timer_expire.tv_sec = 0;
	timer_expire.tv_usec = 0;
	timer_started = 0;
}

static void timer_start(void)
{
	int nsec=0;
	gettimeofday(&timer_expire, NULL);
	timer_expire.tv_usec += micro_frame_delay;
	if (timer_expire.tv_usec > 1000000)
	{
		nsec = timer_expire.tv_usec / 1000000;
		timer_expire.tv_sec += nsec;
		timer_expire.tv_usec -= nsec*1000000;
	}
	timer_started=1;
}

static void do_timer_wait(void)
{
	int nsec=0;
	struct timespec ts;
	struct timeval tv;
	if (! timer_started)
		return;

	gettimeofday(&tv, NULL);
	if (tv.tv_sec > timer_expire.tv_sec)
		goto end;
	else if (tv.tv_sec == timer_expire.tv_sec  &&  tv.tv_usec >= timer_expire.tv_usec)
		goto end;

	ts.tv_sec = timer_expire.tv_sec - tv.tv_sec;
	ts.tv_nsec = 1000 * (timer_expire.tv_usec - tv.tv_usec);
	if (ts.tv_nsec < 0)
	{
		ts.tv_nsec += 1000000000UL;
		--ts.tv_sec;
	}
#ifdef _WIN32
	Sleep(ts.tv_sec * 1000 + ts.tv_nsec / 1000000);
#elif defined(macintosh)
	Delay(ts.tv_sec * 1000 + ts.tv_nsec / 1000000, NULL);
#else
	if (nanosleep(&ts, NULL) == -1  &&  errno == EINTR)
		exit(1);
#endif

 end:
	timer_expire.tv_usec += micro_frame_delay;
	if (timer_expire.tv_usec > 1000000)
	{
		nsec = timer_expire.tv_usec / 1000000;
		timer_expire.tv_sec += nsec;
		timer_expire.tv_usec -= nsec*1000000;
	}
}

/*************************
 * audio handlers
 *************************/
#define TOTAL_AUDIO_BUFFERS 64

namespace {

class MVE_audio_deleter
{
public:
	void operator()(int16_t *p) const
	{
		MovieMemoryFree(p);
	}
};

template <typename T>
struct MVE_audio_clamp
{
	const unsigned scale;
	MVE_audio_clamp(const unsigned DigiVolume) :
		scale(DigiVolume)
	{
	}
	T operator()(const T &i) const
	{
		return (static_cast<int32_t>(i) * scale) / 8;
	}
};

}

static int audiobuf_created = 0;
static void mve_audio_callback(void *userdata, unsigned char *stream, int len);
static std::array<std::unique_ptr<short[], MVE_audio_deleter>, TOTAL_AUDIO_BUFFERS> mve_audio_buffers;
static std::array<unsigned, TOTAL_AUDIO_BUFFERS> mve_audio_buflens;
static int    mve_audio_curbuf_curpos=0;
static int mve_audio_bufhead=0;
static int mve_audio_buftail=0;
static int mve_audio_playing=0;
static int mve_audio_canplay=0;
static unsigned mve_audio_flags;
static int mve_audio_enabled = 1;
static std::unique_ptr<SDL_AudioSpec> mve_audio_spec;

static void mve_audio_callback(void *, unsigned char *stream, int len)
{
#ifdef DXX_REPORT_TOTAL_LENGTH
	int total=0;
#endif
	int length;
	if (mve_audio_bufhead == mve_audio_buftail)
		return /* 0 */;

	//con_printf(CON_CRITICAL, "+ <%d (%d), %d, %d>", mve_audio_bufhead, mve_audio_curbuf_curpos, mve_audio_buftail, len);

	while (mve_audio_bufhead != mve_audio_buftail                                           /* while we have more buffers  */
		   &&  len > (mve_audio_buflens[mve_audio_bufhead]-mve_audio_curbuf_curpos))        /* and while we need more data */
	{
		length = mve_audio_buflens[mve_audio_bufhead]-mve_audio_curbuf_curpos;
		memcpy(stream,                                                                  /* cur output position */
		       (reinterpret_cast<uint8_t *>(mve_audio_buffers[mve_audio_bufhead].get()))+mve_audio_curbuf_curpos,           /* cur input position  */
		       length);                                                                 /* cur input length    */

#ifdef DXX_REPORT_TOTAL_LENGTH
		total += length;
#endif
		stream += length;                                                               /* advance output */
		len -= length;                                                                  /* decrement avail ospace */
		mve_audio_buffers[mve_audio_bufhead].reset();                                 /* free the buffer */
		mve_audio_buflens[mve_audio_bufhead]=0;                                         /* free the buffer */

		if (++mve_audio_bufhead == TOTAL_AUDIO_BUFFERS)                                 /* next buffer */
			mve_audio_bufhead = 0;
		mve_audio_curbuf_curpos = 0;
	}

#ifdef DXX_REPORT_TOTAL_LENGTH
	//con_printf(CON_CRITICAL, "= <%d (%d), %d, %d>: %d", mve_audio_bufhead, mve_audio_curbuf_curpos, mve_audio_buftail, len, total);
#endif
	/*    return total; */

	if (len != 0                                                                        /* ospace remaining  */
		&&  mve_audio_bufhead != mve_audio_buftail)                                     /* buffers remaining */
	{
		memcpy(stream,                                                                  /* dest */
		       (reinterpret_cast<uint8_t *>(mve_audio_buffers[mve_audio_bufhead].get()))+mve_audio_curbuf_curpos,         /* src */
			   len);                                                                    /* length */

		mve_audio_curbuf_curpos += len;                                                 /* advance input */
		stream += len;                                                                  /* advance output (unnecessary) */
		len -= len;                                                                     /* advance output (unnecessary) */

		if (mve_audio_curbuf_curpos >= mve_audio_buflens[mve_audio_bufhead])            /* if this ends the current chunk */
		{
			mve_audio_buffers[mve_audio_bufhead].reset();                             /* free buffer */
			mve_audio_buflens[mve_audio_bufhead]=0;

			if (++mve_audio_bufhead == TOTAL_AUDIO_BUFFERS)                             /* next buffer */
				mve_audio_bufhead = 0;
			mve_audio_curbuf_curpos = 0;
		}
	}

	//con_printf(CON_CRITICAL, "- <%d (%d), %d, %d>", mve_audio_bufhead, mve_audio_curbuf_curpos, mve_audio_buftail, len);
}

static int create_audiobuf_handler(unsigned char, unsigned char minor, const unsigned char *data, int, void *)
{
	int flags;
	int sample_rate;
	int desired_buffer;

	if (!mve_audio_enabled)
		return 1;

	if (audiobuf_created)
		return 1;
	else
		audiobuf_created = 1;

	flags = get_ushort(data + 2);
	sample_rate = get_ushort(data + 4);
	desired_buffer = get_int(data + 6);

	const unsigned stereo = (flags & MVE_AUDIO_FLAGS_STEREO);
	const unsigned bitsize = (flags & MVE_AUDIO_FLAGS_16BIT);
	if (!minor)
		flags &= ~MVE_AUDIO_FLAGS_COMPRESSED;
	const unsigned compressed = flags & MVE_AUDIO_FLAGS_COMPRESSED;
	mve_audio_flags = flags;

	const unsigned format = (bitsize)
		? (words_bigendian ? AUDIO_S16MSB : AUDIO_S16LSB)
		: AUDIO_U8;

	if (CGameArg.SndDisableSdlMixer)
	{
		con_puts(CON_CRITICAL, "creating audio buffers:");
		con_printf(CON_CRITICAL, "sample rate = %d, desired buffer = %d, stereo = %d, bitsize = %d, compressed = %d",
				sample_rate, desired_buffer, stereo ? 1 : 0, bitsize ? 16 : 8, compressed ? 1 : 0);
	}

	mve_audio_spec = std::make_unique<SDL_AudioSpec>();
	mve_audio_spec->freq = sample_rate;
	mve_audio_spec->format = format;
	mve_audio_spec->channels = (stereo) ? 2 : 1;
	mve_audio_spec->samples = 4096;
	mve_audio_spec->callback = mve_audio_callback;
	mve_audio_spec->userdata = NULL;

	// MD2211: if using SDL_Mixer, we never reinit the sound system
	if (CGameArg.SndDisableSdlMixer)
	{
		if (SDL_OpenAudio(mve_audio_spec.get(), NULL) >= 0) {
			con_puts(CON_CRITICAL, "   success");
			mve_audio_canplay = 1;
		}
		else {
			con_printf(CON_CRITICAL, "   failure : %s", SDL_GetError());
			mve_audio_canplay = 0;
		}
	}

#if DXX_USE_SDLMIXER
	else {
		// MD2211: using the same old SDL audio callback as a postmixer in SDL_mixer
		Mix_SetPostMix(mve_audio_spec->callback, mve_audio_spec->userdata);
		mve_audio_canplay = 1;
	}
#endif

	mve_audio_buffers = {};
	mve_audio_buflens = {};
	return 1;
}

static int play_audio_handler(unsigned char, unsigned char, const unsigned char *, int, void *)
{
	if (mve_audio_canplay  &&  !mve_audio_playing  &&  mve_audio_bufhead != mve_audio_buftail)
	{
		if (CGameArg.SndDisableSdlMixer)
			SDL_PauseAudio(0);
#if DXX_USE_SDLMIXER
		else
			Mix_Pause(0);
#endif
		mve_audio_playing = 1;
	}
	return 1;
}

static int audio_data_handler(unsigned char major, unsigned char, const unsigned char *data, int, void *)
{
	static const int selected_chan=1;
	int chan;
	int nsamp;
	if (mve_audio_canplay)
	{
		if (mve_audio_playing)
			SDL_LockAudio();

		chan = get_ushort(data + 2);
		nsamp = get_ushort(data + 4);
		if (chan & selected_chan)
		{
			std::unique_ptr<int16_t[], MVE_audio_deleter> p;
			const auto DigiVolume = GameCfg.DigiVolume;
			/* HACK: +4 mveaudio_uncompress adds 4 more bytes */
			/* At volume 0 (minimum), no sound is wanted. */
			if (DigiVolume && major == MVE_OPCODE_AUDIOFRAMEDATA) {
				const auto flags = mve_audio_flags;
				if (flags & MVE_AUDIO_FLAGS_COMPRESSED) {
					nsamp += 4;

					p.reset(reinterpret_cast<int16_t *>(MovieMemoryAllocate(nsamp)));
					mveaudio_uncompress(p.get(), data); /* XXX */
				} else {
					nsamp -= 8;
					data += 8;

					p.reset(reinterpret_cast<int16_t *>(MovieMemoryAllocate(nsamp)));
					memcpy(p.get(), data, nsamp);
				}
				if (DigiVolume < 8)
				{
					/* At volume 8 (maximum), no scaling is needed. */
					if (flags & MVE_AUDIO_FLAGS_16BIT)
					{
						int16_t *const p16 = p.get();
						std::transform(p16, reinterpret_cast<int16_t *>(reinterpret_cast<uint8_t *>(p16) + nsamp), p16, MVE_audio_clamp<int16_t>(DigiVolume));
					}
					else
					{
						int8_t *const p8 = reinterpret_cast<int8_t *>(p.get());
						std::transform(p8, p8 + nsamp, p8, MVE_audio_clamp<int8_t>(DigiVolume));
					}
				}
			} else {
				p.reset(reinterpret_cast<int16_t *>(MovieMemoryAllocate(nsamp)));
				memset(p.get(), 0, nsamp); /* XXX */
			}
			unsigned buflen = nsamp;

			// MD2211: the following block does on-the-fly audio conversion for SDL_mixer
#if DXX_USE_SDLMIXER
			if (!CGameArg.SndDisableSdlMixer) {
				// build converter: in = MVE format, out = SDL_mixer output
				int out_freq;
				Uint16 out_format;
				int out_channels;
				Mix_QuerySpec(&out_freq, &out_format, &out_channels); // get current output settings

				SDL_AudioCVT cvt{};
				SDL_BuildAudioCVT(&cvt, mve_audio_spec->format, mve_audio_spec->channels, mve_audio_spec->freq,
					out_format, out_channels, out_freq);

				const auto cvtbuf = std::make_unique<uint8_t[]>(nsamp * cvt.len_mult);
				cvt.buf = cvtbuf.get();
				cvt.len = nsamp;

				// read the audio buffer into the conversion buffer
				memcpy(cvt.buf, p.get(), nsamp);

				// do the conversion
				if (SDL_ConvertAudio(&cvt))
					con_printf(CON_URGENT, "%s:%u: SDL_ConvertAudio failed: nsamp=%u out_format=%i out_channels=%i out_freq=%i", __FILE__, __LINE__, nsamp, out_format, out_channels, out_freq);
				else
				{
				// copy back to the audio buffer
					const std::size_t converted_buffer_size = cvt.len_cvt;
					p.reset(reinterpret_cast<int16_t *>(MovieMemoryAllocate(converted_buffer_size))); // free the old audio buffer
					buflen = converted_buffer_size;
					memcpy(p.get(), cvt.buf, converted_buffer_size);
				}
			}
#endif
			mve_audio_buffers[mve_audio_buftail] = std::move(p);
			mve_audio_buflens[mve_audio_buftail] = buflen;

			if (++mve_audio_buftail == TOTAL_AUDIO_BUFFERS)
				mve_audio_buftail = 0;

			if (mve_audio_buftail == mve_audio_bufhead)
				con_printf(CON_CRITICAL, "d'oh!  buffer ring overrun (%d)", mve_audio_bufhead);
		}

		if (mve_audio_playing)
			SDL_UnlockAudio();
	}

	return 1;
}

/*************************
 * video handlers
 *************************/

static int videobuf_created = 0;
static int video_initialized = 0;
int g_width, g_height;
static std::vector<unsigned char> g_vBuffers;
unsigned char *g_vBackBuf1, *g_vBackBuf2;

static int g_destX, g_destY;
static int g_screenWidth, g_screenHeight;
static const unsigned char *g_pCurMap;
static int g_nMapLength=0;
static int g_truecolor;

static int create_videobuf_handler(unsigned char, unsigned char minor, const unsigned char *data, int, void *)
{
	short w, h,
#ifdef DEBUG
		count, 
#endif
		truecolor;

	if (videobuf_created)
		return 1;
	else
		videobuf_created = 1;

	w = get_short(data);
	h = get_short(data+2);

#ifdef DEBUG
	if (minor > 0) {
		count = get_short(data+4);
	} else {
		count = 1;
	}
#endif

	if (minor > 1) {
		truecolor = get_short(data+6);
	} else {
		truecolor = 0;
	}

	g_width = w << 3;
	g_height = h << 3;

	/* TODO: * 4 causes crashes on some files */
	/* only malloc once */
	g_vBuffers.assign(g_width * g_height * 8, 0);
	g_vBackBuf1 = &g_vBuffers[0];
	if (truecolor) {
		g_vBackBuf2 = reinterpret_cast<uint8_t *>(reinterpret_cast<uint16_t *>(g_vBackBuf1) + (g_width * g_height));
	} else {
		g_vBackBuf2 = (g_vBackBuf1 + (g_width * g_height));
	}
#ifdef DEBUG
	con_printf(CON_CRITICAL, "DEBUG: w,h=%d,%d count=%d, tc=%d", w, h, count, truecolor);
#endif

	g_truecolor = truecolor;

	return 1;
}

static int display_video_handler(unsigned char, unsigned char, const unsigned char *, int, void *)
{
	MovieShowFrame(g_vBackBuf1, g_destX, g_destY, g_width, g_height, g_screenWidth, g_screenHeight);

	g_frameUpdated = 1;

	return 1;
}

static int init_video_handler(unsigned char, unsigned char, const unsigned char *data, int, void *)
{
	short width, height;

	if (video_initialized)
		return 1; /* maybe we actually need to change width/height here? */
	else
		video_initialized = 1;

	width = get_short(data);
	height = get_short(data+2);
	g_screenWidth = width;
	g_screenHeight = height;

	return 1;
}

static int video_palette_handler(unsigned char, unsigned char, const unsigned char *data, int, void *)
{
	short start, count;
	start = get_short(data);
	count = get_short(data+2);

	auto p = data + 4;
	MovieSetPalette(p - 3*start, start, count);
	return 1;
}

static int video_codemap_handler(unsigned char, unsigned char, const unsigned char *data, int len, void *)
{
	g_pCurMap = data;
	g_nMapLength = len;
	return 1;
}

static int video_data_handler(unsigned char, unsigned char, const unsigned char *data, int len, void *)
{
	unsigned short nFlags;

// don't need those but kept for further reference
// 	nFrameHot  = get_short(data);
// 	nFrameCold = get_short(data+2);
// 	nXoffset   = get_short(data+4);
// 	nYoffset   = get_short(data+6);
// 	nXsize     = get_short(data+8);
// 	nYsize     = get_short(data+10);
	nFlags     = get_ushort(data+12);

	if (nFlags & 1)
	{
		std::swap(g_vBackBuf1, g_vBackBuf2);
	}

	/* convert the frame */
	if (g_truecolor) {
		decodeFrame16(g_vBackBuf1, g_pCurMap, g_nMapLength, data+14, len-14);
	} else {
		decodeFrame8(g_vBackBuf1, g_pCurMap, g_nMapLength, data+14, len-14);
	}

	return 1;
}

static int end_chunk_handler(unsigned char, unsigned char, const unsigned char *, int, void *)
{
	g_pCurMap=NULL;
	return 1;
}

int MVE_rmPrepMovie(MVESTREAM_ptr_t &pMovie, MVEFILE::stream_type *const src, int x, int y, int)
{
	if (pMovie) {
		mve_reset(pMovie.get());
		return 0;
	}

	pMovie = mve_open(src);

	if (!pMovie)
		return 1;

	g_destX = x;
	g_destY = y;

	auto &mve = *pMovie.get();
	mve_set_handler(mve, MVE_OPCODE_ENDOFSTREAM,          end_movie_handler);
	mve_set_handler(mve, MVE_OPCODE_ENDOFCHUNK,           end_chunk_handler);
	mve_set_handler(mve, MVE_OPCODE_CREATETIMER,          create_timer_handler);
	mve_set_handler(mve, MVE_OPCODE_INITAUDIOBUFFERS,     create_audiobuf_handler);
	mve_set_handler(mve, MVE_OPCODE_STARTSTOPAUDIO,       play_audio_handler);
	mve_set_handler(mve, MVE_OPCODE_INITVIDEOBUFFERS,     create_videobuf_handler);

	mve_set_handler(mve, MVE_OPCODE_DISPLAYVIDEO,         display_video_handler);
	mve_set_handler(mve, MVE_OPCODE_AUDIOFRAMEDATA,       audio_data_handler);
	mve_set_handler(mve, MVE_OPCODE_AUDIOFRAMESILENCE,    audio_data_handler);
	mve_set_handler(mve, MVE_OPCODE_INITVIDEOMODE,        init_video_handler);

	mve_set_handler(mve, MVE_OPCODE_SETPALETTE,           video_palette_handler);
	mve_set_handler(mve, MVE_OPCODE_SETDECODINGMAP,       video_codemap_handler);

	mve_set_handler(mve, MVE_OPCODE_VIDEODATA,            video_data_handler);

	mve_play_next_chunk(mve); /* video initialization chunk */
	mve_play_next_chunk(mve); /* audio initialization chunk */

	return 0;
}


void MVE_getVideoSpec(MVE_videoSpec *vSpec)
{
	vSpec->screenWidth = g_screenWidth;
	vSpec->screenHeight = g_screenHeight;
	vSpec->width = g_width;
	vSpec->height = g_height;
	vSpec->truecolor = g_truecolor;
}


MVE_StepStatus MVE_rmStepMovie(MVESTREAM &mve)
{
	static int init_timer=0;
	int cont=1;

	if (!timer_started)
		timer_start();

	while (cont && !g_frameUpdated) // make a "step" be a frame, not a chunk...
		cont = mve_play_next_chunk(mve);
	g_frameUpdated = 0;

	if (!cont)
		return MVE_StepStatus::EndOfFile;

	if (micro_frame_delay  && !init_timer) {
		timer_start();
		init_timer = 1;
	}

	do_timer_wait();

	return MVE_StepStatus::Continue;
}

void MVE_rmEndMovie(std::unique_ptr<MVESTREAM>)
{
	timer_stop();
	timer_created = 0;

	if (mve_audio_canplay) {
		// MD2211: if using SDL_Mixer, we never reinit sound, hence never close it
		if (CGameArg.SndDisableSdlMixer)
		{
			SDL_CloseAudio();
		}
		mve_audio_canplay = 0;
	}
	mve_audio_buffers = {};
	mve_audio_buflens = {};

	mve_audio_curbuf_curpos=0;
	mve_audio_bufhead=0;
	mve_audio_buftail=0;
	mve_audio_playing=0;
	mve_audio_canplay=0;
	mve_audio_flags = 0;

	mve_audio_spec.reset();
	audiobuf_created = 0;
	g_vBuffers.clear();
	g_pCurMap=NULL;
	g_nMapLength=0;
	videobuf_created = 0;
	video_initialized = 0;
}


void MVE_rmHoldMovie()
{
	timer_started = 0;
}


void MVE_sndInit(int x)
{
	mve_audio_enabled = (x == -1 ? 0 : 1);
}
