/* LICENSE:
  =========================================================================
    CMPack'04 Source Code Release for OPEN-R SDK 1.1.5-r2 for ERS7
    Copyright (C) 2004 Multirobot Lab [Project Head: Manuela Veloso]
    School of Computer Science, Carnegie Mellon University
    All rights reserved.
  ========================================================================= */

#ifndef EasyNet_defs_h_DEFINED
#define EasyNet_defs_h_DEFINED


/* Implementation */

#include <string.h>

typedef void (NET_OBJECT::*netObjCallback)(NetTCPConnection, int);

// These are constants rather that #define's to accommodate class methods better
const netMemberCallback BLOCK_LISTEN  = &Net_Class::Block_Dummy_Func;
const netMemberCallback BLOCK_CONNECT = &Net_Class::Block_Dummy_Func;
const netMemberCallback BLOCK_SEND    = &Net_Class::Block_Dummy_Func;
const netMemberCallback BLOCK_RECEIVE = &Net_Class::Block_Dummy_Func;
const netMemberCallback BLOCK_CLOSE   = &Net_Class::Block_Dummy_Func;

NetConnectionItem   Net_Connections;
antStackRef         Net_IPStackRef;
TCPEndpointError    NetTCPError;
OID                 Net_myOID;


NetTCPConnection Net_NewConnection(size_t SendBufSize, size_t RecvBufSize)
{
    NetTCPConnection Connection = new struct NetTCPConnectionStruct; 

    Connection->This        = NULL;
    Connection->Call        = NULL;
    Connection->SendBufSize = SendBufSize;
    Connection->RecvBufSize = RecvBufSize;
    Connection->SendingSize = 0;
    Connection->MinSend     = 0;
    Connection->MinReceive  = 1;
    Connection->MaxReceive  = RecvBufSize;
    Connection->Listening   = false;
    Connection->Connected   = false;
    Connection->Sending     = false;
    Connection->Receiving   = false;
    Connection->SendFlush   = false;
    Connection->CloseMode   = CLOSE_NONE;
    Connection->Error       = TCP_SUCCESS;
    Connection->UserData    = 0;
    Connection->DroppedDataWarningPtr  = NULL;
    Connection->DroppedDataWarningSize = 0;

    NetTCPError = TCP_SUCCESS;

    if (!Net_Initialized()) {
        Net_IPStackRef = antStackRef("IPStack");
        WhoAmI(&Net_myOID);
        Net_Connections = NULL;
    }

    // Create endpoint

    // The ANT engine here wants to be passed a value that is 'slightly bigger'
    // than the largest packet we're going to send. Description on page 8 of the
    // manual is contradictory, but all actual sample programs use 2x, so that's
    // what I'm using here. Note: if buffering is used, the 2x might end up being
    // more than we need, but network packets are usually small in size, so this
    // will likely not be a huge waste of memory

    antEnvCreateEndpointMsg tcpCreateMsg(EndpointType_TCP, SendBufSize * 2);
    tcpCreateMsg.Call(Net_IPStackRef, sizeof(tcpCreateMsg));
    if (tcpCreateMsg.error != ANT_SUCCESS) {
        NetTCPError = TCP_FAIL;
        return Connection;
    }
    Connection->antEndpoint = tcpCreateMsg.moduleRef;

    // Allocate send buffer
    antEnvCreateSharedBufferMsg sendBufferMsg(SendBufSize);
    sendBufferMsg.Call(Net_IPStackRef, sizeof(sendBufferMsg));
    if (sendBufferMsg.error != ANT_SUCCESS) {
        NetTCPError = TCP_FAIL;
        return Connection;
    }

    Connection->antSharedSend = sendBufferMsg.buffer;
    // An antSharedBuffer has a different addressing space, so we need
    // to get it 'mapped' onto a valid address for us. 
    Connection->antSharedSend.Map();
    Connection->SendBuffer = (char*)(Connection->antSharedSend.GetAddress());
    Connection->SendingStart = Connection->SendBuffer;
    Connection->SendingStop  = Connection->SendBuffer + Connection->SendBufSize;

    // Allocate receive buffer. Same as above.
    antEnvCreateSharedBufferMsg recvBufferMsg(RecvBufSize);
    recvBufferMsg.Call(Net_IPStackRef, sizeof(recvBufferMsg));
    if (recvBufferMsg.error != ANT_SUCCESS) {
        NetTCPError = TCP_FAIL;
        // Destroy Send shared buffer
        Connection->antSharedSend.UnMap();
        antEnvDestroySharedBufferMsg destroySendBufMsg(Connection->antSharedSend);
        destroySendBufMsg.Call(Net_IPStackRef, 
sizeof(antEnvDestroySharedBufferMsg));
        return Connection;
    }
    
    Connection->antSharedRecv = recvBufferMsg.buffer;
    Connection->antSharedRecv.Map();
    Connection->ReceiveBuffer = (char*)(Connection->antSharedRecv.GetAddress());

    return Connection;

}



