/* Copyright (c) 2002-2012 Croteam Ltd. All rights reserved. */

#include "stdh.h"

#include <Engine/Base/CTString.h>
#include <Engine/Base/Console.h>
#include <Engine/Base/ErrorReporting.h>
#include <Engine/Base/ErrorTable.h>
#include <Engine/Base/Lists.h>
#include <Engine/Base/Stream.h>
#include <Engine/Base/Translation.h>

#include <Engine/Network/ClientInterface.h>
#include <Engine/Network/CPacket.h>

#include <Engine/Base/Listiterator.inl>

// how many acknowledges can fit into one UDP packet
#define MAX_ACKS_PER_PACKET (MAX_UDP_BLOCK_SIZE/sizeof(ULONG))

extern FLOAT net_fDropPackets;
extern INDEX net_bReportPackets;

CClientInterface::CClientInterface(void)
{
 Clear();
};

CClientInterface::~CClientInterface(void)
{
 Clear();
};


void CClientInterface::Clear(void) 
{
	ci_bUsed = FALSE;

	ci_bReliableComplete = FALSE;
	ci_pbInputBuffer.Clear();
	ci_pbOutputBuffer.Clear();
	ci_pbReliableInputBuffer.Clear();
	ci_pbWaitAckBuffer.Clear();

	ci_adrAddress.Clear();
	ci_strAddress = "";
	
	ci_pciOther = NULL;
	ci_ulSequence = 0;
};

// mark the client interface as local for this computer
void CClientInterface::SetLocal(CClientInterface *pciOther)
{
  Clear();

	ci_bUsed = TRUE;

  ci_bClientLocal = TRUE;
  ci_pciOther = pciOther;
  if (pciOther!=NULL) {
    pciOther->ci_pciOther = this;
  }

	ci_adrAddress.Clear();
		
};

// send a message through this client interface - reliable messages are not limited in size
void CClientInterface::Send(const void *pvSend, SLONG slSize,BOOL bReliable)
{
	ASSERT (ci_bUsed == TRUE);
	ASSERT(pvSend != NULL && slSize>0);
	// unreliable messages must fit within one UDP packet
	ASSERT(bReliable != UDP_PACKET_UNRELIABLE || slSize < MAX_UDP_BLOCK_SIZE);

	UBYTE ubPacketReliable;
	UBYTE* pubData;
	SLONG slSizeToSend;
  SLONG slTransferSize;
	ULONG ulSequence;
	CPacket* ppaNewPacket;

	//if the message is reliable, make sure the first packet is marked as a head of the message
	if (bReliable) {
		ubPacketReliable = UDP_PACKET_RELIABLE | UDP_PACKET_RELIABLE_HEAD;
		if (slSize <= MAX_UDP_BLOCK_SIZE) {
			ubPacketReliable |= UDP_PACKET_RELIABLE_TAIL;
		}
	} else {
		ubPacketReliable = UDP_PACKET_UNRELIABLE;
	}

	pubData = (UBYTE*) pvSend;
	slSizeToSend = slSize;
  slTransferSize = slSizeToSend;
	
	
	// split large reliable messages into packets, and put them in the output buffer
	while (slSizeToSend>MAX_UDP_BLOCK_SIZE) {
		ppaNewPacket = new CPacket;

		// for each packet, increment the sequence (very important)
		ulSequence = (++ci_ulSequence);
		ppaNewPacket->WriteToPacket(pubData,MAX_UDP_BLOCK_SIZE,ubPacketReliable,ulSequence,ci_adrAddress.adr_uwID,slTransferSize);
		ppaNewPacket->pa_adrAddress.adr_ulAddress = ci_adrAddress.adr_ulAddress;
		ppaNewPacket->pa_adrAddress.adr_uwPort = ci_adrAddress.adr_uwPort;
		ppaNewPacket->pa_adrAddress.adr_uwID = ci_adrAddress.adr_uwID;
		ci_pbOutputBuffer.AppendPacket(*ppaNewPacket,TRUE);

		// turn off udp head flag, if exists (since we just put a packet in the output buffer, the next 
		// packet cannot be the head
		ubPacketReliable &= UDP_PACKET_RELIABLE;

		slSizeToSend -=  MAX_UDP_BLOCK_SIZE;
		pubData += MAX_UDP_BLOCK_SIZE;
	}

	// what remains is a tail of a reliable message, or an unreliable packet
	if (ubPacketReliable != UDP_PACKET_UNRELIABLE) {
		ubPacketReliable |= UDP_PACKET_RELIABLE_TAIL;
	}

	// so send it
	ppaNewPacket = new CPacket;

	ulSequence = (++ci_ulSequence);
	ppaNewPacket->WriteToPacket(pubData,slSizeToSend,ubPacketReliable,ulSequence,ci_adrAddress.adr_uwID,slTransferSize);
	ppaNewPacket->pa_adrAddress.adr_ulAddress = ci_adrAddress.adr_ulAddress;
	ppaNewPacket->pa_adrAddress.adr_uwPort = ci_adrAddress.adr_uwPort;
	ppaNewPacket->pa_adrAddress.adr_uwID = ci_adrAddress.adr_uwID;
	ci_pbOutputBuffer.AppendPacket(*ppaNewPacket,TRUE);

  
};

