text: //-----------------------------------------------------------------------------
  // 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();
  }