NetTCPConnection NET_OBJECT::NetTCPConnect(IPAddress addr, Port port,
                 netObjCallback ConnectCallback, netObjCallback SendCallback,
                 netObjCallback ReceiveCallback, netObjCallback CloseCallback,
                 size_t SendBufSize, size_t RecvBufSize)
{

    NetTCPConnection Connection = Net_NewConnection(SendBufSize, RecvBufSize);

    Connection->ConnectCallback = reinterpret_cast<netMemberCallback>(ConnectCallback);
    Connection->SendCallback    = reinterpret_cast<netMemberCallback>(SendCallback);
    Connection->ReceiveCallback = reinterpret_cast<netMemberCallback>(ReceiveCallback);
    Connection->CloseCallback   = reinterpret_cast<netMemberCallback>(CloseCallback);

    if (NetTCPError == TCP_FAIL) {
        if ((ConnectCallback != NULL) && (ConnectCallback != BLOCK_CONNECT)) {
            (this->*(reinterpret_cast<netObjCallback>(ConnectCallback)))(Connection, 0);
        }
        delete Connection;
        return NULL;
    } // if <there was an error creating the Connection>


    // Connect
    TCPEndpointConnectMsg connectMsg(Connection->antEndpoint,
                          IP_ADDR_ANY, IP_PORT_ANY, addr, port);

    if (Connection->ConnectCallback == ::BLOCK_CONNECT) {

        connectMsg.Call(Net_IPStackRef, sizeof(TCPEndpointConnectMsg));
        NetTCPError = Connection->Error = connectMsg.error;
        if (connectMsg.error != TCP_SUCCESS) {
            // Destroy Send shared buffer
            Connection->antSharedSend.UnMap();
            antEnvDestroySharedBufferMsg destroySendBufMsg(
                                         Connection->antSharedSend);
            destroySendBufMsg.Call(Net_IPStackRef,
                                   sizeof(antEnvDestroySharedBufferMsg));
    
            // Destroy Receive shared buffer
            Connection->antSharedRecv.UnMap();
            antEnvDestroySharedBufferMsg destroyRecvBufMsg(
                                         Connection->antSharedRecv);
            destroyRecvBufMsg.Call(Net_IPStackRef,
                                   sizeof(antEnvDestroySharedBufferMsg));

            // Close (abort) Endpoint
            TCPEndpointCloseMsg closeMsg(Connection->antEndpoint, TRUE);
            closeMsg.Call(Net_IPStackRef, sizeof(TCPEndpointCloseMsg));

            delete Connection;
            return NULL;
        } else {  // if now <blocking connect was successful> 
            Connection->LocalAddress  = connectMsg.lAddress;
            Connection->LocalPort     = connectMsg.lPort;
            Connection->RemoteAddress = connectMsg.fAddress;
            Connection->RemotePort    = connectMsg.fPort;
            Connection->Connected     = true;
            Net_AddConnection(Connection);

            // Unless the receive is blocking, post receive request
            if (Connection->ReceiveCallback != ::BLOCK_RECEIVE) {
                NetReceive(Connection,
                           Connection->MinReceive, Connection->MaxReceive);
            }

            return Connection;
        } // if <blocking connect returned error or not>

    } else { // if now <connect was non-blocking>

        connectMsg.continuation = (void*)Connection;
        connectMsg.Send(Net_IPStackRef, Net_myOID,
                        Extra_Entry[entryNet_ConnectCont], sizeof(connectMsg));
        return Connection;

    } // if <connect was blocking or not>
}


inline NetTCPConnection NET_OBJECT::NetTCPConnect(IPAddress addr, Port port,
                 netObjCallback ConnectCallback, netObjCallback SendCallback,
                 netObjCallback ReceiveCallback, netObjCallback CloseCallback)
{
    return NetTCPConnect(addr, port, ConnectCallback, SendCallback,
              ReceiveCallback, CloseCallback, 8192, 8192);
}

NetTCPConnection NetTCPConnect_Class(IPAddress addr, Port port,
                 netMemberCallback ConnectCallback, netMemberCallback SendCallback,
                 netMemberCallback ReceiveCallback, netMemberCallback CloseCallback,
                 size_t SendBufSize, size_t RecvBufSize,
                 void* ThisVoid, netCallbackCaller Call)
{


    NetTCPConnection Connection = Net_NewConnection(SendBufSize, RecvBufSize);

    Connection->This = ThisVoid;
    Connection->Call = Call;

    Connection->ConnectCallback = ConnectCallback;
    Connection->SendCallback    = SendCallback;
    Connection->ReceiveCallback = ReceiveCallback;
    Connection->CloseCallback   = CloseCallback;

    if (NetTCPError == TCP_FAIL) {
        if (ConnectCallback != 0) {
            (*Call)(ConnectCallback, Connection, 0);
        }
        delete Connection;
        return NULL;
    } // if <there was an error creating the Connection>


    // Connect
    TCPEndpointConnectMsg connectMsg(Connection->antEndpoint,
                          IP_ADDR_ANY, IP_PORT_ANY, addr, port);

    if (Connection->ConnectCallback == ::BLOCK_CONNECT) {

        connectMsg.Call(Net_IPStackRef, sizeof(TCPEndpointConnectMsg));
        NetTCPError = Connection->Error = connectMsg.error;
        if (connectMsg.error != TCP_SUCCESS) {
            // Destroy Send shared buffer
            Connection->antSharedSend.UnMap();
            antEnvDestroySharedBufferMsg destroySendBufMsg(
                                         Connection->antSharedSend);
            destroySendBufMsg.Call(Net_IPStackRef,
                                   sizeof(antEnvDestroySharedBufferMsg));
    
            // Destroy Receive shared buffer
            Connection->antSharedRecv.UnMap();
            antEnvDestroySharedBufferMsg destroyRecvBufMsg(
                                         Connection->antSharedRecv);
            destroyRecvBufMsg.Call(Net_IPStackRef,
                                   sizeof(antEnvDestroySharedBufferMsg));

            // Close (abort) Endpoint
            TCPEndpointCloseMsg closeMsg(Connection->antEndpoint, TRUE);
            closeMsg.Call(Net_IPStackRef, sizeof(TCPEndpointCloseMsg));

            delete Connection;
            return NULL;
        } else {  // if now <blocking connect was successful> 
            Connection->LocalAddress  = connectMsg.lAddress;
            Connection->LocalPort     = connectMsg.lPort;
            Connection->RemoteAddress = connectMsg.fAddress;
            Connection->RemotePort    = connectMsg.fPort;
            Connection->Connected     = true;
            Net_AddConnection(Connection);

            // Unless the receive is blocking, post receive request
            if (Connection->ReceiveCallback != BLOCK_RECEIVE) {
                NetReceive(Connection,
                           Connection->MinReceive, Connection->MaxReceive);
            }

            return Connection;
        } // if <blocking connect returned error or not>

    } else { // if now <connect was non-blocking>

        connectMsg.continuation = (void*)Connection;
        connectMsg.Send(Net_IPStackRef, Net_myOID,
                        Extra_Entry[entryNet_ConnectCont], sizeof(connectMsg));
        return Connection;

    } // if <connect was blocking or not>

}