// send a message through this client interface, to the provided address
void CClientInterface::SendTo(const void *pvSend, SLONG slSize,const CAddress adrAdress,BOOL bReliable)
{
	ASSERT (ci_bUsed);
	ASSERT(pvSend != NULL && slSize>0);
	// unreliable packets must fit within one UDP packet
	ASSERT(bReliable != UDP_PACKET_UNRELIABLE || slSize < MAX_UDP_BLOCK_SIZE);

	UBYTE ubPacketReliable;
	UBYTE* pubData;
	SLONG slSizeToSend;
  SLONG slTransferSize;
	ULONG ulSequence;
	CPacket* ppaNewPacket;

	//if the message is reliable, make sure the first packet is marked as a head of the message
	if (bReliable) {
		ubPacketReliable = UDP_PACKET_RELIABLE | UDP_PACKET_RELIABLE_HEAD;
		if (slSize <= MAX_UDP_BLOCK_SIZE) {
			ubPacketReliable |= UDP_PACKET_RELIABLE_TAIL;
		}
	} else {
		ubPacketReliable = UDP_PACKET_UNRELIABLE;
	}

	pubData = (UBYTE*) pvSend;
	slSizeToSend = slSize;
  slTransferSize = slSizeToSend;
	

	// split large reliable messages into packets, and put them in the output buffer
	while (slSizeToSend>MAX_UDP_BLOCK_SIZE) {
		ppaNewPacket = new CPacket;

		// for each packet, increment the sequence (very important)
		ulSequence = (++ci_ulSequence);
		ppaNewPacket->WriteToPacket(pubData,MAX_UDP_BLOCK_SIZE,ubPacketReliable,ulSequence,adrAdress.adr_uwID,slTransferSize);
		ppaNewPacket->pa_adrAddress.adr_ulAddress = adrAdress.adr_ulAddress;
		ppaNewPacket->pa_adrAddress.adr_uwPort = adrAdress.adr_uwPort;
		ppaNewPacket->pa_adrAddress.adr_uwID = adrAdress.adr_uwID;
		ci_pbOutputBuffer.AppendPacket(*ppaNewPacket,TRUE);

		// turn off udp head flag, if exists (since we just put a packet in the output buffer, the next 
		// packet cannot be the head
		ubPacketReliable &= UDP_PACKET_RELIABLE;

		slSizeToSend -=  MAX_UDP_BLOCK_SIZE;
		pubData += MAX_UDP_BLOCK_SIZE;
	}

	// what remains is a tail of a reliable message, or an unreliable packet
	if (ubPacketReliable != UDP_PACKET_UNRELIABLE) {
		ubPacketReliable |= UDP_PACKET_RELIABLE_TAIL;
	}

	ppaNewPacket = new CPacket;

	ulSequence = (++ci_ulSequence);
	ppaNewPacket->WriteToPacket(pubData,slSizeToSend,ubPacketReliable,ulSequence,adrAdress.adr_uwID,slTransferSize);
	ppaNewPacket->pa_adrAddress.adr_ulAddress = adrAdress.adr_ulAddress;
	ppaNewPacket->pa_adrAddress.adr_uwPort = adrAdress.adr_uwPort;
	ppaNewPacket->pa_adrAddress.adr_uwID = adrAdress.adr_uwID;
	ci_pbOutputBuffer.AppendPacket(*ppaNewPacket,TRUE);
};


