#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <vs1000.h>
#include <minifat.h>
#include <codec.h>
#include <player.h>

extern struct CodecServices cs;
extern u_int16 ogg[398];

auto u_int32 Swap32(register __c u_int32 n) {
  return ((n & 0xff00ff00UL)>>8) | ((n & 0x00ff00ffUL)<<8);
}
auto u_int16 Swap16(register __b0 u_int16 n) {
  return (n << 8) | (n >> 8);
}
#define MAKE_ID(c,d,a,b) (((u_int32)(a)<<24)|((u_int32)(b)<<16)|((c)<<8)|(d))
#define	WAVE_FORMAT_PCM		(0x0001)
/** Riff 'f','m','t',' ' TAG format. This is a little-endian format. */
struct WavRiffFormatTag {
  u_int16 formatTag;		/**< WAV subformat */
  u_int16 channels;		/**< Number of audio channels */
  u_int32 samplesPerSecond;	/**< Sample rate -- mixed-endian! */
  u_int32 avgBytesPerSec;	/**< Average data rate */
  u_int16 blockAlign;		/**< Block align if format is block oriented */
  u_int16 bitsPerSample;	/**< Number of bits per audio sample */
};
u_int16 RealPlayCurrentFile(void);
enum CodecError PlayWavOrOggFile(void) {
    u_int32 dataStart;
    u_int32 riffLeft, dat;
    __y s_int16 w;
    __y s_int16 cnt = 0;
    register u_int32 bytesLeft;

    LoadCheck(NULL, 0); /* higher clock, but 4.0x not absolutely required */
    cs.Read(&cs, (void *)&dat, 0, 4);
    if (dat != MAKE_ID('R','I','F','F')) {
	Seek(0);
	return RealPlayCurrentFile();
    }
    cs.playTimeSamples = cs.playTimeSeconds = 0;

    /* RIFF */
    cs.Read(&cs, (void *)&dat, 0, 4);
    riffLeft = Swap32(dat) -4;
    cs.Read(&cs, (void *)&dat, 0, 4);
    if (dat != MAKE_ID('W','A','V','E')) {
	return ceFormatNotSupported;
    }
    while (riffLeft >= 8 && !cs.cancel) {
	u_int32 name;
	__y u_int16 inBuf;

	cs.Read(&cs, (void *)&name, 0, 4);
	cs.Read(&cs, (void *)&dat, 0, 4);
	bytesLeft = Swap32(dat);
	if (name == MAKE_ID('d','a','t','a')) {
	    break;
	}
	inBuf = (bytesLeft > 16) ? 16 : bytesLeft;
	riffLeft -= 8 + bytesLeft;
	if (inBuf) {
	    cs.Read(&cs, ogg, 0, inBuf);
	    bytesLeft -= inBuf;
	}
	if (name == MAKE_ID('f','m','t',' ')) {
	    /* Is there enough minimum data? */
	    if (inBuf >= sizeof(struct WavRiffFormatTag)*(2/sizeof(u_int16))) {
		register struct WavRiffFormatTag *fmt =
		    (struct WavRiffFormatTag *)ogg;
		u_int16 fmtTag = Swap16(fmt->formatTag);
	
		cs.channels = Swap16(fmt->channels);
		cs.sampleRate = Swap16/*Swap32Mix*/(fmt->samplesPerSecond);
		/* cs.Output() handles sample rate */
		if (cs.channels > 2)
		    return ceFormatNotSupported;
#if 0
		puthex(cs.channels);
		puthex(cs.sampleRate);
		puts("chn,rate");
		puthex(Swap16(fmt->formatTag));
		puthex(Swap16(fmt->bitsPerSample));
		puts("fmt,bps");
#endif
		w = Swap16(fmt->bitsPerSample);
		if (fmtTag == WAVE_FORMAT_PCM ||
		    fmtTag == 0xfffe /*WAV with extended header*/) {
		} else {
		    return ceFormatNotSupported;
		}
	    }
	}
	cs.Seek(&cs, bytesLeft, SEEK_CUR);
    }
 data:
    dataStart = cs.Tell(&cs); /* for cs.goTo */
    while (bytesLeft) {
	int rd = (sizeof(ogg) & ~3); /* in bytes (half of the buffer) */
	if (w != 8)
	    rd <<= 1;
	if (rd > bytesLeft)
	    rd = bytesLeft;

	if (cs.cancel) {
	    cs.cancel = 0;
	    return ceCancelled;
	}
	rd = cs.Read(&cs, ogg, 0, rd);
	bytesLeft -= rd;
	if (!rd)
	    break;
#if 1 /* implement fast play mode -- not very good for high bitrates,
	 should play longer pieces or window the samples */
	if (cs.fastForward > 1) {
	    s_int32 seek = (cs.fastForward-1) * (s_int32)rd;
	    cs.Seek(&cs, seek, SEEK_CUR);
	    bytesLeft -= seek;
	}
#endif
	if (w == 8) {
	    /* 8-bit */
	    register s_int16 i = 0;
	    register u_int16 *wp = ogg + rd;
	    register u_int16 *rp = (ogg-1)+rd/2;
	    for (i=0; i<rd; i+=2) {
		register u_int16 t = *rp ^ 0x8080U;
		*--wp = (t << 8);
		*--wp = (t & 0xFF00U);
		rp--;
	    }
	} else {
	    /* 16-bit */
	    register int i;
	    register u_int16 *wp = ogg;
	    for (i=0; i<rd; i+=2) {
		*wp = (*wp << 8) | (*wp >> 8);
		wp++;
	    }
	    rd >>= 1;
	}
	if (cs.channels == 2)
	    rd >>= 1;
	if ((cs.playTimeSamples += rd) > cs.sampleRate) {
	    cs.playTimeSamples -= cs.sampleRate;
	    cs.playTimeSeconds++;
	}
	cs.Output(&cs, ogg, rd);
#if 1   /*implement goTo -- slow down the reaction artificially */
	if (cnt > 0) {
	    cnt -= rd;
	} else if (cs.goTo != 0xffffU) {
	    s_int32 pos = dataStart
		+ ((s_int32)cs.goTo * cs.sampleRate * cs.channels * w / 8);
	    bytesLeft -= cs.Tell(&cs) - pos;
	    cs.Seek(&cs, pos, SEEK_SET);
	    cs.playTimeSamples = 0;
	    cs.playTimeSeconds = cs.goTo;
	    cs.goTo = -1;
	    cnt = cs.sampleRate/4; /* next goTo allowed after 1/4 sec */
	}
#endif
    }
    return ceOk;
}


void main(void) {
    /* allow both .WAV and .OGG */
    supportedFiles = defSupportedFiles;
#if 0
    /* the following two lines are only needed and allowed for emulator */
    minifatInfo.supportedSuffixes = supportedFiles;
    player.totalFiles = OpenFile(0xffffU);
#endif
    /* Install the new file player function and return to player. */
    SetHookFunction((u_int16)PlayCurrentFile, PlayWavOrOggFile);
}
