/*
 *
 * @APPLE_LICENSE_HEADER_START@
 *
 * Copyright (c) 1999-2001 Apple Computer, Inc.  All Rights Reserved. The
 * contents of this file constitute Original Code as defined in and are
 * subject to the Apple Public Source License Version 1.2 (the 'License').
 * You may not use this file except in compliance with the License.  Please
 * obtain a copy of the License at http://www.apple.com/publicsource and
 * read it before using this file.
 *
 * This Original Code and all software distributed under the License are
 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, FITNESS
 * FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.  Please
 * see the License for the specific language governing rights and
 * limitations under the License.
 *
 *
 * @APPLE_LICENSE_HEADER_END@
 *
 */
/*
	File:		RTPPacketResender.cpp

	Contains:	RTPPacketResender class to buffer and track re-transmits of RTP packets.
	
	
*/

#include <stdio.h>

#include "RTPPacketResender.h"
#include "RTPStream.h"
#include "atomic.h"
#include "OSMutex.h"

#if RTP_PACKET_RESENDER_DEBUGGING
#include "QTSSRollingLog.h"
#include <stdarg.h>

class MyAckListLog : public QTSSRollingLog
{
	public:
	
		MyAckListLog(char * logFName) : QTSSRollingLog() { ::strcpy( fLogFName, logFName ); }
		virtual ~MyAckListLog() {}
	
		virtual char* GetLogName() 
		{ 
			char *logFileNameStr = NEW char[80];
			
			::strcpy( logFileNameStr, fLogFName );
			return logFileNameStr; 
		}
		
		virtual char* GetLogDir()
		{ 
			char *logDirStr = NEW char[80];
			
#ifdef __MacOSX__
			::strcpy( logDirStr, "/Library/QuickTimeStreaming/Logs" );
#else
			::strcpy( logDirStr, "/var/streaming/logs" );
#endif
			return logDirStr; 
		}
		
		virtual UInt32 GetRollIntervalInDays() 	{ return 0; }
		virtual UInt32 GetMaxLogBytes()			{ return 0; }
		
	char	fLogFName[128];
	
};
#endif

static const UInt32 kPacketArrayIncreaseInterval = 32;// must be multiple of 2
static const UInt32 kInitialPacketArraySize = 64;// must be multiple of kPacketArrayIncreaseInterval (Turns out this is as big as we typically need)
//static const UInt32 kMaxPacketArraySize = 512;// must be multiple of kPacketArrayIncreaseInterval it would have to be a 3 mbit or more

static const UInt32 kMaxDataBufferSize = 1600;
OSBufferPool RTPPacketResender::sBufferPool(kMaxDataBufferSize);
unsigned int	RTPPacketResender::sNumWastedBytes = 0;

RTPPacketResender::RTPPacketResender()
:	fBandwidthTracker(NULL),
	fSocket(NULL),
	fDestAddr(0),
	fDestPort(0),
	fMaxPacketsInList(0),
	fPacketsInList(0),
	fNumResends(0),
	fNumExpired(0),
	fNumAcksForMissingPackets(0),
	fNumSent(0),
	fPacketArray(NULL),
	fPacketArraySize(kInitialPacketArraySize),
	fPacketArrayMask(0),
	fHighestSeqNum(0),
	fLastUsed(0),
	fPacketQMutex()
{
	fPacketArray = (RTPResenderEntry*) NEW char[sizeof(RTPResenderEntry) * fPacketArraySize];
	::memset(fPacketArray,0,sizeof(RTPResenderEntry) * fPacketArraySize);

}

RTPPacketResender::~RTPPacketResender()
{
	for (UInt32 x = 0; x < fPacketArraySize; x++)
	{
		if (fPacketArray[x].fPacketSize > 0)
			atomic_sub(&sNumWastedBytes, kMaxDataBufferSize - fPacketArray[x].fPacketSize);
		if (fPacketArray[x].fPacketData != NULL)
		{
			if (fPacketArray[x].fIsSpecialBuffer)
				delete [] (char*)fPacketArray[x].fPacketData;
			else
				sBufferPool.Put(fPacketArray[x].fPacketData);
		}
	}
			
	delete [] fPacketArray;
	

}

#if RTP_PACKET_RESENDER_DEBUGGING
void RTPPacketResender::logprintf( const char * format, ... )
{
	/*
		WARNING - the logger is not multiple task thread safe.
		its OK when we run just one thread for all of the
		sending tasks though.
		
		each logger for a given session will open up access
		to the same log file.  with one thread we're serialized
		on writing to the file, so it works. 
	*/
	
	va_list argptr;
	char	buff[1024];
	

	va_start( argptr, format );
	
	vsprintf( buff, format, argptr );
	
	va_end(argptr);

	if ( fLogger )
	{
		fLogger->WriteToLog(buff, false);
		printf( buff );
	}
}


