#include "DecodeThread.h"
#include "giofile.h"
#include "main.h"
#include "pdtimer.h"
#include "mpegutil.h"
#include "../Winamp/wa_ipc.h"
#include "config.h"
#include <shlwapi.h>
#include "adts.h"
#include "adts_vlb.h"
#include <foundation/error.h>

// {19450308-90D7-4E45-8A9D-DC71E67123E2}
static const GUID adts_aac_guid = 
{ 0x19450308, 0x90d7, 0x4e45, { 0x8a, 0x9d, 0xdc, 0x71, 0xe6, 0x71, 0x23, 0xe2 } };

// {4192FE3F-E843-445c-8D62-51BE5EE5E68C}
static const GUID adts_mp2_guid = 
{ 0x4192fe3f, 0xe843, 0x445c, { 0x8d, 0x62, 0x51, 0xbe, 0x5e, 0xe5, 0xe6, 0x8c } };

extern int m_is_stream;
extern bool m_is_stream_seekable;

// post this to the main window at end of file (after playback as stopped)
#define WM_WA_MPEG_EOF WM_USER+2

/* public data */
int last_decode_pos_ms;
int decode_pos_ms; // current decoding position, in milliseconds.
volatile int seek_needed; // if != -1, it is the point that the decode
// thread should seek to, in ms.
int g_ds;

size_t g_bits;
int g_sndopened;
int g_bufferstat;
int g_length = -1000;
int g_vis_enabled;
volatile int g_closeaudio = 0;

CGioFile *g_playing_file=0;
/* private data */
static size_t g_samplebuf_used;
static int need_prebuffer;
static int g_srate, g_nch, g_br_add, g_br_div, g_avg_vbr_br;
int g_br;

class EndCutter
{
public:
	EndCutter() : buffer(0), cutSize(0), filledSize(0), preCutSize(0), preCut(0), decoderDelay(0)
	{}
	~EndCutter()
	{
		free(buffer);
	}
	void SetEndSize(int postSize)
	{
		postSize -= decoderDelay;
		if (postSize < 0)
			postSize = 0;
		else if (postSize)
		{
			free(buffer);
			buffer = (char *)calloc(postSize, sizeof(char));
			cutSize = postSize;
		}
	}

	void SetSize(int decoderDelaySize, int preSize, int postSize)
	{
		decoderDelay = decoderDelaySize;
		SetEndSize(postSize);

		preCutSize = preSize;
		preCut = preCutSize + decoderDelay;
	}

	void Flush(int time_in_ms)
	{
		if (time_in_ms == 0) // TODO: calculate actual delay if we seek within the encoder delay area
			preCut = preCutSize; // reset precut size if we seek to the start

		filledSize = 0;
		mod.outMod->Flush(time_in_ms);
	}

	void Write(char *out, int outSize)
	{
		if (!out && (!outSize))
		{
			mod.outMod->Write(0, 0);
			return ;
		}

		// cut pre samples, if necessary
		int pre = min(preCut, outSize);
		out += pre;
		outSize -= pre;
		preCut -= pre;

		if (!outSize)
			return ;

		int remainingFill = cutSize - filledSize;
		int fillWrite = min(outSize - remainingFill, filledSize); // only write fill buffer if we've got enough left to fill it up

		if (fillWrite > 0)
		{
			mod.outMod->Write((char *)buffer, fillWrite);
			if (cutSize - fillWrite)
				memmove(buffer, buffer + fillWrite, cutSize - fillWrite);
			filledSize -= fillWrite;

		}
		remainingFill = cutSize - filledSize;
		int outWrite = max(0, outSize - remainingFill);
		if (outWrite)
			mod.outMod->Write((char *)out, outWrite);
		out += outWrite;
		outSize -= outWrite;

		if (outSize)
		{
			memcpy(buffer + filledSize, out, outSize);
			filledSize += outSize;
		}


	}
	char *buffer;
	int cutSize;
	int filledSize;
	int preCut, preCutSize, decoderDelay;
};

class DecodeLoop
{
public:
	DecodeLoop() : decoder(0)
	{
		isAac = 0;
		isEAAC = 0;

		last_bpos = -1;
		need_synclight = true;
		done = 0;
		br = 0;

		g_framesize = 0;
		maxlatency = 0;
		sampleFrameSize = 0;
		memset(&g_samplebuf, 0, sizeof(g_samplebuf));
	}