NetTCPConnection NET_OBJECT::NetTCPListen(Port port,
                 netObjCallback ConnectCallback, netObjCallback SendCallback,
                 netObjCallback ReceiveCallback, netObjCallback CloseCallback,
                 size_t SendBufSize, size_t RecvBufSize)
{

    NetTCPConnection Connection = Net_NewConnection(SendBufSize, RecvBufSize);

    Connection->ConnectCallback = reinterpret_cast<netMemberCallback>(ConnectCallback);
    Connection->SendCallback    = reinterpret_cast<netMemberCallback>(SendCallback);
    Connection->ReceiveCallback = reinterpret_cast<netMemberCallback>(ReceiveCallback);
    Connection->CloseCallback   = reinterpret_cast<netMemberCallback>(CloseCallback);

    if (NetTCPError == TCP_FAIL) {
        if ((ConnectCallback != NULL) && (ConnectCallback != BLOCK_CONNECT)) {
            (this->*(reinterpret_cast<netObjCallback>(ConnectCallback)))(Connection, 0);
        }
        delete Connection;
        return NULL;
    } // if <there was an error creating the Connection>


    // Listen
    TCPEndpointListenMsg listenMsg(Connection->antEndpoint, IP_ADDR_ANY, port);

    if (Connection->ConnectCallback == ::BLOCK_LISTEN) {

        listenMsg.Call(Net_IPStackRef, sizeof(TCPEndpointConnectMsg));
        NetTCPError = Connection->Error = listenMsg.error;
        if (listenMsg.error != TCP_SUCCESS) {
            // Destroy Send shared buffer
            Connection->antSharedSend.UnMap();
            antEnvDestroySharedBufferMsg destroySendBufMsg(
                                         Connection->antSharedSend);
            destroySendBufMsg.Call(Net_IPStackRef,
                                   sizeof(antEnvDestroySharedBufferMsg));
    
            // Destroy Receive shared buffer
            Connection->antSharedRecv.UnMap();
            antEnvDestroySharedBufferMsg destroyRecvBufMsg(
                                         Connection->antSharedRecv);
            destroyRecvBufMsg.Call(Net_IPStackRef,
                                   sizeof(antEnvDestroySharedBufferMsg));

            // Close (abort) Endpoint
            TCPEndpointCloseMsg closeMsg(Connection->antEndpoint, TRUE);
            closeMsg.Call(Net_IPStackRef, sizeof(TCPEndpointCloseMsg));

            delete Connection;
            return NULL;
        } else {  // if now <blocking connect was successful> 
            Connection->LocalAddress  = listenMsg.lAddress;
            Connection->LocalPort     = listenMsg.lPort;
            Connection->RemoteAddress = listenMsg.fAddress;
            Connection->RemotePort    = listenMsg.fPort;
            Connection->Connected     = true;
            Net_AddConnection(Connection);

            // Unless the receive is blocking, post receive request
            if (Connection->ReceiveCallback != ::BLOCK_RECEIVE) {
                NetReceive(Connection,
                           Connection->MinReceive, Connection->MaxReceive);
            }

            return Connection;
        } // if <blocking connect returned error or not>

    } else { // if now <connect was non-blocking>

        Connection->Listening = true;
        listenMsg.continuation = (void*)Connection;
        listenMsg.Send(Net_IPStackRef, Net_myOID,
                        Extra_Entry[entryNet_ConnectCont], sizeof(listenMsg));
        return Connection;

    } // if <connect was blocking or not>
}


inline NetTCPConnection NET_OBJECT::NetTCPListen(Port port,
                 netObjCallback ConnectCallback, netObjCallback SendCallback,
                 netObjCallback ReceiveCallback, netObjCallback CloseCallback)
{
    return NetTCPListen(port, ConnectCallback, SendCallback,
              ReceiveCallback, CloseCallback, 8192, 8192);
}