void RTPPacketResender::SetDebugInfo(UInt32 trackID, UInt16 remoteRTCPPort, UInt32 curPacketDelay)
{
	fTrackID = trackID;
	fRemoteRTCPPort = remoteRTCPPort;
	fCurrentPacketDelay = curPacketDelay;
}

void RTPPacketResender::SetLog( StrPtrLen *logname )
{
	/*
		WARNING - see logprintf()
	*/
	
	char	logFName[128];
	
	memcpy( logFName, logname->Ptr, logname->Len );
	logFName[logname->Len] = 0;
	
	if ( fLogger )
		delete fLogger;

	fLogger = new MyAckListLog( logFName );
	
	fLogger->EnableLog();
}

void RTPPacketResender::LogClose(SInt64 inTimeSpentInFlowControl)
{
	this->logprintf("Flow control duration msec: %"_64BITARG_"d. Max outstanding packets: %d\n", inTimeSpentInFlowControl, this->GetMaxPacketsInList());
	
}


UInt32 RTPPacketResender::SpillGuts(UInt32 inBytesSentThisInterval)
{
	if (fInfoDisplayTimer.DurationInMilliseconds() > 1000 )
	{
		//fDisplayCount++;
		
		// spill our guts on the state of KRR
		char *isFlowed = "open";
		
		if ( fBandwidthTracker->IsFlowControlled() )
			isFlowed = "flowed";
		
		SInt64	kiloBitperSecond = (( (SInt64)inBytesSentThisInterval * (SInt64)1000 * (SInt64)8 ) / fInfoDisplayTimer.DurationInMilliseconds() ) / (SInt64)1024;
		
		//fStreamCumDuration += fInfoDisplayTimer.DurationInMilliseconds();
		fInfoDisplayTimer.Reset();

		//this->logprintf( "\n[%li] info for track %li, cur bytes %li, cur kbit/s %li\n", /*(long)fStreamCumDuration,*/ fTrackID, (long)inBytesSentThisInterval, (long)kiloBitperSecond);
		this->logprintf( "\nx info for track %li, cur bytes %li, cur kbit/s %li\n", /*(long)fStreamCumDuration,*/ fTrackID, (long)inBytesSentThisInterval, (long)kiloBitperSecond);
		this->logprintf( "stream is %s, bytes pending ack %li, cwnd %li, ssth %li, wind %li \n", isFlowed, fBandwidthTracker->BytesInList(), fBandwidthTracker->CongestionWindow(), fBandwidthTracker->SlowStartThreshold(), fBandwidthTracker->ClientWindowSize() );
		this->logprintf( "stats- resends:  %li, expired:  %li, dupe acks: %li, sent: %li\n", fNumResends, fNumExpired, fNumAcksForMissingPackets, fNumSent );
		this->logprintf( "delays- cur:  %li, srtt: %li , dev: %li, rto: %li, bw: %li\n\n", fCurrentPacketDelay, fBandwidthTracker->RunningAverageMSecs(), fBandwidthTracker->RunningMeanDevationMSecs(), fBandwidthTracker->CurRetransmitTimeout(), fBandwidthTracker->GetCurrentBandwidthInBps());
		
		
		inBytesSentThisInterval = 0;
	}
	return inBytesSentThisInterval;
}

#endif


void RTPPacketResender::SetDestination(UDPSocket* inOutputSocket, UInt32 inDestAddr, UInt16 inDestPort)
{
	fSocket = inOutputSocket;
	fDestAddr = inDestAddr;
	fDestPort = inDestPort;
}