	~DecodeLoop()
	{
		if (decoder)
		{
			decoder->Close();
			decoder->Release();
		}
		decoder=0;

	}

	DWORD Loop();
	DWORD OpenDecoder();
	void Seek(int seekPosition);
	void PreBuffer();
	void Decode();
	void Viz();
	void CalculateCodecDelay();
	DWORD OpenOutput(int numChannels, int sampleRate, int bitsPerSample);
	void SetupStream();

	BYTE g_samplebuf[6*3*2*2*1152];

	int g_framesize;
	int isAac;
	int isEAAC;

	CGioFile file;

	int maxlatency;
	int last_bpos;
	bool need_synclight;
	int done; // set to TRUE if decoding has finished, 2 if all has been written
	size_t br;

	EndCutter endCutter;
	int sampleFrameSize;
	adts *decoder;
};

static int CalcPreBuffer(int buffer_setting, int bitrate)
{
	if (bitrate < 8) 
		bitrate = 8;
	else if (bitrate > 320) 
		bitrate = 320;
	int prebuffer = (buffer_setting * bitrate) / 128;
	if (prebuffer > 100)
		prebuffer=100;
	return prebuffer;
}

void DecodeLoop::SetupStream()
{
	char buf[1024] = {0};
	int len;

	m_is_stream = file.IsStream();

	//Wait until we have data...
	while (!killDecodeThread && file.Peek(buf, 1024, &len) == NErr_Success && !len)
		Sleep(50);

	m_is_stream_seekable = file.IsStreamSeekable();
	char *content_type = file.m_content_type;
	if (content_type)
	{
		if (!_strnicmp(content_type, "misc/ultravox", 13))
		{
			switch (file.uvox_last_message)
			{
			case 0x8001:
			case 0x8003:
				isEAAC = 1;
				isAac = 1;
				break;

			case 0x8000:
				isAac = 1;
				break;
			}
		}
		else if (!_strnicmp(content_type, "audio/aac", 9))
		{
			isEAAC = 1;
			isAac = 1;
		}
		else if (!_strnicmp(content_type, "audio/aacp", 10))
		{
			isEAAC = 1;
			isAac = 1;
		}
		else if (!_strnicmp(content_type, "audio/apl", 10))
		{
			isEAAC = 1;
			isAac = 1;
		}
	}

	// todo: poll until connected to see if we get aac uvox frames or a content-type:aac header
}

DWORD DecodeLoop::OpenOutput(int numChannels, int sampleRate, int bitsPerSample)
{
	maxlatency = mod.outMod->Open(sampleRate, numChannels, bitsPerSample, -1, -1);

	// maxlatency is the maxium latency between a outMod->Write() call and
	// when you hear those samples. In ms. Used primarily by the visualization
	// system.

	if (maxlatency < 0) // error opening device
	{
		PostMessage(mod.hMainWindow, WM_COMMAND, 40047, 0);
		return 0;
	}
	g_sndopened = 1;
	if (maxlatency == 0 && file.IsStream() == 2) // can't use with disk writer
	{
		if (!killDecodeThread)
		{
			EnterCriticalSection(&g_lfnscs);
			WASABI_API_LNGSTRING_BUF(IDS_CANNOT_WRITE_STREAMS_TO_DISK,lastfn_status,256);
			LeaveCriticalSection(&g_lfnscs);
			PostMessage(mod.hMainWindow, WM_USER, 0, IPC_UPDTITLE);
		}
		if (!killDecodeThread) Sleep(200);
		if (!killDecodeThread) PostMessage(mod.hMainWindow, WM_WA_MPEG_EOF, 0, 0);
		g_bufferstat = 0;
		g_closeaudio = 1;

		return 0;
	}

	if (paused) mod.outMod->Pause(1);

	// set the output plug-ins default volume.
	// volume is 0-255, -666 is a token for
	// current volume.

	mod.outMod->SetVolume(-666);
	return 1;
}

void DecodeLoop::CalculateCodecDelay()
{
	int decoderDelaySamples = (int)decoder->GetDecoderDelay();

	endCutter.SetSize(decoderDelaySamples*sampleFrameSize,
	                  file.prepad*sampleFrameSize,
	                  file.postpad*sampleFrameSize);
}