// receive a message through the interface, discard originating address
BOOL CClientInterface::Receive(void *pvReceive, SLONG &slSize,BOOL bReliable)
{
	ASSERT (slSize>0);
	ASSERT (pvReceive != NULL);

	// we'll use the other receive procedure, and tell it to ignore the address
	return ReceiveFrom(pvReceive,slSize,NULL,bReliable);
};

// receive a message through the interface, and fill in the originating address
BOOL CClientInterface::ReceiveFrom(void *pvReceive, SLONG &slSize, CAddress *padrAdress,BOOL bReliable)
{
	CPacket* ppaPacket;
	UBYTE* pubData = (UBYTE*) pvReceive;
	SLONG slDummySize;
	UBYTE ubReliable;

	// if a reliable message is requested
	if (bReliable) {
		// if there is no complete reliable message ready
		if (ci_pbReliableInputBuffer.CheckSequence(slDummySize) == FALSE) {
			return FALSE;
		// if the ready message is longer than the expected size
		} else if ( GetCurrentReliableSize() > slSize) {
			return FALSE;
		// if everything is ok, compose the message and kill the packets
		} else {
			// fill in the originating address (if necessary)
			if (padrAdress != NULL) {
				ppaPacket = ci_pbReliableInputBuffer.PeekFirstPacket();
				padrAdress->adr_ulAddress = ppaPacket->pa_adrAddress.adr_ulAddress;
				padrAdress->adr_uwPort = ppaPacket->pa_adrAddress.adr_uwPort;
				padrAdress->adr_uwID = ppaPacket->pa_adrAddress.adr_uwID;
			}

			slSize = 0;
			do {
				ppaPacket = ci_pbReliableInputBuffer.GetFirstPacket();
				ubReliable = ppaPacket->pa_ubReliable;
				slDummySize = ppaPacket->pa_slSize - MAX_HEADER_SIZE;
				ppaPacket->ReadFromPacket(pubData,slDummySize);
				pubData += slDummySize;
				slSize += slDummySize;
				delete ppaPacket;
			} while (!(ubReliable & UDP_PACKET_RELIABLE_TAIL));
			return TRUE;
		}
	// if an unreliable message is requested
	} else {
		// if there are no packets in the input buffer, return
		if (ci_pbInputBuffer.pb_ulNumOfPackets == 0) {
			return FALSE;
		}
		ppaPacket = ci_pbInputBuffer.PeekFirstPacket();
		// if the reliable buffer is not empty, nothing can be accepted from the input buffer
		// because it would be accepted out-of order (before earlier sequences have been read)
		if (ci_pbReliableInputBuffer.pb_ulNumOfPackets != 0) {
			return FALSE;
		// if the first packet in the input buffer is not unreliable
		} else if (ppaPacket->pa_ubReliable != UDP_PACKET_UNRELIABLE) {
			return FALSE;
		// if the ready message is longer than the expected size
		} else if ( ppaPacket->pa_slTransferSize > slSize) {
			return FALSE;
		// if everything is ok, read the packet data, and kill the packet
		} else {
			// fill in the originating address (if necessary)
			if (padrAdress != NULL) {
				padrAdress->adr_ulAddress = ppaPacket->pa_adrAddress.adr_ulAddress;
				padrAdress->adr_uwPort = ppaPacket->pa_adrAddress.adr_uwPort;
				padrAdress->adr_uwID = ppaPacket->pa_adrAddress.adr_uwID;
			}
			slSize = ppaPacket->pa_slSize - MAX_HEADER_SIZE;
			ppaPacket->ReadFromPacket(pubData,slSize);
			// remove the packet from the buffer, and delete it from memory
			ci_pbInputBuffer.RemoveFirstPacket(TRUE); 
			return TRUE;
		}
		 
	}

	return FALSE;
};