NetTCPConnection NetTCPListen_Class(Port port,
                 netMemberCallback ConnectCallback, netMemberCallback SendCallback,
                 netMemberCallback ReceiveCallback, netMemberCallback CloseCallback,
                 size_t SendBufSize, size_t RecvBufSize,
                 void* ThisVoid, netCallbackCaller Call)
{

    NetTCPConnection Connection = Net_NewConnection(SendBufSize, RecvBufSize);

    Connection->This = ThisVoid;
    Connection->Call = Call;

    Connection->ConnectCallback = ConnectCallback;
    Connection->SendCallback    = SendCallback;
    Connection->ReceiveCallback = ReceiveCallback;
    Connection->CloseCallback   = CloseCallback;

    if (NetTCPError == TCP_FAIL) {
        if (ConnectCallback != NULL) {
            (*Call)(ConnectCallback, Connection, 0);
        }
        delete Connection;
        return NULL;
    } // if <there was an error creating the Connection>


    // Listen
    TCPEndpointListenMsg listenMsg(Connection->antEndpoint, IP_ADDR_ANY, port);

    if (Connection->ConnectCallback == BLOCK_LISTEN) {

        listenMsg.Call(Net_IPStackRef, sizeof(TCPEndpointConnectMsg));
        NetTCPError = Connection->Error = listenMsg.error;
        if (listenMsg.error != TCP_SUCCESS) {
            // Destroy Send shared buffer
            Connection->antSharedSend.UnMap();
            antEnvDestroySharedBufferMsg destroySendBufMsg(
                                         Connection->antSharedSend);
            destroySendBufMsg.Call(Net_IPStackRef,
                                   sizeof(antEnvDestroySharedBufferMsg));
    
            // Destroy Receive shared buffer
            Connection->antSharedRecv.UnMap();
            antEnvDestroySharedBufferMsg destroyRecvBufMsg(
                                         Connection->antSharedRecv);
            destroyRecvBufMsg.Call(Net_IPStackRef,
                                   sizeof(antEnvDestroySharedBufferMsg));

            // Close (abort) Endpoint
            TCPEndpointCloseMsg closeMsg(Connection->antEndpoint, TRUE);
            closeMsg.Call(Net_IPStackRef, sizeof(TCPEndpointCloseMsg));

            delete Connection;
            return NULL;
        } else {  // if now <blocking connect was successful> 
            Connection->LocalAddress  = listenMsg.lAddress;
            Connection->LocalPort     = listenMsg.lPort;
            Connection->RemoteAddress = listenMsg.fAddress;
            Connection->RemotePort    = listenMsg.fPort;
            Connection->Connected     = true;
            Net_AddConnection(Connection);

            // Unless the receive is blocking, post receive request
            if (Connection->ReceiveCallback != BLOCK_RECEIVE) {
                NetReceive(Connection,
                           Connection->MinReceive, Connection->MaxReceive);
            }

            return Connection;
        } // if <blocking connect returned error or not>

    } else { // if now <connect was non-blocking>

        Connection->Listening = true;
        listenMsg.continuation = (void*)Connection;
        listenMsg.Send(Net_IPStackRef, Net_myOID,
                        Extra_Entry[entryNet_ConnectCont], sizeof(listenMsg));
        return Connection;

    } // if <connect was blocking or not>

}



void NET_OBJECT::Net_ConnectCont(ANTENVMSG msg)
{

    antEnvMsg* replyMsg = antEnvMsg::Receive(msg);
    NetTCPConnection Connection = (NetTCPConnection)replyMsg->continuation;

    if (Connection->Listening) {
        TCPEndpointListenMsg* listenMsg = (TCPEndpointListenMsg*)replyMsg;
        NetTCPError = Connection->Error = listenMsg->error;
        Connection->LocalAddress  = listenMsg->lAddress;
        Connection->LocalPort     = listenMsg->lPort;
        Connection->RemoteAddress = listenMsg->fAddress;
        Connection->RemotePort    = listenMsg->fPort;
    } else {
        TCPEndpointConnectMsg* connectMsg = (TCPEndpointConnectMsg*)replyMsg;
        NetTCPError = Connection->Error = connectMsg->error;
        Connection->LocalAddress  = connectMsg->lAddress;
        Connection->LocalPort     = connectMsg->lPort;
        Connection->RemoteAddress = connectMsg->fAddress;
        Connection->RemotePort    = connectMsg->fPort;
    }

    if (Connection->Error == TCP_SUCCESS) {
        Connection->Connected = true;
        Connection->Listening = false;
        Net_AddConnection(Connection);

        if ((Connection->SendingSize > Connection->MinSend) ||
             ((Connection->SendFlush == true) && (Connection->SendingSize > 0))) {
            // if there's data pending, do send

            char*  SendingStart = Connection->SendingStart;
            char*  SendingStop  = Connection->SendingStop;
            size_t SendingSize  = Connection->SendingSize;

            TCPEndpointSendMsg sendMsg(Connection->antEndpoint,
                                     (byte*)SendingStart, SendingSize);
            sendMsg.continuation = (void*)Connection;
            sendMsg.Send(Net_IPStackRef, Net_myOID,
                         Extra_Entry[entryNet_SendCont], sizeof(sendMsg));

            // check whether there's more room at start or end of buffer
            // and prepare pointers to accept new data while this is sending
            if ((SendingStart - Connection->SendBuffer) >=
                (SendingStop - (SendingStart + SendingSize))) {
                SendingStop  = SendingStart;
                SendingStart = Connection->SendBuffer;
            } else {
                SendingStart += SendingSize;
            }
            Connection->Sending      = true;
            Connection->SendingStart = SendingStart;
            Connection->SendingStop  = SendingStop;
            Connection->SendingSize  = 0;
            Connection->SendFlush    = false;
            
        } else { // if now <there's no more data pending>
            // reset SendingStart so new sends will queue data from beginning
            Connection->SendingStart = Connection->SendBuffer;
            Connection->Sending      = false;
            Connection->SendingStop  =
                      Connection->SendingStart + Connection->SendBufSize;

            if (Connection->ConnectCallback != NULL) {
                if (Connection->Call != NULL) {
                    (*Connection->Call)(Connection->ConnectCallback, Connection, 1);
                } else {
                    (this->*(reinterpret_cast<netObjCallback>(Connection->ConnectCallback)))
                                                                             (Connection, 1);
                }
            }

            // Unless the receive is blocking, post receive request
            if (Connection->ReceiveCallback != ::BLOCK_RECEIVE) {
                NetReceive(Connection,
                           Connection->MinReceive, Connection->MaxReceive);
            }

        }
    } else { // if now <connect returned an error>

        /* This is now unnecessary, since connection is destroyed *\
        // purge any pending sends if connect failed
        Connection->SendingStart = Connection->SendBuffer;
        Connection->SendingSize  = 0;
        Connection->SendFlush    = false;
        \*                                                        */

        if (Connection->ConnectCallback != NULL) {
            if (Connection->Call != NULL) {
                (*Connection->Call)(Connection->ConnectCallback, Connection, 0);
            } else {
                (this->*(reinterpret_cast<netObjCallback>(Connection->ConnectCallback)))
                                                                         (Connection, 0);
            }
        }

        // Should still be CLOSE_NONE from the initialization, but just in case...
        Connection->CloseMode = CLOSE_NONE;

        // Send close message. Net_CloseCont will do the rest. 
        TCPEndpointCloseMsg closeMsg(Connection->antEndpoint, TRUE);
        closeMsg.continuation = (void*)Connection;
        closeMsg.Send(Net_IPStackRef, Net_myOID,
                        Extra_Entry[entryNet_CloseCont], sizeof(closeMsg));


    } // if <connect was successful or not>
}



