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

#include "game/net/tcpObject.h"

#include "platform/platform.h"
#include "platform/event.h"
#include "platform/gameInterface.h"
#include "console/simBase.h"
#include "console/consoleInternal.h"
#include "game/demoGame.h"

TCPObject *TCPObject::table[TCPObject::TableSize] = {0, };

IMPLEMENT_CONOBJECT(TCPObject);

TCPObject *TCPObject::find(NetSocket tag)
{
   for(TCPObject *walk = table[U32(tag) & TableMask]; walk; walk = walk->mNext)
      if(walk->mTag == tag)
         return walk;
   return NULL;
}

void TCPObject::addToTable(NetSocket newTag)
{
   removeFromTable();
   mTag = newTag;
   mNext = table[U32(mTag) & TableMask];
   table[U32(mTag) & TableMask] = this;
}

void TCPObject::removeFromTable()
{
   for(TCPObject **walk = &table[U32(mTag) & TableMask]; *walk; walk = &((*walk)->mNext))
   {
      if(*walk == this)
      {
         *walk = mNext;
         return;
      }
   }
}

TCPObject::TCPObject()
{
   mBuffer = NULL;
   mBufferSize = 0;
   mPort = 0;
   mTag = InvalidSocket;
   mNext = NULL;
   mState = Disconnected;
}

TCPObject::~TCPObject()
{
   disconnect();
   dFree(mBuffer);
}

bool TCPObject::processArguments(S32 argc, const char **argv)
{
   if(argc == 0)
      return true;
   else if(argc == 1)
   {
      addToTable(U32(dAtoi(argv[0])));
      return true;
   }
   return false;
}

bool TCPObject::onAdd()
{
   if(!Parent::onAdd())
      return false;

   const char *name = getName();

   if(name && name[0] && getClassRep())
   {
      Namespace *parent = getClassRep()->getNameSpace();
      Con::linkNamespaces(parent->mName, name);
      mNameSpace = Con::lookupNamespace(name);

   }

   Sim::getTCPGroup()->addObject(this);

   return true;
}

U32 TCPObject::onReceive(U8 *buffer, U32 bufferLen)
{
   // we got a raw buffer event
   // default action is to split the buffer into lines of text
   // and call processLine on each
   // for any incomplete lines we have mBuffer
   U32 start = 0;
   parseLine(buffer, &start, bufferLen);
   return start;
}

void TCPObject::parseLine(U8 *buffer, U32 *start, U32 bufferLen)
{
   // find the first \n in buffer
   U32 i;
   U8 *line = buffer + *start;

   for(i = *start; i < bufferLen; i++)
      if(buffer[i] == '\n' || buffer[i] == 0)
         break;
   U32 len = i - *start;

   if(i == bufferLen || mBuffer)
   {
      // we've hit the end with no newline
      mBuffer = (U8 *) dRealloc(mBuffer, mBufferSize + len + 1);
      dMemcpy(mBuffer + mBufferSize, line, len);
      mBufferSize += len;
      *start = i;

      // process the line
      if(i != bufferLen)
      {
         mBuffer[mBufferSize] = 0;
         if(mBufferSize && mBuffer[mBufferSize-1] == '\r')
            mBuffer[mBufferSize - 1] = 0;
         U8 *temp = mBuffer;
         mBuffer = 0;
         mBufferSize = 0;

         processLine(temp);
         dFree(temp);
      }
   }
   else if(i != bufferLen)
   {
      line[len] = 0;
      if(len && line[len-1] == '\r')
         line[len-1] = 0;
      processLine(line);
   }
   if(i != bufferLen)
      *start = i + 1;
}

void TCPObject::onConnectionRequest(const NetAddress *addr, U32 connectId)
{
   char idBuf[16];
   char addrBuf[256];
   Net::addressToString(addr, addrBuf);
   dSprintf(idBuf, sizeof(idBuf), "%d", connectId);
   Con::executef(this, 3, "onConnectRequest", addrBuf, idBuf);
}