// receive a message through the interface, discard originating address
BOOL CClientInterface::Receive(CTStream &strmReceive,UBYTE bReliable)
{
	CPacket* ppaPacket;
	UBYTE ubReliable;
  SLONG slDummySize;

	// if a reliable message is requested
	if (bReliable) {
		// if there is no complete reliable message ready
		if (ci_pbReliableInputBuffer.CheckSequence(slDummySize) == FALSE) {
			return FALSE;	
		// if everything is ok, compose the message and kill the packets
		} else {
			do {
				ppaPacket = ci_pbReliableInputBuffer.GetFirstPacket();
				ubReliable = ppaPacket->pa_ubReliable;
				strmReceive.Write_t(ppaPacket->pa_pubPacketData + MAX_HEADER_SIZE,ppaPacket->pa_slSize - MAX_HEADER_SIZE);
        if (ci_pbInputBuffer.pb_ulLastSequenceOut < ppaPacket->pa_ulSequence) {
          ci_pbInputBuffer.pb_ulLastSequenceOut = ppaPacket->pa_ulSequence;
        }
				delete ppaPacket;
			} while (!(ubReliable & UDP_PACKET_RELIABLE_TAIL));
			return TRUE;
		}
	// if an unreliable message is requested
	} else {
		ppaPacket = ci_pbInputBuffer.PeekFirstPacket();
		// if the reliable buffer is not empty, nothing can be accepted from the input buffer
		// because it would be accepted out-of order (before earlier sequences have been read)
		if (ci_pbReliableInputBuffer.pb_ulNumOfPackets != 0) {
			return FALSE;
		// if the first packet in the input buffer is not unreliable
		} else if (ppaPacket->pa_ubReliable != UDP_PACKET_RELIABLE) {
			return FALSE;
		// if everything is ok, read the packet data, and kill the packet
		} else {
			strmReceive.Write_t(ppaPacket->pa_pubPacketData + MAX_HEADER_SIZE,ppaPacket->pa_slSize - MAX_HEADER_SIZE);
			// remove the packet from the buffer, and delete it from memory
      if (ci_pbInputBuffer.pb_ulLastSequenceOut < ppaPacket->pa_ulSequence) {
        ci_pbInputBuffer.pb_ulLastSequenceOut = ppaPacket->pa_ulSequence;
      }
			ci_pbInputBuffer.RemoveFirstPacket(TRUE); 			
			return TRUE;
		}
		 
	}

	return FALSE;
};


// exchanges packets beetween this socket and it's local partner
// from output of this buffet to the input of the other and vice versa
void CClientInterface::ExchangeBuffers(void)
{
	ASSERT (ci_pciOther != NULL);
	CPacket* ppaPacket;
	CTimerValue tvNow = _pTimer->GetHighPrecisionTimer();

	// take the output from this interface and give it to it's partner socket
	while (ci_pbOutputBuffer.pb_ulNumOfPackets > 0) {
		ppaPacket = ci_pbOutputBuffer.PeekFirstPacket();
		if (ppaPacket->pa_tvSendWhen < tvNow) {
			ci_pbOutputBuffer.RemoveFirstPacket(FALSE);
			if (ci_pciOther->ci_pbInputBuffer.InsertPacket(*ppaPacket,FALSE) == FALSE) {
				delete ppaPacket;
			}
		} else {
			break;
		}
	}

	// and the other way around
	while (ci_pciOther->ci_pbOutputBuffer.pb_ulNumOfPackets > 0) {
		ppaPacket = ci_pciOther->ci_pbOutputBuffer.PeekFirstPacket();
		if (ppaPacket->pa_tvSendWhen < tvNow) {
			ppaPacket = ci_pciOther->ci_pbOutputBuffer.GetFirstPacket();
			if (ci_pbInputBuffer.InsertPacket(*ppaPacket,FALSE) == FALSE) {
				delete ppaPacket;
			};

		}	else {
			break;
		}
	}

};