int NetSend(NetTCPConnection Connection, const void *buf, size_t size)
{

    if (!Net_ConnectionValid(Connection)) {
        NetTCPError = TCP_BUFFER_INVALID;
        return 0;
    }

    if (Connection->SendCallback == BLOCK_SEND) {

        if (Connection->Sending == true) {
            NetTCPError = Connection->Error = TCP_CONNECTION_BUSY;
            return 0;
        }
        if (size > size_t(Connection->SendingStop - Connection->SendingStart)) {
            NetTCPError = Connection->Error = TCP_MESSAGE_TOO_LONG;
            return 0;
        }
        if (buf != Connection->SendBuffer) {
            memcpy(Connection->SendingStart + Connection->SendingSize, buf, size);
        }
        Connection->SendingSize += size;
        if (Connection->SendingSize >= Connection->MinSend) { // if have enough bytes
            TCPEndpointSendMsg sendMsg(Connection->antEndpoint,
                          (byte*)Connection->SendingStart, Connection->SendingSize);
            sendMsg.Call(Net_IPStackRef, sizeof(TCPEndpointSendMsg));
            Connection->SendingStart = Connection->SendBuffer;
            Connection->SendingSize  = 0;
            NetTCPError = Connection->Error = sendMsg.error;
            if (sendMsg.error == TCP_SUCCESS) {
                return sendMsg.size;
            } else {
                return 0;
            }
        } else { // if now <we need to buffer more bytes before sending>
            return int(size);
        }
    } else { // if now <connection is non-blocking>

        char*  SendingStart = Connection->SendingStart;
        char*  SendingStop  = Connection->SendingStop;
        size_t SendingSize  = Connection->SendingSize;

        if ((SendingStart + SendingSize + size) <= SendingStop) {
            // if we have enough room to accept the data 
            if ((size == 0) || (buf == NULL)) {
                // a call with size == 0 or NULL buf will flush right away
                Connection->SendFlush = true;
            } else {
                if (buf != Connection->SendBuffer) {
                    memcpy(SendingStart + SendingSize, buf, size);
                }
                SendingSize += size;
            }
            if (((SendingSize >= Connection->MinSend) ||
                                          (Connection->SendFlush == true)) &&
                 (Connection->Sending == false) && (Connection->Connected)) {
                // Take action only if we gathered enough bytes (or user wants
                // to flush buffer) and we're ready to send, otherwise exit.

                // Submit send message
                TCPEndpointSendMsg sendMsg(Connection->antEndpoint,
                                           (byte*)SendingStart, SendingSize);
                sendMsg.continuation = (void*)Connection;
                sendMsg.Send(Net_IPStackRef, Net_myOID,
                             Extra_Entry[entryNet_SendCont], sizeof(sendMsg));
                Connection->Sending = true;

                // check whether there's more room at start or end of buffer
                // and prepare pointers to accept new data while this is sending
                if ((SendingStart - Connection->SendBuffer) >=
                    (SendingStop - (SendingStart + SendingSize))) {
                    SendingStop  = SendingStart;
                    SendingStart = Connection->SendBuffer;
                } else {
                    SendingStart += SendingSize;
                }
                SendingSize = 0;
                Connection->SendingStart = SendingStart;
                Connection->SendingStop  = SendingStop;
            } // if <we did send data>
            Connection->SendingSize  = SendingSize;
            return 1;

        } else { // if now <we don't have enough room to accept data>
            // clip size to max available and copy that many bytes to buf
            size = SendingStop - (SendingStart + SendingSize);
            memcpy(SendingStart + SendingSize, buf, size);
            SendingSize += size;
            // overwrite last bytes with warning if one is present
            if (Connection->DroppedDataWarningPtr != NULL) {
                memcpy(SendingStop - Connection->DroppedDataWarningSize,
                       Connection->DroppedDataWarningPtr,
                       Connection->DroppedDataWarningSize);
            }
            if (Connection->Sending == false) {
                // Submit send message if no other send is in progress
                TCPEndpointSendMsg sendMsg(Connection->antEndpoint,
                                           (byte*)SendingStart, SendingSize);
                sendMsg.continuation = (void*)Connection;
                sendMsg.Send(Net_IPStackRef, Net_myOID,
                             Extra_Entry[entryNet_SendCont], sizeof(sendMsg));
                Connection->Sending = true;

                SendingSize = 0;
                Connection->SendingStop  = SendingStart;
                Connection->SendingStart = Connection->SendBuffer;
            }
            Connection->SendingSize = SendingSize;
            return 1;

        } // if <we have enough room to accept data or not>

    } // if <connection is blocking or not>
}