RTPResenderEntry*	RTPPacketResender::GetEmptyEntry(UInt16 inSeqNum, UInt32 inPacketSize)
{
	
	RTPResenderEntry* theEntry = NULL;
	
	for (UInt32 packetIndex = 0 ;packetIndex < fPacketsInList; packetIndex++) // see if packet is already in the array
	{	if (inSeqNum == fPacketArray[packetIndex].fSeqNum)
		{	return NULL;
		}
	}

	if (fPacketsInList == fPacketArraySize) // allocate a new array
	{
		fPacketArraySize += kPacketArrayIncreaseInterval;
		RTPResenderEntry* tempArray = (RTPResenderEntry*) NEW char[sizeof(RTPResenderEntry) * fPacketArraySize];
		::memset(tempArray,0,sizeof(RTPResenderEntry) * fPacketArraySize);
		::memcpy(tempArray,fPacketArray,sizeof(RTPResenderEntry) * fPacketsInList);
		delete [] fPacketArray;
		fPacketArray = tempArray;
		//printf("NewArray size=%ld packetsInList=%ld\n",fPacketArraySize, fPacketsInList);
	}

	if (fPacketsInList <  fPacketArraySize) // have an open spot
	{	theEntry = &fPacketArray[fPacketsInList];
		fPacketsInList++;
		
		if (fPacketsInList < fPacketArraySize)
			fLastUsed = fPacketsInList;
		else
			fLastUsed = fPacketArraySize;
	}
	else 
	{
		// nothing open so re-use 
		if (fLastUsed < fPacketArraySize - 1)
			fLastUsed ++;
		else
			fLastUsed = 0;
			
		//printf("array is full = %lu reusing index=%lu\n",fPacketsInList,fLastUsed); 
		theEntry = &fPacketArray[fLastUsed];
		RemovePacket(fLastUsed, false); // delete packet in place don't fill we will use the spot
	}
			
	//
	// Check to see if this packet is too big for the buffer. If it is, then
	// we need to specially allocate a special buffer
	if (inPacketSize > kMaxDataBufferSize)
	{
		//sBufferPool.Put(theEntry->fPacketData);
		theEntry->fIsSpecialBuffer = true;
		theEntry->fPacketData = NEW char[inPacketSize];
	}
	else// It is not special, it's from the buffer pool
	{	theEntry->fIsSpecialBuffer = false;
		theEntry->fPacketData = sBufferPool.Get();
	}

	

	return theEntry;
}


void RTPPacketResender::ClearOutstandingPackets()
{	
	//OSMutexLocker packetQLocker(&fPacketQMutex);
	//for (UInt16 packetIndex = 0; packetIndex < fPacketArraySize; packetIndex++) //Testing purposes
	for (UInt16 packetIndex = 0; packetIndex < fPacketsInList; packetIndex++)
	{
		this->RemovePacket(packetIndex,false);// don't move packets delete in place
		Assert(fPacketArray[packetIndex].fPacketSize==0);
	}
	if (fBandwidthTracker != NULL)
		fBandwidthTracker->EmptyWindow(fBandwidthTracker->BytesInList()); //clean it out
	fPacketsInList = 0; // deleting in place doesn't decrement
	
	Assert(fPacketsInList == 0);
}

void RTPPacketResender::AddPacket( void * inRTPPacket, UInt32 packetSize, SInt32 ageLimit )
{
	//OSMutexLocker packetQLocker(&fPacketQMutex);
	// the caller needs to adjust the overall age limit by reducing it
	// by the current packet lateness.
	
	// we compute a re-transmit timeout based on the Karns RTT esmitate
	
	UInt16* theSeqNumP = (UInt16*)inRTPPacket;
	UInt16 theSeqNum = ntohs(theSeqNumP[1]);
	
	if ( ageLimit > 0 )
	{	
		RTPResenderEntry* theEntry = this->GetEmptyEntry(theSeqNum, packetSize);

		//
		// This may happen if this sequence number has already been added.
		// That may happen if we have repeat packets in the stream.
		if (theEntry == NULL || theEntry->fPacketSize > 0)
			return;
			
		//
		// Reset all the information in the RTPResenderEntry
		::memcpy(theEntry->fPacketData, inRTPPacket, packetSize);
		theEntry->fPacketSize = packetSize;
		theEntry->fAddedTime = OS::Milliseconds();
		theEntry->fOrigRetransTimeout = fBandwidthTracker->CurRetransmitTimeout();
		theEntry->fExpireTime = theEntry->fAddedTime + ageLimit;
		theEntry->fNumResends = 0;
		theEntry->fSeqNum = theSeqNum;
		
		//
		// Track the number of wasted bytes we have
		atomic_add(&sNumWastedBytes, kMaxDataBufferSize - packetSize);
		
		//PLDoubleLinkedListNode<RTPResenderEntry> * listNode = NEW PLDoubleLinkedListNode<RTPResenderEntry>( new RTPResenderEntry(inRTPPacket, packetSize, ageLimit, fRTTEstimator.CurRetransmitTimeout() ) );
		//fAckList.AddNodeToTail(listNode);
		fBandwidthTracker->FillWindow(packetSize);
	}
	else
	{
#if RTP_PACKET_RESENDER_DEBUGGING	
		this->logprintf( "packet too old to add: seq# %li, age limit %li, cur late %li, track id %li\n", (long)ntohs( *((UInt16*)(((char*)inRTPPacket)+2)) ), (long)ageLimit, fCurrentPacketDelay, fTrackID );
#endif
		fNumExpired++;
	}
	fNumSent++;
}

