#include "gvSource.h"
#include "gvCodec.h"
#include "gvFrame.h"
#include "gvUtil.h"
#include <stdio.h>

// some code based on:
// "Skew Detection and Compensation for Internet Audio Applications"
// http://csperkins.org/publications/icme2000.pdf

#define GVI_SYNCHRONIZATION_DELAY                 200

#define GVI_MAX_SOURCES                           8

#define GVI_CLOCK_OFFSET_AVERAGING_FACTOR         0.96875  //(31/32)

#define GVI_DIVERGENCE_HIGH_WATERMARK             10
#define GVI_DIVERGENCE_LOW_WATERMARK              ((GVFrameStamp)-5)

#define GVI_SHOW_SKEW_CORRECTIONS                 0
#define GVI_SHOW_SOURCELIST_CHANGES               0

typedef struct GVISource
{
	GVBool          m_inUse;
	GVSource        m_source;
	GVBool          m_isTalking;
	GVFrameStamp    m_finishedTalkingTime;
	GVFrameStamp    m_clockOffset;
	float           m_clockOffsetAverage;
	GVDecoderData   m_decoderData;
	GVIPendingFrame m_frameHead;
} GVISource;

static int GVISynchronizationDelayFrames;

static void gviFreeSource(GVISource * source)
{
	GVIPendingFrame * frame = source->m_frameHead.m_next;
	GVIPendingFrame * next;

	// make sure it is in use
	if(!source->m_inUse)
		return;

	// put all of the pending frames back into the list
	while(frame)
	{
		next = frame->m_next;
		gviPutPendingFrame(frame);
		frame = next;
	}

	// free the decoder data
	gviFreeDecoder(source->m_decoderData);

#if GVI_SHOW_SOURCELIST_CHANGES
	printf("Freed source\n");
#endif

	// it is no longer in use
	source->m_inUse = GVFalse;
}

GVISourceList gviNewSourceList(void)
{
	int size;
	GVISourceList sourceList;

	if(!GVISynchronizationDelayFrames)
	{
		int delay;
		
		delay = gviMultiplyByBytesPerMillisecond(GVI_SYNCHRONIZATION_DELAY);
		delay = gviRoundUpToNearestMultiple(delay, GVIBytesPerFrame);

		GVISynchronizationDelayFrames = (delay / GVIBytesPerFrame);
	}

	size = (sizeof(GVISource) * GVI_MAX_SOURCES);
	sourceList = (GVISourceList)gsimalloc((unsigned int)size);
	if(sourceList)
		memset(sourceList, 0, (unsigned int)size);

	return sourceList;
}

void gviFreeSourceList(GVISourceList sourceList)
{
	gviClearSourceList(sourceList);
	gsifree(sourceList);
}

void gviClearSourceList(GVISourceList sourceList)
{
	int i;

	assert(sourceList);

	for(i = 0 ; i < GVI_MAX_SOURCES ; i++)
		gviFreeSource(&sourceList[i]);
}

static GVISource * gviFindSourceInList(GVISourceList sourceList, GVSource source)
{
	GVISource * gviSource;
	int i;

	// check if this source is in the list
	for(i = 0 ; i < GVI_MAX_SOURCES ; i++)
	{
		gviSource = &sourceList[i];
		if(gviSource->m_inUse)
		{
			if(memcmp(&gviSource->m_source, &source, sizeof(GVSource)) == 0)
			{
				return gviSource;
			}
		}
	}

	return NULL;
}

GVBool gviIsSourceTalking(GVISourceList sourceList, GVSource source)
{
	GVISource * gviSource = gviFindSourceInList(sourceList, source);
	if(!gviSource)
		return GVFalse;

	return gviSource->m_isTalking;
}

int gviListTalkingSources(GVISourceList sourceList, GVSource sources[], int maxSources)
{
	GVISource * gviSource;
	int numTalking = 0;
	int i;

	// loop through the sources
	for(i = 0 ; i < GVI_MAX_SOURCES ; i++)
	{
		gviSource = &sourceList[i];

		// check if the source is in use and talking
		if(gviSource->m_inUse && gviSource->m_isTalking)
		{
			// add it to the list
			memcpy(&sources[numTalking], &gviSource->m_source, sizeof(GVSource));

			// one more talker
			numTalking++;

			// check for the max
			if(numTalking == maxSources)
				break;
		}
	}

	return numTalking;
}