int NetSend(NetTCPConnection Connection, const void *buf)
{
    if (buf != NULL) {
        return NetSend(Connection, buf, strlen((char*)buf)); //  no end \0
    } else {
        return NetSend(Connection, NULL, 0); // flush buffer
    } 
}



void NET_OBJECT::Net_SendCont(ANTENVMSG msg)
{

    TCPEndpointSendMsg* sendMsg = (TCPEndpointSendMsg*)antEnvMsg::Receive(msg);
    NetTCPConnection Connection = (NetTCPConnection)(sendMsg->continuation);

    NetTCPError = Connection->Error = sendMsg->error;
    Connection->Sending = false;

    if (sendMsg->error != TCP_SUCCESS) {
        // purge any further, pending operations if this failed (except for close)
        Connection->SendingStart = Connection->SendBuffer;
        Connection->SendingSize  = 0;
        Connection->SendFlush    = false;

        if (Connection->SendCallback != NULL) {
            if (Connection->Call != NULL) {
                (*Connection->Call)(Connection->SendCallback, Connection, 0);
            } else {
                (this->*(reinterpret_cast<netObjCallback>(Connection->SendCallback)))
                                                                      (Connection, 0);
            }
        }

        // This is debatable, but maybe the best thing to do is to proceed with a
        // close if one close was queued, even if the last send returned an error.
        // If the user has sent some more data in the send callback above, the close
        // will remain queued anyways.

        if (Connection->CloseMode != CLOSE_NONE) {
            NetClose(Connection, Connection->CloseMode);
        }

    } else { // Send was successful

        if ((Connection->SendingSize > Connection->MinSend) ||
             ((Connection->SendFlush == true) && (Connection->SendingSize > 0))) {
            // if there's data pending, do send

            char*  SendingStart = Connection->SendingStart;
            char*  SendingStop  = Connection->SendingStop;
            size_t SendingSize  = Connection->SendingSize;

            TCPEndpointSendMsg sendMsg(Connection->antEndpoint,
                                     (byte*)SendingStart, SendingSize);
            sendMsg.continuation = (void*)Connection;
            sendMsg.Send(Net_IPStackRef, Net_myOID,
                         Extra_Entry[entryNet_SendCont], sizeof(sendMsg));

            // check whether there's more room at start or end of buffer
            // and prepare pointers to accept new data while this is sending
            if ((SendingStart - Connection->SendBuffer) >=
                (SendingStop - (SendingStart + SendingSize))) {
                SendingStop  = SendingStart;
                SendingStart = Connection->SendBuffer;
            } else {
                SendingStart += SendingSize;
            }
            Connection->Sending      = true;
            Connection->SendingStart = SendingStart;
            Connection->SendingStop  = SendingStop;
            Connection->SendingSize  = 0;
            Connection->SendFlush    = false;
            
        } else { // if now <there's no more data pending>
            // reset SendingStart so new sends will queue data from beginning
            Connection->SendingStart = Connection->SendBuffer;
            Connection->Sending      = false;
            Connection->SendingStop  =
                      Connection->SendingStart + Connection->SendBufSize;

            if (Connection->SendCallback != NULL) {
                // invoke call back if one is specified
                if (Connection->Call != NULL) {
                    (*Connection->Call)(Connection->SendCallback, Connection,
                                                                    sendMsg->size);
                } else {
                    (this->*(reinterpret_cast<netObjCallback>(Connection->SendCallback)))
                                                              (Connection, sendMsg->size);
                }
            }
            // if a close was queued, post close message now that send is done
            if (Connection->CloseMode != CLOSE_NONE) {
                NetClose(Connection, Connection->CloseMode);
            }
        } // if <there's more data to send or not>
    } // if <send returned an error or not>
}



