//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------

#include "core/dnet.h"
#include "console/console.h"
#include "core/bitStream.h"
#include "console/consoleTypes.h"

bool gLogToConsole = false;

S32 gNetBitsReceived = 0;

enum NetPacketType
{
   DataPacket,
   PingPacket,
   AckPacket,
   InvalidPacketType,
};

static const char *packetTypeNames[] =
{
   "DataPacket",
   "PingPacket",
   "AckPacket",
};

//-----------------------------------------------------------------
//-----------------------------------------------------------------
//-----------------------------------------------------------------
ConsoleFunction(DNetSetLogging, void, 2, 2, "(bool enabled)")
{
   argc;
   gLogToConsole = dAtob(argv[1]);
}

ConnectionProtocol::ConnectionProtocol()
{
   mLastSeqRecvd = 0;
   mHighestAckedSeq = 0;
   mLastSendSeq = 0; // start sending at 1
   mAckMask = 0;
   mLastRecvAckAck = 0;
}
void ConnectionProtocol::buildSendPacketHeader(BitStream *stream, S32 packetType)
{
   S32 ackByteCount = ((mLastSeqRecvd - mLastRecvAckAck + 7) >> 3);
   AssertFatal(ackByteCount <= 4, "Too few ack bytes!");

   S32 headerSize = 3 + ackByteCount;
   S32 datalen = 0;

   if(packetType == DataPacket)
      mLastSendSeq++;

   stream->writeFlag(true);
   stream->writeInt(mConnectSequence & 1, 1);
   stream->writeInt(mLastSendSeq, 9);
   stream->writeInt(mLastSeqRecvd, 9);
   stream->writeInt(packetType, 2);
   stream->writeInt(ackByteCount, 3);
   stream->writeInt(mAckMask, ackByteCount * 8);

   // if we're resending this header, we can't advance the
   // sequence recieved (in case this packet drops and the prev one
   // goes through)

   if(gLogToConsole)
      Con::printf("build hdr %d %d", mLastSendSeq, packetType);

   if(packetType == DataPacket)
      mLastSeqRecvdAtSend[mLastSendSeq & 0x1F] = mLastSeqRecvd;
}

void ConnectionProtocol::sendPingPacket()
{
   U8 buffer[16];
   BitStream bs(buffer, 16);
   buildSendPacketHeader(&bs, PingPacket);
   if(gLogToConsole)
      Con::printf("send ping %d", mLastSendSeq);

   sendPacket(&bs);
}

void ConnectionProtocol::sendAckPacket()
{
   U8 buffer[16];
   BitStream bs(buffer, 16);
   buildSendPacketHeader(&bs, AckPacket);
   if(gLogToConsole)
      Con::printf("send ack %d", mLastSendSeq);

   sendPacket(&bs);
}

// packets are read directly into the data portion of
// connection notify packets... makes the events easier to post into
// the system.