// update interface's input buffer (transfer from input buffer to the reliable buffer...),
// for incoming acknowledge packets, remove acknowledged packets from the output buffers,
// and generate acknowledge messages for incoming reliable packets
BOOL CClientInterface::UpdateInputBuffers(void)
{
	BOOL bSomethingDone;
	ULONG pulGenAck[MAX_ACKS_PER_PACKET];
	ULONG ulAckCount=0;
	CTimerValue tvNow;
		
	// if there are packets in the input buffer, process them
	FORDELETELIST(CPacket,pa_lnListNode,ci_pbInputBuffer.pb_lhPacketStorage,ppaPacket) {
		CPacket &paPacket = *ppaPacket;
		
			// if it's an acknowledge packet, remove the acknowledged packets from the wait acknowledge buffer
			if (ppaPacket->pa_ubReliable & UDP_PACKET_ACKNOWLEDGE) {
				ULONG *pulAck;
				SLONG slSize;
				ULONG ulSequence;

				slSize = ppaPacket->pa_slSize - MAX_HEADER_SIZE;
				// if slSize isn't rounded to the size of ulSequence, abort 
				ASSERT (slSize % sizeof(ULONG) == 0);

				// get the pointer to the start of acknowledged sequences
				pulAck = (ULONG*) (ppaPacket->pa_pubPacketData + MAX_HEADER_SIZE);
				// for each acknowledged sequence number
				while (slSize>0) {
					ulSequence = *pulAck;
					
					// report the packet info to the console
					if (net_bReportPackets == TRUE) {
						tvNow = _pTimer->GetHighPrecisionTimer();
						CPrintF("%lu: Received acknowledge for packet sequence %d\n",(ULONG) tvNow.GetMilliseconds(),ulSequence);
					}
					
					// remove the matching packet from the wait acknowledge buffer
					ci_pbWaitAckBuffer.RemovePacket(ulSequence,TRUE);
					// if the packet is waiting to be resent it's in the outgoing buffer, so remove it
					ci_pbOutputBuffer.RemovePacket(ulSequence,TRUE);
					pulAck++;
					slSize -= sizeof(ULONG);
				}

				// take this packet out of the input buffer and kill it
				ci_pbInputBuffer.RemovePacket(ppaPacket->pa_ulSequence,FALSE);
				delete ppaPacket;
				
				bSomethingDone = TRUE;
			// if the packet is reliable
			} else if (ppaPacket->pa_ubReliable & UDP_PACKET_RELIABLE) {
				
				// generate packet acknowledge
				// if the packet is from the broadcast address, send the acknowledge for that packet only
				if (ppaPacket->pa_adrAddress.adr_uwID == '//' || ppaPacket->pa_adrAddress.adr_uwID == 0) {
					CPacket *ppaAckPacket = new CPacket;
					ppaAckPacket->pa_adrAddress.adr_ulAddress = ppaPacket->pa_adrAddress.adr_ulAddress;
					ppaAckPacket->pa_adrAddress.adr_uwPort = ppaPacket->pa_adrAddress.adr_uwPort;
					ppaAckPacket->WriteToPacket(&(ppaPacket->pa_ulSequence),sizeof(ULONG),UDP_PACKET_ACKNOWLEDGE,++ci_ulSequence,'//',sizeof(ULONG));
					ci_pbOutputBuffer.AppendPacket(*ppaAckPacket,TRUE);
					if (net_bReportPackets == TRUE) {
						CPrintF("Acknowledging broadcast packet sequence %d\n",ppaPacket->pa_ulSequence);
					}
					ci_pbInputBuffer.RemovePacket(ppaPacket->pa_ulSequence,FALSE);
				}	else {
					// if we have filled the packet to the maximum with acknowledges (an extremely rare event)
					// finish this packet and start the next one
					if (ulAckCount == MAX_ACKS_PER_PACKET) {
						CPacket *ppaAckPacket = new CPacket;
						ppaAckPacket->pa_adrAddress.adr_ulAddress = ci_adrAddress.adr_ulAddress;
						ppaAckPacket->pa_adrAddress.adr_uwPort = ci_adrAddress.adr_uwPort;
						ppaAckPacket->WriteToPacket(pulGenAck,ulAckCount*sizeof(ULONG),UDP_PACKET_ACKNOWLEDGE,++ci_ulSequence,ci_adrAddress.adr_uwID,ulAckCount*sizeof(ULONG));
						ci_pbOutputBuffer.AppendPacket(*ppaAckPacket,TRUE);
						ulAckCount = 0;
					}	
					// add the acknowledge for this packet
					pulGenAck[ulAckCount] = ppaPacket->pa_ulSequence;

					// report the packet info to the console
					if (net_bReportPackets == TRUE) {
						tvNow = _pTimer->GetHighPrecisionTimer();
						CPrintF("%lu: Acknowledging packet sequence %d\n",(ULONG) tvNow.GetMilliseconds(),ppaPacket->pa_ulSequence);
					}

					ulAckCount++;
				}
			
				// take this packet out of the input buffer
				ci_pbInputBuffer.RemovePacket(ppaPacket->pa_ulSequence,FALSE);		

        if (ppaPacket->pa_ulSequence == 8) {
          ppaPacket->pa_ulSequence = 8;
        }
				// a packet can be accepted from the broadcast ID only if it is an acknowledge packet or 
				// if it is a connection confirmation response packet and the client isn't already connected
				if (ppaPacket->pa_adrAddress.adr_uwID == '//' || ppaPacket->pa_adrAddress.adr_uwID == 0) {
					if  (((!ci_bUsed) && (ppaPacket->pa_ubReliable & UDP_PACKET_CONNECT_RESPONSE)) ||
						 (ppaPacket->pa_ubReliable & UDP_PACKET_ACKNOWLEDGE) || ci_bClientLocal) {

           /* if (ci_pbReliableInputBuffer.pb_ulLastSequenceOut >= ppaPacket->pa_ulSequence) {
              delete ppaPacket;
            } else*/ 
            ppaPacket->pa_ulSequence = 0;
            if (ci_pbReliableInputBuffer.InsertPacket(*ppaPacket,FALSE) == FALSE) {
							delete ppaPacket;
						}
					}	else {
						delete ppaPacket;
					}
					// reject duplicates
				} else if (ppaPacket->pa_ulSequence > ci_pbReliableInputBuffer.pb_ulLastSequenceOut &&
					!(ci_pbReliableInputBuffer.IsSequenceInBuffer(ppaPacket->pa_ulSequence))) {
					if (ci_pbReliableInputBuffer.InsertPacket(*ppaPacket,FALSE) == FALSE) {
						delete ppaPacket;
					}				
				} else {
					delete ppaPacket;
				}

			// if the packet is unreliable, leave it in the input buffer
			// when it is needed, the message will be pulled from there
			} else {
	
			// reject duplicates
			ci_pbInputBuffer.RemovePacket(ppaPacket->pa_ulSequence,FALSE);
			if (ppaPacket->pa_ulSequence > ci_pbInputBuffer.pb_ulLastSequenceOut &&
					!(ci_pbReliableInputBuffer.IsSequenceInBuffer(ppaPacket->pa_ulSequence))) {						
				if (ci_pbInputBuffer.InsertPacket(*ppaPacket,FALSE) == FALSE) {
					delete ppaPacket;
				}				
			} else {
				delete ppaPacket;
			}

		}
	}

	// if there are any remaining unsent acknowldges, put them into a packet and send it
	if (ulAckCount >0) {
		CPacket *ppaAckPacket = new CPacket;
		ppaAckPacket->pa_adrAddress.adr_ulAddress = ci_adrAddress.adr_ulAddress;
		ppaAckPacket->pa_adrAddress.adr_uwPort = ci_adrAddress.adr_uwPort;
		ppaAckPacket->WriteToPacket(pulGenAck,ulAckCount*sizeof(ULONG),UDP_PACKET_ACKNOWLEDGE,++ci_ulSequence,ci_adrAddress.adr_uwID,ulAckCount*sizeof(ULONG));
		ci_pbOutputBuffer.AppendPacket(*ppaAckPacket,TRUE);
	}	
	return TRUE;

};


