#include <dos/dos.h>
#include <proto/dos.h>
#include <string.h>

#include "libacm.h"

#define ACM_HEADER_LEN	14
#define SEEK_SET OFFSET_BEGINNING

static int acm_read_file (void *ptr, int size, int n, void *arg);
static int acm_close_file (void *arg);
static int acm_seek_file (void *arg, int offset, int whence);
static int acm_tell_file (void *arg);

static const acm_io_callbacks acm_io = {
	acm_read_file,
	acm_seek_file,
	acm_close_file,
	acm_tell_file,
	NULL
};

int acm_open_file (ACMStream **res, const char *filename) {
	int err;
	BPTR file;
	ACMStream *acm;

	file = IDOS->Open(filename, MODE_OLDFILE);
	if (!file) return IDOS->IoErr();

	err = acm_open_decoder(&acm, (void *)file, acm_io);
	if (err) {
		IDOS->Close(file);
		return err;
	}

	*res = acm;
	return 0;
}

/*
 * utility functions
 */
static int pcm2time(ACMStream *acm, int pcm) {
	return ((10 * pcm) / acm->info.rate) * 100;
}

/*
 * info functions
 */

const ACMInfo *acm_info(ACMStream *acm) {
	return &acm->info;
}

int acm_seekable(ACMStream *acm) {
	return acm->data_len > 0;
}

int acm_bitrate(ACMStream *acm) {
	long secs, curpos, filelen, bitrate;
	const ACMInfo *info = acm_info(acm);

	if (acm_raw_total(acm) < 0)
		return 13000;

	secs = acm_pcm_total(acm) / info->rate;
	bitrate = acm_raw_total(acm) / secs * 8;
	return bitrate;
}

int acm_pcm_tell(ACMStream *acm) {
	return acm->stream_pos / acm->info.channels;
}

int acm_pcm_total(ACMStream *acm) {
	return acm->total_values / acm->info.channels;
}

int acm_time_tell(ACMStream *acm) {
	return pcm2time(acm, acm_pcm_tell(acm));
}

int acm_time_total(ACMStream *acm) {
	return pcm2time(acm, acm_pcm_total(acm));
}

int acm_raw_tell(ACMStream *acm) {
	if (acm->io.tell_func)
		return acm->io.tell_func(acm->io_arg);
	return -1;
}

int acm_raw_total(ACMStream *acm) {
	return acm->data_len;
}

/*
 * seeking
 */

int acm_seek_time(ACMStream *acm, int time_ms) {
	int pos_pcm = (time_ms / 100) * (acm->info.rate / 10);
	return acm_seek_pcm(acm, pos_pcm);
}

int acm_seek_pcm(ACMStream *acm, int pcm_pos) {
	int res, word_pos;

	word_pos = pcm_pos * acm->info.channels;

	if (word_pos < acm->stream_pos) {
		if (acm->io.seek_func == NULL)
			return -1;

		if (acm->io.seek_func(acm->io_arg,ACM_HEADER_LEN,SEEK_SET) < 0)
			return -1;
		
		acm->buf_pos = 0;
 		acm->buf_size = 0;
		acm->bit_avail = 0;
		acm->bit_data = 0;

		acm->stream_pos = 0;
		acm->block_pos = 0;
		acm->block_ready = 0;

		memset(acm->wrapbuf, 0, acm->wrapbuf_len * sizeof(int));
	}
	while (acm->stream_pos < word_pos) {
		int step = 2048;
		if (acm->stream_pos + step > word_pos)
			step = word_pos - acm->stream_pos;

		res = acm_read(acm, NULL, step*2, 0,2,1);
		if (res < 1)
			break;
	}
	return acm->stream_pos * acm->info.channels;
}

/*
 * read loop - full block reading
 */
int acm_read_loop(ACMStream *acm, char *dst, int bytes,
		int bigendianp, int samplelen, int sgned)
{
	int res, got = 0;
	while (bytes > 0) {
		res = acm_read(acm, dst, bytes, bigendianp, samplelen, sgned);
		if (res > 0) {
			dst += res;
			got += res;
			bytes -= res;
		} else {
			if (res < 0 && got == 0)
				return res;
			break;
		}
	}
	return got;
}

/*
 * io functions
 */

static int acm_read_file (void *ptr, int size, int n, void *arg) {
	BPTR file = (BPTR)arg;
	return IDOS->FRead(file, ptr, size, n);
}

static int acm_close_file (void *arg) {
	IDOS->Close((BPTR)arg);
	return 0;
}

static int acm_seek_file (void *arg, int offset, int whence) {
	if (IDOS->ChangeFilePosition((BPTR)arg, offset, whence) == -1) {
		return IDOS->IoErr();
	}
	return 0;
}

static int acm_tell_file (void *arg) {
	return IDOS->GetFilePosition((BPTR)arg);
}