void DecodeLoop::Viz()
{
	if (!config_fastvis || (decoder->GetLayer() != 3 || g_ds))
	{
		int vis_waveNch;
		int vis_specNch;
		int csa = mod.SAGetMode();
		int is_vis_running = mod.VSAGetMode(&vis_specNch, &vis_waveNch);
		if (csa || is_vis_running)
		{
			int l = 576 * sampleFrameSize;
			int ti = decode_pos_ms;
			{
				if (g_ds == 2)
				{
					memcpy(g_samplebuf + g_samplebuf_used, g_samplebuf, g_samplebuf_used);
				}
				size_t pos = 0;
				while (pos < g_samplebuf_used)
				{
					int a, b;
					if (mod.SAGetMode()) mod.SAAddPCMData((char *)g_samplebuf + pos, g_nch, (int)g_bits, ti);
					if (mod.VSAGetMode(&a, &b)) mod.VSAAddPCMData((char *)g_samplebuf + pos, g_nch, (int)g_bits, ti);
					ti += ((l / sampleFrameSize * 1000) / g_srate);
					pos += l >> g_ds;
				}
			}
		}
	}
	else
	{
		int l = (576 * (int)g_bits * g_nch);
		int ti = decode_pos_ms;
		size_t pos = 0;
		int x = 0;
		while (pos < g_samplebuf_used)
		{
			do_layer3_vis((short*)(g_samplebuf + pos), &g_vis_table[x++][0][0][0], g_nch, ti);
			ti += (l / g_nch / 2 * 1000) / g_srate;
			pos += l;
		}
	}
}

void DecodeLoop::Decode()
{
	while (g_samplebuf_used < (size_t)g_framesize && !killDecodeThread && seek_needed == -1)
	{
		size_t newl = 0;
		size_t br=0;
		size_t endCut=0;
		int res = decoder->Decode(&file, g_samplebuf + g_samplebuf_used, sizeof(g_samplebuf) / 2 - g_samplebuf_used, &newl, &br, &endCut);

		if (config_gapless && endCut)
			endCutter.SetEndSize((int)endCut* sampleFrameSize);

		// we're not using switch here because we sometimes need to break out of the while loop
		if (res == adts::SUCCESS)
		{
			if (!file.m_vbr_frames)
			{
				if (br) {
					bool do_real_br=false;
					if (!(config_miscopts&2) && br != decoder->GetCurrentBitrate())
					{
						do_real_br=true;
					}

					int r = (int)br;
					g_br_add += r;
					g_br_div++;
					r = (g_br_add + g_br_div / 2) / g_br_div;
					if (g_br != r)
					{
						need_synclight = false;
						g_br = r;
						if (!file.m_vbr_frames && file.IsSeekable()) g_length = MulDiv(file.GetContentLength(), 8, g_br);
						if (!do_real_br)
							mod.SetInfo(g_br, -1, -1, 1);
					}
					if (do_real_br)
						mod.SetInfo((int)br, -1, -1, 1);
				}
			}
			else
			{
				if (br) {
					int r;
					if (!(config_miscopts&2) || !g_avg_vbr_br)
						r = (int)br;
					else r = g_avg_vbr_br;
					if (g_br != r)
					{
						need_synclight = false;
						g_br = r;
						mod.SetInfo(g_br, -1, -1, 1);
					}
				}
			}
			if (need_synclight)
			{
				need_synclight = false;
				mod.SetInfo(-1, -1, -1, 1);
			}
			g_samplebuf_used += newl;
		}
		else if (res == adts::ENDOFFILE)
		{
			done = 1;
			break;
		}
		else if (res == adts::NEEDMOREDATA)
		{
			if (file.IsStream() && !need_synclight)
			{
				need_synclight = true; mod.SetInfo(-1, -1, -1, 0);
			}
			if (file.IsStream() && !mod.outMod->IsPlaying())
			{
				need_prebuffer = CalcPreBuffer(config_http_prebuffer_underrun, (int)br);
			}
			break;
		}
		else
		{
			if (!need_synclight) mod.SetInfo(-1, -1, -1, 0);
			need_synclight = true;
			break;
		}
	}
}