// update socket input buffer (transfer from input buffer to the reliable buffer...),
// for incoming acknowledge packets, remove acknowledged packets from the output buffers,
// and generate acknowledge messages for incoming reliable packets
// this method is different than the previous becoause it sends acknowledges for each 
// packet separately, instead of grouping them together
BOOL CClientInterface::UpdateInputBuffersBroadcast(void)
{
	BOOL bSomethingDone;
	CTimerValue tvNow;
	
	// if there are packets in the input buffer, process them
	FORDELETELIST(CPacket,pa_lnListNode,ci_pbInputBuffer.pb_lhPacketStorage,ppaPacket) {
		CPacket &paPacket = *ppaPacket;

			// if it's an acknowledge packet, remove the acknowledged packets from the wait acknowledge buffer
			if (ppaPacket->pa_ubReliable & UDP_PACKET_ACKNOWLEDGE) {
				ULONG *pulAck;
				SLONG slSize;
				ULONG ulSequence;

				slSize = ppaPacket->pa_slSize - MAX_HEADER_SIZE;
				// if slSize isn't rounded to the size of ulSequence, abort 
				ASSERT (slSize % sizeof(ULONG) == 0);

				// get the pointer to the start of acknowledged sequences
				pulAck = (ULONG*) (ppaPacket->pa_pubPacketData + MAX_HEADER_SIZE);
				// for each acknowledged sequence number
				while (slSize>0) {
					ulSequence = *pulAck;

					// report the packet info to the console
					if (net_bReportPackets == TRUE) {
						tvNow = _pTimer->GetHighPrecisionTimer();
						CPrintF("%lu: Received acknowledge for broadcast packet sequence %d\n",(ULONG) tvNow.GetMilliseconds(),ulSequence);
					}

					// remove the matching packet from the wait acknowledge buffer
					ci_pbWaitAckBuffer.RemovePacket(ulSequence,TRUE);
					// if the packet is waiting to be resent it's in the outgoing buffer, so remove it
					ci_pbOutputBuffer.RemovePacket(ulSequence,TRUE);
					pulAck++;
					slSize -= sizeof(ULONG);
				}

				ci_pbInputBuffer.RemovePacket(ppaPacket->pa_ulSequence,FALSE);
				bSomethingDone = TRUE;
				delete ppaPacket;
			// if the packet is reliable
			} else if (ppaPacket->pa_ubReliable & UDP_PACKET_RELIABLE) {
				
				// generate packet acknowledge (each reliable broadcast packet is acknowledged separately 
				// because the broadcast interface can receive packets from any number of different addresses
				CPacket *ppaAckPacket = new CPacket;
				ppaAckPacket->pa_adrAddress.adr_ulAddress = ppaPacket->pa_adrAddress.adr_ulAddress;
				ppaAckPacket->pa_adrAddress.adr_uwPort = ppaPacket->pa_adrAddress.adr_uwPort;
				ppaAckPacket->WriteToPacket(&(ppaPacket->pa_ulSequence),sizeof(ULONG),UDP_PACKET_ACKNOWLEDGE,ci_ulSequence++,ppaPacket->pa_adrAddress.adr_uwID,sizeof(ULONG));
				ci_pbOutputBuffer.AppendPacket(*ppaAckPacket,TRUE);

				// report the packet info to the console
				if (net_bReportPackets == TRUE) {
					tvNow = _pTimer->GetHighPrecisionTimer();
					CPrintF("%lu: Acknowledging broadcast packet sequence %d\n",(ULONG) tvNow.GetMilliseconds(),ppaPacket->pa_ulSequence);
				}

				ci_pbInputBuffer.RemovePacket(ppaPacket->pa_ulSequence,FALSE);
				if (ci_pbReliableInputBuffer.InsertPacket(*ppaPacket,FALSE) == FALSE) {
					delete ppaPacket;
				}				
			} else {
	
			// if the packet is unreliable, leave it in the input buffer
			// when it is needed, the message will be pulled from there
			// have to check for duplicates
	
			ci_pbInputBuffer.RemovePacket(ppaPacket->pa_ulSequence,FALSE);
			if (ppaPacket->pa_ulSequence > ci_pbInputBuffer.pb_ulLastSequenceOut &&
					!(ci_pbReliableInputBuffer.IsSequenceInBuffer(ppaPacket->pa_ulSequence))) {						
				if (ci_pbInputBuffer.InsertPacket(*ppaPacket,FALSE) == FALSE) {
					delete ppaPacket;
				}
			} else {
				delete ppaPacket;
			}

		}
	}

	return TRUE;
							
};