void RTPPacketResender::AckPacket( UInt16 inSeqNum, SInt64& inCurTimeInMsec )
{
	//OSMutexLocker packetQLocker(&fPacketQMutex);
	
	SInt32 foundIndex = -1;
	for (UInt32 packetIndex = 0 ;packetIndex < fPacketsInList; packetIndex++)
	{	if (inSeqNum == fPacketArray[packetIndex].fSeqNum)
		{	foundIndex = packetIndex;
			break;
		}
	}
	
	RTPResenderEntry* theEntry = NULL;
	if (foundIndex != -1)
		theEntry = &fPacketArray[foundIndex];


	if (theEntry == NULL || theEntry->fPacketSize == 0 )
	{	/* 	we got an ack for a packet that has already expired or
			for a packet whose re-transmit crossed with it's original ack
	
		*/
#if RTP_PACKET_RESENDER_DEBUGGING	
		this->logprintf( "acked packet not found: %li, track id %li, OS::MSecs %li\n"
		, (long)inSeqNum, fTrackID, (long)OS::Milliseconds()
		 );
#endif
		fNumAcksForMissingPackets++;
		//printf("Ack for missing packet: %d\n", inSeqNum);
		 
		 // hmm.. we -should not have- closed down the window in this case
		 // so reopen it a bit as we normally would.
		 // ?? who know's what it really was, just use kMaximumSegmentSize
	 	fBandwidthTracker->EmptyWindow( RTPBandwidthTracker::kMaximumSegmentSize, false );

		// when we don't add an estimate from re-transmitted segments we're actually *underestimating* 
		// both the variation and srtt since we're throwing away ALL estimates above the current RTO!
		// therefore it's difficult for us to rapidly adapt to increases in RTT, as well as RTT that
		// are higher than our original RTO estimate.
		
		// for duplicate acks, use 1.5x the cur RTO as the RTT sample
		//fRTTEstimator.AddToEstimate( fRTTEstimator.CurRetransmitTimeout() * 3 / 2 );
		/// this results in some very very big RTO's since the dupes come in batches of maybe 10 or more!
		
//		printf("Got ack for expired packet %d\n", inSeqNum);
	}
	else
	{

#if RTP_PACKET_RESENDER_DEBUGGING
		Assert(inSeqNum == theEntry->fSeqNum);
		this->logprintf( "Ack for packet: %li, track id %li, OS::MSecs %qd\n"
		, (long)inSeqNum, fTrackID, OS::Milliseconds()
		 );
#endif		
		fBandwidthTracker->EmptyWindow(theEntry->fPacketSize);
		if ( theEntry->fNumResends == 0 )
		{
			// add RTT sample...		
			// only use rtt from packets acked after their initial send, do not use
			// estimates gatherered from re-trasnmitted packets.
			//fRTTEstimator.AddToEstimate( theEntry->fPacketRTTDuration.DurationInMilliseconds() );
			fBandwidthTracker->AddToRTTEstimate( inCurTimeInMsec - theEntry->fAddedTime );
		
//			printf("Got ack for packet %d RTT = %qd\n", inSeqNum, inCurTimeInMsec - theEntry->fAddedTime);
		}
		else
		{
	#if RTP_PACKET_RESENDER_DEBUGGING
			this->logprintf( "re-tx'd packet acked.  ack num : %li, pack seq #: %li, num resends %li, track id %li, size %li, OS::MSecs %qd\n" \
			, (long)inSeqNum, (long)ntohs( *((UInt16*)(((char*)theEntry->fPacketData)+2)) ), (long)theEntry->fNumResends
			, (long)fTrackID, theEntry->fPacketSize, OS::Milliseconds() );
	#endif
		}
		this->RemovePacket(foundIndex);
	}
}

void RTPPacketResender::RemovePacket(UInt32 packetIndex, Bool16 reuseIndex)
{
	//OSMutexLocker packetQLocker(&fPacketQMutex);

	Assert(packetIndex < fPacketArraySize);
	if (packetIndex >= fPacketArraySize)
		return;
		
	if (fPacketsInList == 0)
		return;
		
	RTPResenderEntry* theEntry = &fPacketArray[packetIndex];
	if (theEntry->fPacketSize == 0)
		return;
		
	//
	// Track the number of wasted bytes we have
	atomic_sub(&sNumWastedBytes, kMaxDataBufferSize - theEntry->fPacketSize);
	Assert(theEntry->fPacketSize > 0);

	//
	// Update our list information
	Assert(fPacketsInList > 0);
	
	if (theEntry->fIsSpecialBuffer)
	{	delete [] (char*)theEntry->fPacketData;
	}
	else if (theEntry->fPacketData != NULL)
		sBufferPool.Put(theEntry->fPacketData);
		

	if (reuseIndex) // we are re-using the space so keep array contiguous
	{
		fPacketArray[packetIndex] = fPacketArray[fPacketsInList -1]; 
		::memset(&fPacketArray[fPacketsInList -1],0,sizeof(RTPResenderEntry));
		fPacketsInList--;

	}
	else	// the array is full
	{
		fBandwidthTracker->EmptyWindow( theEntry->fPacketSize, false ); // keep window available
		::memset(theEntry,0,sizeof(RTPResenderEntry));
	}
	
}