static GVISource * gviAddSourceToList(GVISourceList sourceList, GVSource source)
{
	GVBool result;
	GVISource * gviSource = NULL;
	int i;

	// loop through the sources
	for(i = 0 ; i < GVI_MAX_SOURCES ; i++)
	{
		// check if this source is available
		if(!sourceList[i].m_inUse)
		{
			gviSource = &sourceList[i];
			break;
		}

		// also look for a source without frames
		// if we don't find a totally free one, we can take this one over
		if(!gviSource && !sourceList[i].m_frameHead.m_next)
			gviSource = &sourceList[i];
	}

	// check if we didn't find anything
	if(!gviSource)
		return NULL;

	// if this source is already in use, free it first
	if(gviSource->m_inUse)
		gviFreeSource(gviSource);

	// make sure we can get a new decoder before moving on
	result = gviNewDecoder(&gviSource->m_decoderData);
	if(!result)
		return NULL;

	// mark as in use
	gviSource->m_inUse = GVTrue;

	// set the rest of the info
	memcpy(&gviSource->m_source, &source, sizeof(GVSource));
	gviSource->m_clockOffset = 0;
	gviSource->m_clockOffsetAverage = 0;
	gviSource->m_isTalking = GVFalse;
	gviSource->m_finishedTalkingTime = 0;
	gviSource->m_frameHead.m_next = NULL;

#if GVI_SHOW_SOURCELIST_CHANGES
	printf("Added source\n");
#endif

	return gviSource;
}

static void gviAddPacketToSource(GVISource * source, GVFrameStamp frameStamp, const GVByte * packet, int len)
{
	GVIPendingFrame * frame;
	GVIPendingFrame * nextFrame;
	GVIPendingFrame * newFrame;
	GVFrameStamp packetFinishedTime;
	int numFrames;
	int i;

	// the packet len should be a multiple of the encoded frame size
	assert(!(len % GVIEncodedFrameSize));

	// calculate the number of frames in this packet
	numFrames = (len / GVIEncodedFrameSize);

	// use the clock offset to adjust the frameStamp
	frameStamp += source->m_clockOffset;

	// figure out when this packet will be finished
	packetFinishedTime = (GVFrameStamp)(frameStamp + numFrames);

	// update the time at which this source is done talking
	if(gviIsFrameStampGT(packetFinishedTime, source->m_finishedTalkingTime))
		source->m_finishedTalkingTime = packetFinishedTime;

	// find where to add it
	// it must be in chronological order
	for(frame = &source->m_frameHead ; frame->m_next ; frame = frame->m_next)
	{
		// store a pointer to the next frame
		// this is the frame we will compare to this time through the loop
		nextFrame = frame->m_next;

		// check if the framestamp is the same (a repeated packet)
		if(nextFrame->m_frameStamp == frameStamp)
			return;

		// check if the new frame should be placed in front of the next frame
		if(gviIsFrameStampGT(nextFrame->m_frameStamp, frameStamp))
		{
			// check that the packet's finish time doesn't cross over the next frame
			assert(!gviIsFrameStampGT(packetFinishedTime, nextFrame->m_frameStamp));

			// everything is good, break out so we can insert the new frames
			break;
		}
	}

	// loop through the frames in the packet
	for(i = 0 ; i < numFrames ; i++)
	{
		// get a new frame
		newFrame = gviGetPendingFrame();
		if(!newFrame)
			return;

		// fill it in
		newFrame->m_frameStamp = frameStamp;
#if GVI_PRE_DECODE
		gviDecodeSet(newFrame->m_frame, packet, source->m_decoderData);
#else
		memcpy(newFrame->m_frame, packet, (unsigned int)GVIEncodedFrameSize);
#endif
		newFrame->m_next = frame->m_next;

		// setup the previous frame's next pointer to point to this one
		frame->m_next = newFrame;

		// adjust vars
		frameStamp++;
		packet += GVIEncodedFrameSize;
		frame = newFrame;
	}
}