void DecodeLoop::PreBuffer()
{
	int p = file.RunStream();
	int pa = file.PercentAvailable();
	if (pa >= need_prebuffer || p == 2)
	{
		EnterCriticalSection(&g_lfnscs);
		lastfn_status[0] = 0;
		LeaveCriticalSection(&g_lfnscs);
		PostMessage(mod.hMainWindow, WM_USER, 0, IPC_UPDTITLE);
		need_prebuffer = 0;
		g_bufferstat = 0;
		last_bpos = -1;
	}
	else
	{
		int bpos = pa * 100 / need_prebuffer;
		if (!g_bufferstat) g_bufferstat = decode_pos_ms;
		if (bpos != last_bpos)
		{
			last_bpos = bpos;
			EnterCriticalSection(&g_lfnscs);
			if (stricmp(lastfn_status, "stream temporarily interrupted"))
			{
				char langbuf[512] = {0};
				wsprintfA(lastfn_status, WASABI_API_LNGSTRING_BUF(IDS_BUFFER_X,langbuf,512), bpos);
			}
			LeaveCriticalSection(&g_lfnscs);

			int csa = mod.SAGetMode();
			char tempdata[75*2] = {0, };
			int x;
			if (csa&1)
			{
				for (x = 0; x < bpos*75 / 100; x ++)
				{
					tempdata[x] = x * 16 / 75;
				}
			}
			if (csa&2)
			{
				int offs = (csa & 1) ? 75 : 0;
				x = 0;
				while (x < bpos*75 / 100)
				{
					tempdata[offs + x++] = -6 + x * 14 / 75;
				}
				while (x < 75)
				{
					tempdata[offs + x++] = 0;
				}
			}
			if (csa == 4)
			{
				tempdata[0] = tempdata[1] = (bpos * 127 / 100);
			}

			if (csa) mod.SAAdd(tempdata, ++g_bufferstat, (csa == 3) ? 0x80000003 : csa);
			PostMessage(mod.hMainWindow, WM_USER, 0, IPC_UPDTITLE);
		}
	}
}

void DecodeLoop::Seek(int seekPosition)
{
	if (done == 3)
		return;
	done=0;
	int br = (int)decoder->GetCurrentBitrate();

	need_prebuffer = CalcPreBuffer(config_http_prebuffer_underrun, br);
	if (need_prebuffer < 1) need_prebuffer = 5;

	last_decode_pos_ms = decode_pos_ms = seekPosition;

	seek_needed = -1;
	endCutter.Flush(decode_pos_ms);
	decoder->Flush(&file);
	done = 0;
	g_samplebuf_used = 0;

	int r = g_br;
	if (g_br_div) r = (g_br_add + g_br_div / 2) / g_br_div;
	file.Seek(decode_pos_ms, r);
	//      need_prebuffer=config_http_prebuffer/8;
	//			g_br_add=g_br_div=0;

}

DWORD DecodeLoop::OpenDecoder()
{
	mod.UsesOutputPlug &= ~8;
	if (isAac)
	{
		if (isEAAC)
		{
				waServiceFactory *factory = mod.service->service_getServiceByGuid(adts_aac_guid);
				if (factory)
					decoder = (adts *)factory->getInterface();

				mod.UsesOutputPlug|=8;
		}
		if (!decoder)
		{
			decoder = new ADTS_VLB;
			mod.UsesOutputPlug &= ~8;
		}
	}
	else
	{
		waServiceFactory *factory = mod.service->service_getServiceByGuid(adts_mp2_guid);
		if (factory)
			decoder = (adts *)factory->getInterface();

		mod.UsesOutputPlug|=8;
	}

	if (decoder) {
		decoder->SetDecoderHooks(mp3GiveVisData, mp2Equalize, mp3Equalize);
	}

	if (decoder 
		&& decoder->Initialize(AGAVE_API_CONFIG->GetBool(playbackConfigGroupGUID, L"mono", false), 
													 config_downmix == 2, 
													 AGAVE_API_CONFIG->GetBool(playbackConfigGroupGUID, L"surround", true),
													 (int)AGAVE_API_CONFIG->GetUnsigned(playbackConfigGroupGUID, L"bits", 16), true, false,
													 (config_miscopts&1)/*crc*/) == adts::SUCCESS
		&& decoder->Open(&file))
	{
		// sync to stream
		while (1)
		{
			switch (decoder->Sync(&file, g_samplebuf, sizeof(g_samplebuf), &g_samplebuf_used, &br))
			{
			case adts::SUCCESS:
				return 1;
			case adts::FAILURE:
			case adts::ENDOFFILE:
				if (!killDecodeThread)
				{
					if (!lastfn_status_err)
					{
						EnterCriticalSection(&g_lfnscs);
						WASABI_API_LNGSTRING_BUF(IDS_ERROR_SYNCING_TO_STREAM,lastfn_status,256);
						LeaveCriticalSection(&g_lfnscs);
						PostMessage(mod.hMainWindow, WM_USER, 0, IPC_UPDTITLE);
					}
				}
				if (!killDecodeThread) Sleep(200);
				if (!killDecodeThread) 			PostMessage(mod.hMainWindow, WM_WA_MPEG_EOF, 0, 0);
				return 0;
			case adts::NEEDMOREDATA:
				if (!killDecodeThread && file.IsStream()) Sleep(25);
				if (killDecodeThread) return 0;
			}
		}
	}

	return 0;
}