void RTPPacketResender::ResendDueEntries()
{
	if (fPacketsInList <= 0)
		return;
		
	//OSMutexLocker packetQLocker(&fPacketQMutex);
	//
	SInt32 numResends = 0;
	RTPResenderEntry* theEntry = NULL; 
	SInt64 curTime = OS::Milliseconds();
	for (SInt32 packetIndex = fPacketsInList -1; packetIndex >= 0; packetIndex--) // walk backwards because remove packet moves array members forward
	{
		theEntry = &fPacketArray[packetIndex];
		
		if (theEntry->fPacketSize == 0) 
			continue;
		
		if ((curTime - theEntry->fAddedTime) > fBandwidthTracker->CurRetransmitTimeout())
		{
			// Change:  Only expire packets after they were due to be resent. This gives the client
			// a chance to ack them and improves congestion avoidance and RTT calculation
			if (curTime > theEntry->fExpireTime)
			{
	#if RTP_PACKET_RESENDER_DEBUGGING	
				unsigned char version;
				version = *((char*)theEntry->fPacketData);
				version &= 0x84; 	// grab most sig 2 bits
				version = version >> 6;	// shift by 6 bits
				this->logprintf( "expired:  seq number %li, track id %li (port: %li), vers # %li, pack seq # %li, size: %li, OS::Msecs: %qd\n", \
									(long)ntohs( *((UInt16*)(((char*)theEntry->fPacketData)+2)) ), fTrackID,  (long) ntohs(fDestPort), \
									(long)version, (long)ntohs( *((UInt16*)(((char*)theEntry->fPacketData)+2))), theEntry->fPacketSize, OS::Milliseconds() );
	#endif
				//
				// This packet is expired
				fNumExpired++;
				//printf("Packet expired: %d\n", ((UInt16*)thePacket)[1]);
	                        fBandwidthTracker->EmptyWindow(theEntry->fPacketSize);
				this->RemovePacket(packetIndex);
//				printf("Expired packet %d\n", theEntry->fSeqNum);
				continue;
			}
			
			// Resend this packet
			fSocket->SendTo(fDestAddr, fDestPort, theEntry->fPacketData, theEntry->fPacketSize);
			//printf("Packet resent: %d\n", ((UInt16*)theEntry->fPacketData)[1]);

			theEntry->fNumResends++;
	#if RTP_PACKET_RESENDER_DEBUGGING	
			this->logprintf( "re-sent: %li RTO %li, track id %li (port %li), size: %li, OS::Ms %qd\n", (long)ntohs( *((UInt16*)(((char*)theEntry->fPacketData)+2)) ),  curTime - theEntry->fAddedTime, \
					fTrackID, (long) ntohs(fDestPort) \
					, theEntry->fPacketSize, OS::Milliseconds());
	#endif		

			fNumResends++;
			
			numResends ++;
			//printf("resend loop numResends=%ld packet theEntry->fNumResends=%ld stream fNumResends=\n",numResends,theEntry->fNumResends++, fNumResends);
						
			// ok -- lets try this.. add 1.5x of the INITIAL duration since the last send to the rto estimator
			// since we won't get an ack on this packet
			// this should keep us from exponentially increasing due o a one time increase
			// in the actuall rtt, only AddToEstimate on the first resend ( assume that it's a dupe )
			// if it's not a dupe, but rather an actual loss, the subseqnuent actuals wil bring down the average quickly
			
			if ( theEntry->fNumResends == 1 )
				fBandwidthTracker->AddToRTTEstimate( (theEntry->fOrigRetransTimeout  * 3) / 2 );
			
//			printf("Retransmitted packet %d\n", theEntry->fSeqNum);
			theEntry->fAddedTime = curTime;
			fBandwidthTracker->AdjustWindowForRetransmit();
			continue;
		}
		
	}
}
void RTPPacketResender::RemovePacket(RTPResenderEntry* inEntry){ Assert(0); }