bool TCPObject::processLine(U8 *line)
{
   Con::executef(this, 2, "onLine", line);
   return true;
}

void TCPObject::onDNSResolved()
{
   mState = DNSResolved;
   Con::executef(this, 1, "onDNSResolved");
}

void TCPObject::onDNSFailed()
{
   mState = Disconnected;
   Con::executef(this, 1, "onDNSFailed");
}

void TCPObject::onConnected()
{
   mState = Connected;
   Con::executef(this, 1, "onConnected");
}

void TCPObject::onConnectFailed()
{
   mState = Disconnected;
   Con::executef(this, 1, "onConnectFailed");
}

void TCPObject::finishLastLine()
{
   if(mBufferSize)
   {
      mBuffer[mBufferSize] = 0;
      processLine(mBuffer);
      dFree(mBuffer);
      mBuffer = 0;
      mBufferSize = 0;
   }
}

void TCPObject::onDisconnect()
{
   finishLastLine();
   mState = Disconnected;
   Con::executef(this, 1, "onDisconnect");
}

void TCPObject::listen(U16 port)
{
   mState = Listening;
   U32 newTag = Net::openListenPort(port);
   addToTable(newTag);
}

void TCPObject::connect(const char *address)
{
   NetSocket newTag = Net::openConnectTo(address);
   addToTable(newTag);
}

void TCPObject::disconnect()
{
   if( mTag != InvalidSocket ) {
      Net::closeConnectTo(mTag);
   }
   removeFromTable();
}

void TCPObject::send(const U8 *buffer, U32 len)
{
   Net::sendtoSocket(mTag, buffer, S32(len));
}

ConsoleMethod( TCPObject, send, void, 3, 0, "(...)"
              "Parameters are transmitted as strings, one at a time.")
{
   for(S32 i = 2; i < argc; i++)
      object->send((const U8 *) argv[i], dStrlen(argv[i]));
}

ConsoleMethod( TCPObject, listen, void, 3, 3, "(int port)"
              "Start listening on the specified ports for connections.")
{
   object->listen(U32(dAtoi(argv[2])));
}

ConsoleMethod( TCPObject, connect, void, 3, 3, "(string addr)"
              "Connect to the given address.")
{
   object->connect(argv[2]);
}

ConsoleMethod( TCPObject, disconnect, void, 2, 2, "Disconnect from whatever we're connected to, if anything.")
{
   object->disconnect();
}

void DemoGame::processConnectedReceiveEvent(ConnectedReceiveEvent* event )
{
   TCPObject *tcpo = TCPObject::find(event->tag);
   if(!tcpo)
   {
      Con::printf("Got bad connected receive event.");
      return;
   }
   U32 size = U32(event->size - ConnectedReceiveEventHeaderSize);
   U8 *buffer = event->data;

   while(size)
   {
      U32 ret = tcpo->onReceive(buffer, size);
      AssertFatal(ret <= size, "Invalid return size");
      size -= ret;
      buffer += ret;
   }
}

void DemoGame::processConnectedAcceptEvent( ConnectedAcceptEvent* event )
{
   TCPObject *tcpo = TCPObject::find(event->portTag);
   if(!tcpo)
      return;
   tcpo->onConnectionRequest(&event->address, event->connectionTag);
}

void DemoGame::processConnectedNotifyEvent( ConnectedNotifyEvent* event )
{
   TCPObject *tcpo = TCPObject::find(event->tag);
   if(!tcpo)
      return;
   switch(event->state)
   {
      case ConnectedNotifyEvent::DNSResolved:
         tcpo->onDNSResolved();
         break;
      case ConnectedNotifyEvent::DNSFailed:
         tcpo->onDNSFailed();
         break;
      case ConnectedNotifyEvent::Connected:
         tcpo->onConnected();
         break;
      case ConnectedNotifyEvent::ConnectFailed:
         tcpo->onConnectFailed();
         break;
      case ConnectedNotifyEvent::Disconnected:
         tcpo->onDisconnect();
         break;
   }
}