void ConnectionProtocol::processRawPacket(BitStream *pstream)
{
   // read in the packet header:

   // Fixed packet header: 3 bytes
   //
   //   1 bit game packet flag
   //   1 bit connect sequence
   //   9 bits packet seq number
   //   9 bits ackstart seq number
   //   2 bits packet type
   //   2 bits ack byte count
   //
   // type is:
   //    00 data packet
   //    01 ping packet
   //    02 ack packet

   // next 1-4 bytes are ack flags
   //
   //   header len is 4-9 bytes
   //   average case 4 byte header

   gNetBitsReceived = pstream->getStreamSize();

   pstream->readFlag(); // get rid of the game info packet bit
   U32 pkConnectSeqBit  = pstream->readInt(1);
   U32 pkSequenceNumber = pstream->readInt(9);
   U32 pkHighestAck     = pstream->readInt(9);
   U32 pkPacketType     = pstream->readInt(2);
   S32 pkAckByteCount   = pstream->readInt(3);

   // check connection sequence bit
   if(pkConnectSeqBit != (mConnectSequence & 1))
      return;

   if(pkAckByteCount > 4 || pkPacketType >= InvalidPacketType)
      return;

   S32 pkAckMask = pstream->readInt(8 * pkAckByteCount);

   // verify packet ordering and acking and stuff
   // check if the 9-bit sequence is within the packet window
   // (within 31 packets of the last received sequence number).

   pkSequenceNumber |= (mLastSeqRecvd & 0xFFFFFE00);
   // account for wrap around
   if(pkSequenceNumber < mLastSeqRecvd)
      pkSequenceNumber += 0x200;

   if(pkSequenceNumber > mLastSeqRecvd + 31)
   {
      // the sequence number is outside the window... must be out of order
      // discard.
      return;
   }

   pkHighestAck |= (mHighestAckedSeq & 0xFFFFFE00);
   // account for wrap around

   if(pkHighestAck < mHighestAckedSeq)
      pkHighestAck += 0x200;

   if(pkHighestAck > mLastSendSeq)
   {
      // the ack number is outside the window... must be an out of order
      // packet, discard.
      return;
   }

   if(gLogToConsole)
   {
      for(U32 i = mLastSeqRecvd+1; i < pkSequenceNumber; i++)
         Con::printf("Not recv %d", i);
      Con::printf("Recv %d %s", pkSequenceNumber, packetTypeNames[pkPacketType]);
   }

   // shift up the ack mask by the packet difference
   // this essentially nacks all the packets dropped

   mAckMask <<= pkSequenceNumber - mLastSeqRecvd;

   // if this packet is a data packet (i.e. not a ping packet or an ack packet), ack it
   if(pkPacketType == DataPacket)
      mAckMask |= 1;

   // do all the notifies...
   for(U32 i = mHighestAckedSeq+1; i <= pkHighestAck; i++)
   {
      bool packetTransmitSuccess = pkAckMask & (1 << (pkHighestAck - i));
      handleNotify(packetTransmitSuccess);
      if(gLogToConsole)
         Con::printf("Ack %d %d", i, packetTransmitSuccess);

      if(packetTransmitSuccess)
      {
         mLastRecvAckAck = mLastSeqRecvdAtSend[i & 0x1F];
         if(!mConnectionEstablished)
         {
            mConnectionEstablished = true;
            handleConnectionEstablished();
         }
      }
   }

   // the other side knows more about its window than we do.
   if(pkSequenceNumber - mLastRecvAckAck > 32)
      mLastRecvAckAck = pkSequenceNumber - 32;

   mHighestAckedSeq = pkHighestAck;

   // first things first...
   // ackback any pings or accept connects

   if(pkPacketType == PingPacket)
   {
      // send an ack to the other side
      // the ack will have the same packet sequence as our last sent packet
      // if the last packet we sent was the connection accepted packet
      // we must resend that packet
      sendAckPacket();
   }
   keepAlive(); // notification that the connection is ok

   if(mLastSeqRecvd != pkSequenceNumber && pkPacketType == DataPacket)
      handlePacket(pstream);

   mLastSeqRecvd = pkSequenceNumber;
}

bool ConnectionProtocol::windowFull()
{
   return mLastSendSeq - mHighestAckedSeq >= 30;
}

void ConnectionProtocol::writeDemoStartBlock(ResizeBitStream *stream)
{
   for(U32 i = 0; i < 32; i++)
      stream->write(mLastSeqRecvdAtSend[i]);
   stream->write(mLastSeqRecvd);
   stream->write(mHighestAckedSeq);
   stream->write(mLastSendSeq);
   stream->write(mAckMask);
   stream->write(mConnectSequence);
   stream->write(mLastRecvAckAck);
   stream->write(mConnectionEstablished);
}

bool ConnectionProtocol::readDemoStartBlock(BitStream *stream)
{
   for(U32 i = 0; i < 32; i++)
      stream->read(&mLastSeqRecvdAtSend[i]);
   stream->read(&mLastSeqRecvd);
   stream->read(&mHighestAckedSeq);
   stream->read(&mLastSendSeq);
   stream->read(&mAckMask);
   stream->read(&mConnectSequence);
   stream->read(&mLastRecvAckAck);
   stream->read(&mConnectionEstablished);
   return true;
}