void gviAddPacketToSourceList(GVISourceList sourceList,
							  const GVByte * packet, int len, GVSource source, GVFrameStamp frameStamp,
							  GVFrameStamp currentPlayClock)
{
	GVFrameStamp playTime;
	GVISource * gviSource;
	GVFrameStamp clockOffset;
	GVFrameStamp divergence;
	int unwrappedClockOffset;

	// add the sync delay to the clock to get the play time
	playTime = (GVFrameStamp)(currentPlayClock + GVISynchronizationDelayFrames);

	// calculate the clock offset
	clockOffset = (GVFrameStamp)(playTime - frameStamp);

	// check if this source already exists
	gviSource = gviFindSourceInList(sourceList, source);
	if(!gviSource)
	{
		// it isn't, so add it
		gviSource = gviAddSourceToList(sourceList, source);

		// drop the packet if it couldn't be added
		if(!gviSource)
			return;

		// store the clock offset
		gviSource->m_clockOffset = clockOffset;
		gviSource->m_clockOffsetAverage = (float)clockOffset;

		// init the finished talking time
		gviSource->m_finishedTalkingTime = (GVFrameStamp)(frameStamp + gviSource->m_clockOffset);
	}
	else
	{
		// unwrap the clock offset if needed
		if((clockOffset < gviSource->m_clockOffsetAverage) && gviIsFrameStampGT(clockOffset, (GVFrameStamp)gviSource->m_clockOffsetAverage))
		{
			unwrappedClockOffset = (clockOffset + GVI_FRAMESTAMP_MAX);
		}
		else if((gviSource->m_clockOffsetAverage < clockOffset) && gviIsFrameStampGT((GVFrameStamp)gviSource->m_clockOffsetAverage, clockOffset))
		{
			unwrappedClockOffset = (clockOffset - GVI_FRAMESTAMP_MAX);
		}
		else
		{
			unwrappedClockOffset = clockOffset;
		}

		// update the running average of the clock offset
		gviSource->m_clockOffsetAverage =
			(float)((gviSource->m_clockOffsetAverage * GVI_CLOCK_OFFSET_AVERAGING_FACTOR) +
			(unwrappedClockOffset * (1.0 - GVI_CLOCK_OFFSET_AVERAGING_FACTOR)));
		if(gviSource->m_clockOffsetAverage < 0)
			gviSource->m_clockOffsetAverage += GVI_FRAMESTAMP_MAX;
		else if(gviSource->m_clockOffsetAverage >= GVI_FRAMESTAMP_MAX)
			gviSource->m_clockOffsetAverage -= GVI_FRAMESTAMP_MAX;

		// calculate the divergence
		divergence = (GVFrameStamp)(gviSource->m_clockOffset - (GVFrameStamp)gviSource->m_clockOffsetAverage);

		// check against the high-water mark
		if(gviIsFrameStampGT(divergence, GVI_DIVERGENCE_HIGH_WATERMARK))
		{
#if GVI_SHOW_SKEW_CORRECTIONS
			static int dropCount;
			printf("DROP: %d\n", ++dropCount);
#endif

			// update the clock offset
			gviSource->m_clockOffset--;

			// if this is a one frame packet, just drop it
			if(len == GVIEncodedFrameSize)
				return;

			// otherwise update the params
			packet += GVIEncodedFrameSize;
			len -= GVIEncodedFrameSize;
		}
		// check against the low-water mark
		else if(gviIsFrameStampGT(GVI_DIVERGENCE_LOW_WATERMARK, divergence))
		{
#if GVI_SHOW_SKEW_CORRECTIONS
			static int insertCount;
			printf("INSERT: %d\n", ++insertCount);
#endif

			// update the clock offset
			// this will basically add a frame of silence
			gviSource->m_clockOffset++;
		}

		// check if this packet is too old or too far ahead to play
		if(gviIsFrameStampGT(currentPlayClock, (GVFrameStamp)(frameStamp + gviSource->m_clockOffset)))
			return;
	}

	// add the packet to the source
	gviAddPacketToSource(gviSource, frameStamp, packet, len);
}

GVBool gviWriteSourcesToBuffer(GVISourceList sourceList, GVFrameStamp startTime,
                               GVSample * sampleBuffer, int numFrames)
{
	GVISource * source;
	GVFrameStamp timeSliceEnd;
	GVIPendingFrame * frame;
	int i;
	GVBool result = GVFalse;

	// calculate the end of the time slice
	timeSliceEnd = (GVFrameStamp)(startTime + numFrames);

	// clear the sample buffer
	memset(sampleBuffer, 0, (unsigned int)numFrames * GVIBytesPerFrame);

	// loop through the sources
	for(i = 0 ; i < GVI_MAX_SOURCES ; i++)
	{
		// get the next source
		source = &sourceList[i];

		// check if it is in use
		if(!source->m_inUse)
			continue;

		// keep going while there are pending frames for this source
		while(source->m_frameHead.m_next)
		{
			// cache a pointer to the first frame
			frame = source->m_frameHead.m_next;

			// check if this frame is too far ahead
			if(gviIsFrameStampGTE(frame->m_frameStamp, timeSliceEnd))
				break;

			// make sure this buffer's timeslice hasn't already elapsed
			if(gviIsFrameStampGTE(frame->m_frameStamp, startTime))
			{
				// add the frame to the buffer
				GVSample * writePtr = (sampleBuffer + ((GVFrameStamp)(frame->m_frameStamp - startTime) * GVISamplesPerFrame));
#if GVI_PRE_DECODE
				int j;
				for(j = 0 ; j < GVISamplesPerFrame ; j++)
					writePtr[j] += frame->m_frame[j];
#else
				gviDecodeAdd(writePtr, frame->m_frame, source->m_decoderData);
#endif

				// this source is talking
				source->m_isTalking = GVTrue;

				// set the return value to indicate that decoding took place
				result = GVTrue;
			}

			// update the source to point to the next frame
			source->m_frameHead.m_next = frame->m_next;

			// free the frame we just used
			gviPutPendingFrame(frame);
		}

		// remove the source if it has no more frames and is marked as finished
		if(!source->m_frameHead.m_next && gviIsFrameStampGTE(timeSliceEnd, source->m_finishedTalkingTime))
			gviFreeSource(source);
	}

	return result;

}