DWORD DecodeLoop::Loop()
{
	last_decode_pos_ms = 0;

	if (file.Open(lastfn, config_max_bufsize_k) != NErr_Success)
	{
		if (!killDecodeThread) Sleep(200);
		if (!killDecodeThread) PostMessage(mod.hMainWindow, WM_WA_MPEG_EOF, 0, 0);
		return 0;
	}

	if (file.IsSeekable()) mod.is_seekable = 1;

	wchar_t *ext = PathFindExtension(lastfn);
	if (!_wcsicmp(ext, L".aac")
	    || !_wcsicmp(ext, L".vlb")
	    || !_wcsicmp(ext, L".apl"))
	{
		if (file.IsStream())
			SetupStream();
		else
		{
			isAac = 1;
			if (!_wcsicmp(ext, L".aac") || !_wcsicmp(ext, L".apl")) isEAAC = 1;
		}
	}
	else if (file.IsStream())
		SetupStream();

	if (OpenDecoder() == 0)
		return 0;

	EnterCriticalSection(&streamInfoLock);
	g_playing_file = &file;
	if (file.uvox_3901)
	{
		PostMessage(mod.hMainWindow, WM_WA_IPC, (WPARAM) "0x3901", IPC_METADATA_CHANGED);
			PostMessage(mod.hMainWindow, WM_WA_IPC, 0, IPC_UPDTITLE);
	}
	LeaveCriticalSection(&streamInfoLock);
	

	EnterCriticalSection(&g_lfnscs);
	lastfn_status[0] = 0;
	LeaveCriticalSection(&g_lfnscs);

	lastfn_data_ready = 1;

// TODO?	if (decoder != &aacp) // hack because aac+ bitrate isn't accurate at this point
		br = decoder->GetCurrentBitrate();

	need_prebuffer = CalcPreBuffer(config_http_prebuffer, (int)br);

	if (((!(config_eqmode&4) && decoder->GetLayer() == 3) ||
	                    ((config_eqmode&8) && decoder->GetLayer() < 3)))
	{
		mod.UsesOutputPlug |= 2;
	}
	else
		mod.UsesOutputPlug &= ~2;

	decoder->CalculateFrameSize(&g_framesize);
	decoder->GetOutputParameters(&g_bits, &g_nch, &g_srate);

	if (!killDecodeThread && file.IsStream() == 1)
	{
		DWORD_PTR dw;
		if (!killDecodeThread) SendMessageTimeout(mod.hMainWindow, WM_USER, 0, IPC_UPDTITLE, SMTO_BLOCK, 100, &dw);
		if (!killDecodeThread) SendMessageTimeout(mod.hMainWindow, WM_TIMER, 38, 0, SMTO_BLOCK, 100, &dw);
	}

	sampleFrameSize = g_nch * ((int)g_bits/8);

	if (config_gapless)
		CalculateCodecDelay();

	if (OpenOutput(g_nch, g_srate, (int)g_bits) == 0)
		return 0;

	/* ----- send info to winamp and vis: bitrate, etc ----- */
	g_br = (int)decoder->GetCurrentBitrate();

	g_br_add = g_br;
	g_br_div = 1;
	g_avg_vbr_br = file.GetAvgVBRBitrate();
	mod.SetInfo(g_br, g_srate / 1000, g_nch, 0);

	// initialize visualization stuff
	mod.SAVSAInit((maxlatency << g_ds), g_srate);
	mod.VSASetInfo(g_srate, g_nch);
	/* ----- end send info to winamp and vis ----- */

	if (file.IsSeekable() && g_br)
	{
		mod.is_seekable = 1;
		if (!file.m_vbr_frames) g_length = MulDiv(file.GetContentLength(), 8, g_br);
		else g_length = file.m_vbr_ms;
	}

	if (file.IsStream())
	{
		if (need_prebuffer < config_http_prebuffer / 2)
			need_prebuffer = config_http_prebuffer / 2;
	}

	while (!killDecodeThread)
	{
		if (seek_needed != -1)
			Seek(seek_needed);

		if (need_prebuffer && file.IsStream() && maxlatency && !file.EndOf())
			PreBuffer();

		int needsleep = 1;

		if (done == 2) // done was set to TRUE during decoding, signaling eof
		{
			mod.outMod->CanWrite();		// some output drivers need CanWrite
			// to be called on a regular basis.

			if (!mod.outMod->IsPlaying())
			{
				// we're done playing, so tell Winamp and quit the thread.
				if (!killDecodeThread) PostMessage(mod.hMainWindow, WM_WA_MPEG_EOF, 0, 0);
				done=3;
				break;
			}
		}
		else
		{
			int fs = (g_framesize * ((mod.dsp_isactive() == 1) ? 2 : 1));
			// TODO: we should really support partial writes, there's no gaurantee that CanWrite() will EVER get big enough
			if (mod.outMod->CanWrite() >= fs && (!need_prebuffer || !file.IsStream() || !maxlatency))
				// CanWrite() returns the number of bytes you can write, so we check that
				// to the block size. the reason we multiply the block size by two if
				// mod.dsp_isactive() is that DSP plug-ins can change it by up to a
				// factor of two (for tempo adjustment).
			{
				int p = mod.SAGetMode();
				g_vis_enabled = ((p & 1) || p == 4);
				if (!g_vis_enabled)
				{
					int s, a;
					mod.VSAGetMode(&s, &a);
					if (s) g_vis_enabled = 1;
				}

				Decode();

				if ((g_samplebuf_used >= (size_t)g_framesize || (done && g_samplebuf_used > 0)) && seek_needed == -1)
				{
					// adjust decode position variable
					if (file.isSeekReset())
						last_decode_pos_ms = decode_pos_ms = 0;
					else
						decode_pos_ms += ((int)g_samplebuf_used / sampleFrameSize  * 1000) / g_srate;

					// if we have a DSP plug-in, then call it on our samples
					if (mod.dsp_isactive())
					{
						g_samplebuf_used = mod.dsp_dosamples((short *)g_samplebuf, (int)g_samplebuf_used / sampleFrameSize, (int)g_bits, g_nch, g_srate) * sampleFrameSize;
					}
					Viz();
					endCutter.Write((char *)g_samplebuf, (int)g_samplebuf_used);
					g_samplebuf_used = 0;
					needsleep = 0;
					//memcpy(g_samplebuf,g_samplebuf+r,g_samplebuf_used);
				}
				if (done)
				{
					endCutter.Write(0, 0);
					done = 2;
				}
			}
		}
		if (decode_pos_ms > last_decode_pos_ms + 1000)
		{
			last_decode_pos_ms = decode_pos_ms;
		}

		if (needsleep) Sleep(10);
		// if we can't write data, wait a little bit. Otherwise, continue
		// through the loop writing more data (without sleeping)
	}

	/* ---- change some globals to let everyone know we're done */
	EnterCriticalSection(&g_lfnscs);
	lastfn_status[0] = 0;
	LeaveCriticalSection(&g_lfnscs);
	g_bufferstat = 0;
	g_closeaudio = 1;
	/* ----  */

	return 0;
}

DWORD WINAPI DecodeThread(LPVOID b)
{
	DecodeLoop loop;

	

	DWORD ret = loop.Loop();

	EnterCriticalSection(&streamInfoLock);
	g_playing_file = 0;
	LeaveCriticalSection(&streamInfoLock);
	return ret;
}