int NetReceive(NetTCPConnection Connection,
                          void *buf, size_t min, size_t max)
{

//    FILE* fp = fopen("/MS/runlog.txt", "a");
//    fprintf(fp, "NetReceive called with min: %u  max: %u\n", min, max);
//    fclose(fp);

    if (!Net_ConnectionValid(Connection)) {
        NetTCPError = TCP_BUFFER_INVALID;
        return 0;
    }

    TCPEndpointReceiveMsg receiveMsg(Connection->antEndpoint,
                                     (byte*)Connection->ReceiveBuffer, min, max);

    if (Connection->ReceiveCallback == BLOCK_RECEIVE) {

        receiveMsg.Call(Net_IPStackRef, sizeof(TCPEndpointReceiveMsg));
        NetTCPError = Connection->Error = receiveMsg.error;
        if (receiveMsg.error == TCP_SUCCESS) {
            if (buf != NULL) {
                memcpy(buf, Connection->ReceiveBuffer, receiveMsg.sizeMin);
            }
            return receiveMsg.sizeMin;
        } else {
            return 0;
        }

    } else {

        receiveMsg.continuation = (void*)Connection;
        receiveMsg.Send(Net_IPStackRef, Net_myOID,
                        Extra_Entry[entryNet_ReceiveCont], sizeof(receiveMsg));
        Connection->Receiving = true;
        return 1;

    }
}


inline int NetReceive(NetTCPConnection Connection,
                                 size_t min, size_t max)
{
    return NetReceive(Connection, NULL, min, max);   
}


inline int NetReceive(NetTCPConnection Connection, size_t size)
{
    return NetReceive(Connection, NULL, size, size);
}


inline void NetNextReceive(NetTCPConnection Connection, size_t min, size_t max)
{
    Connection->MinReceive = min;
    Connection->MaxReceive = max;
}


inline void NetNextReceive(NetTCPConnection Connection, size_t size)
{
    Connection->MinReceive = Connection->MaxReceive = size;
}



void NET_OBJECT::Net_ReceiveCont(ANTENVMSG msg)
{

    TCPEndpointReceiveMsg* receiveMsg
        = (TCPEndpointReceiveMsg*)antEnvMsg::Receive(msg);
    NetTCPConnection Connection = (NetTCPConnection)(receiveMsg->continuation);

    NetTCPError = Connection->Error = receiveMsg->error;
    Connection->Receiving = false;

    // Unless the close is blocking, intercept the "connection closed" errors 
    if ((Connection->CloseCallback != ::BLOCK_CLOSE) && (
        (receiveMsg->error == TCP_CONNECTION_CLOSED) ||
        (receiveMsg->error == TCP_CONNECTION_RESET ) ||
        (receiveMsg->error == TCP_CONNECTION_TIMEOUT))) {
        // This will close the connection and call the callback, if specified
        NetClose(Connection, CLOSE_PASSIVE_CLEAN);
        return;
    }

    if (receiveMsg->error != TCP_SUCCESS) {
        if (Connection->ReceiveCallback != NULL) {
            if (Connection->Call != NULL) {
                (*Connection->Call)(Connection->ReceiveCallback, Connection, 0);
            } else {
                (this->*(reinterpret_cast<netObjCallback>(Connection->ReceiveCallback)))
                                                                         (Connection, 0);
            }
        }
    } else { // if <there was no error>
        if (size_t(receiveMsg->sizeMin) < Connection->RecvBufSize) {
            Connection->ReceiveBuffer[receiveMsg->sizeMin] = '\0';
        }
        if (Connection->ReceiveCallback != NULL) {
            if (Connection->Call != NULL) {
                (*Connection->Call)(Connection->ReceiveCallback,
                                               Connection, receiveMsg->sizeMin);
            } else {
                (this->*(reinterpret_cast<netObjCallback>(Connection->ReceiveCallback)))
                                                       (Connection, receiveMsg->sizeMin);
            }
        }
    } // if <receive returned error or not>

    // Post new receive request to keep receiving
    if (Connection->ReceiveCallback != ::BLOCK_RECEIVE) {
        NetReceive(Connection, Connection->MinReceive, Connection->MaxReceive);
    }

}


int NetClose(NetTCPConnection Connection, NetCloseMode mode) 
{
    if (!Net_ConnectionValid(Connection)) {
        NetTCPError = TCP_BUFFER_INVALID;
        return 0;
    }

    // Set the connection's close mode;
    Connection->CloseMode = mode;

    if ((Connection->Sending) && (Connection->CloseCallback != BLOCK_CLOSE)) {
        // If a send is in progress and the close is not blocking, just return,
        // Net_CloseCont will call us when it's ready. Calling a blocking close
        // while a non-blocking send is progress will probably cause a
        // TCP_CONNECTION_BUSY error. Rightfully.
        return 1;
    } 

    // Let's tag this connection as no longer connected so that other parts of
    // the program will not try to send stuff before the callback gets called.
    Connection->Connected = false;

    // antTypes defines a 'boolean' type with uppercase TRUE, so I will use
    // it here, but the rest of open-r uses type 'bool' with lowercase values. 
    boolean forceAbort = false;
    if ((mode == CLOSE_ABORT) || (mode == CLOSE_PASSIVE_ABORT)) {
        forceAbort = TRUE;
    }

    // Prepare the close message 
    TCPEndpointCloseMsg closeMsg(Connection->antEndpoint, forceAbort);

    if (Connection->CloseCallback == BLOCK_CLOSE) {

        // Tell ANT to destroy Send shared buffer
        Connection->antSharedSend.UnMap();
        antEnvDestroySharedBufferMsg destroySendBufMsg(Connection->antSharedSend);
        destroySendBufMsg.Call(Net_IPStackRef, sizeof(antEnvDestroySharedBufferMsg));

        // Tell ANT to destroy Receive shared buffer
        Connection->antSharedRecv.UnMap();
        antEnvDestroySharedBufferMsg destroyRecvBufMsg(Connection->antSharedRecv);
        destroyRecvBufMsg.Call(Net_IPStackRef, sizeof(antEnvDestroySharedBufferMsg));

        // Call the close message
        closeMsg.Call(Net_IPStackRef, sizeof(TCPEndpointCloseMsg));
        NetTCPError = Connection->Error = closeMsg.error;
        if ((closeMsg.error != TCP_SUCCESS) && (mode == CLOSE_CLEAN_ABORT)) {
            TCPEndpointCloseMsg abortMsg(Connection->antEndpoint, TRUE);
            abortMsg.Call(Net_IPStackRef, sizeof(TCPEndpointCloseMsg));
            NetTCPError = abortMsg.error;
        }
        if ((mode != CLOSE_CLEAN_ONLY) || (Connection->Error == TCP_SUCCESS)) {
            Net_RemoveConnection(Connection);
            delete Connection;
            return 1;
        } else {
            return 0;
        }

    } else { // if now <close is non-blocking>

        closeMsg.continuation = (void*)Connection;
        closeMsg.Send(Net_IPStackRef, Net_myOID,
                        Extra_Entry[entryNet_CloseCont], sizeof(closeMsg));
        return 1;
    } // if <close was blocking or not>
}