// take a look at the wait acknowledge buffer and resend any packets that heve reached the timeout
// if there is a packet that can't be sent sucessfully (RS_NOTATALL), signal it
BOOL CClientInterface::UpdateOutputBuffers(void)
{
	CPacket* ppaPacket;
	UBYTE ubRetry;

	// handle resends
	while (ci_pbWaitAckBuffer.pb_ulNumOfPackets > 0) {
		ppaPacket = ci_pbWaitAckBuffer.PeekFirstPacket();
		
		ubRetry = ppaPacket->CanRetry();
		switch (ubRetry) {
			// if it's time to retry sending the packet
			case RS_NOW: {	ci_pbWaitAckBuffer.RemoveFirstPacket(FALSE);
											ci_pbOutputBuffer.Retry(*ppaPacket);
											break;
									 }
			// if the packet cannot be sent now, no other packets can be sent, so exit
			case RS_NOTNOW: { return TRUE; }
			// if the packet has reached the retry limit - close the client's connection
			case RS_NOTATALL: { Clear(); 
													return FALSE;
												}
			

		}
	}
	return TRUE;
};


// get the next available packet from the output buffer
CPacket* CClientInterface::GetPendingPacket(void)
{
	CTimerValue tvNow = _pTimer->GetHighPrecisionTimer();

	if (ci_pbOutputBuffer.pb_ulNumOfPackets == 0) {
		return NULL;
	}

	CPacket* ppaPacket = ci_pbOutputBuffer.PeekFirstPacket();
	
	// if it's time to send the packet
	if (ppaPacket->pa_tvSendWhen <= tvNow) {
		ci_pbOutputBuffer.RemoveFirstPacket(FALSE);
		return ppaPacket;
	}

	return NULL;

};


// reads the expected size of current realiable message in the reliable input buffer
SLONG CClientInterface::GetExpectedReliableSize(void)
{
  if (ci_pbReliableInputBuffer.pb_ulNumOfPackets == 0) {
    return 0;
  }
  CPacket* ppaPacket = ci_pbReliableInputBuffer.PeekFirstPacket();
	return ppaPacket->pa_slTransferSize;
};

// reads the expected size of current realiable message in the reliable input buffer
SLONG CClientInterface::GetCurrentReliableSize(void)
{
  SLONG slSize;
  ci_pbReliableInputBuffer.CheckSequence(slSize);
	return slSize;
};