inline int NetClose(NetTCPConnection Connection)
{
    return NetClose(Connection, CLOSE_CLEAN_ABORT);
}



void NET_OBJECT::Net_CloseCont(ANTENVMSG msg)
{
    TCPEndpointCloseMsg* closeMsg
        = (TCPEndpointCloseMsg*)antEnvMsg::Receive(msg);
    NetTCPConnection Connection = (NetTCPConnection)(closeMsg->continuation);

    // Set the error only if we care about it
    if (Connection->CloseMode == CLOSE_CLEAN_ONLY) {
        NetTCPError = Connection->Error = closeMsg->error;
    }

    if (closeMsg->error != TCP_SUCCESS) {

        // If the close resulted in an error, check if we want to automatically
        // sumbit a new close as an abort. Aborts will proceed here in any case.

        if (Connection->CloseMode == CLOSE_CLEAN_ABORT) {
            NetClose(Connection, CLOSE_ABORT);
            return;
        }

        if (Connection->CloseMode == CLOSE_PASSIVE_CLEAN) {
            NetClose(Connection, CLOSE_PASSIVE_ABORT);
            return;
        }

    }

    // If we have a callback and this was not an aborted connect, call it now.
    if ((Connection->CloseCallback != NULL) && (Connection->CloseMode != CLOSE_NONE)) {

        int Result = 0;

        // If this was _already_ an abort attempt, do as if everything were fine
        // and after the callback we'll just remove the connection and forget
        // about it. The only case in which Result will remain 0 is if the close
        // was explicitely called with a CLOSE_CLEAN_ONLY

        if ((Connection->CloseMode == CLOSE_PASSIVE_CLEAN) ||
            (Connection->CloseMode == CLOSE_PASSIVE_ABORT)) {
            Result = -1;    // -1 means a passive close
        } else {
            if (Connection->CloseMode == CLOSE_ABORT) {
                Result = 1; // +1 means we closed the connection
            }
        } 

        if (Connection->Call != NULL) {
            (*Connection->Call)(Connection->CloseCallback, Connection, Result);
        } else {
            (this->*(reinterpret_cast<netObjCallback>(Connection->CloseCallback)))
                                                              (Connection, Result);
        }
    }

    // Tell ANT to destroy Send shared buffer
    Connection->antSharedSend.UnMap();
    antEnvDestroySharedBufferMsg destroySendBufMsg(Connection->antSharedSend);
    destroySendBufMsg.Call(Net_IPStackRef, sizeof(antEnvDestroySharedBufferMsg));

    // Tell ANT to destroy Receive shared buffer
    Connection->antSharedRecv.UnMap();
    antEnvDestroySharedBufferMsg destroyRecvBufMsg(Connection->antSharedRecv);
    destroyRecvBufMsg.Call(Net_IPStackRef, sizeof(antEnvDestroySharedBufferMsg));

    // Now remove the connection (if we ever managed to connect) and delete it.
    if (Connection->CloseMode != CLOSE_NONE) {
        Net_RemoveConnection(Connection);
    }
    delete Connection;

}




int Net_Initialized()
{
    static int Net_Initialized_State = 0;
    if (Net_Initialized_State) {
        return 1;
    } else {
        Net_Initialized_State = 1;
        return 0;
    }
}






void Net_AddConnection(NetTCPConnection Connection)
{
    NetConnectionItem NewItem = new struct NetConnectionItemStruct;

    NewItem->Connection = Connection;
    NewItem->next       = Net_Connections;
    Net_Connections     = NewItem;
}


void Net_RemoveConnection(NetTCPConnection Connection)
{
    NetConnectionItem This = Net_Connections;
    NetConnectionItem Last;

    if ((Net_Connections != NULL) &&
        (Net_Connections->Connection == Connection)) {
         Net_Connections = Net_Connections->next;
         delete This;
         return;
    }

    Last = Net_Connections;
    This = Net_Connections->next;

    while (This != NULL) {
        if (This->Connection == Connection) {
            Last->next = This->next;
            delete This;
            return;
        }
        Last = This;
        This = This->next;
    }
}


bool Net_ConnectionValid(NetTCPConnection Connection)
{
    NetConnectionItem This = Net_Connections;

    while (This != NULL) {
       if (This->Connection == Connection) return true;
       This = This->next;
    }

    return false;
}



#endif // EasyNet_defs_h_DEFINED









