/*! \file connection.cpp
This is the generic Connection class implementation.

\author Jon A. Lambert
\date 02/15/2003
\version 0.10
*/

#include "sysconfig.h"
#include "connection.h"


/*!
  Constructor for Connection.  There is no default constructor as
  all connections must be constructed with a socket.
 */ 
//##ModelId=3ED44BF10253
Connection::Connection(SOCKET sock)
        : mSock(sock), mpPendingInput(NULL), mpPendingOutput(NULL),
mDisconnectable(false) {}

/*!
  Destructor for Connection
 */ 
//##ModelId=3ED44BF10255
Connection::~Connection() {
    delete[] mpPendingOutput;
    delete[] mpPendingInput;
}

/*!
  Copy Constructor for Connection
 */ 
//##ModelId=3ED44BF1025E
Connection::Connection(const Connection& r_conn)
        : mDisconnectable(r_conn.mDisconnectable), mSock(r_conn.mSock),
mpPendingInput(NULL), mpPendingOutput(NULL) {
    mInBuffer = r_conn.mInBuffer;
    mOutBuffer = r_conn.mOutBuffer;
    if (r_conn.mpPendingInput) {
        mpPendingInput = new char[strlen(r_conn.mpPendingInput) + 1];
        strcpy(mpPendingInput, r_conn.mpPendingInput);
    }
    if (r_conn.mpPendingOutput) {
        mpPendingOutput = new char[strlen(r_conn.mpPendingOutput) + 1];
        strcpy(mpPendingOutput, r_conn.mpPendingOutput);
    }
}

/*!
  Assignment operator for Connection
 */ 
//##ModelId=3ED44BF10288
Connection & Connection::operator=(const Connection& r_conn) {
    if (this == &r_conn)
        return * this;
    mInBuffer = r_conn.mInBuffer;
    mOutBuffer = r_conn.mOutBuffer;
    if (r_conn.mpPendingInput) {
        mpPendingInput = new char[strlen(r_conn.mpPendingInput) + 1];
        strcpy(mpPendingInput, r_conn.mpPendingInput);
    }
    if (r_conn.mpPendingOutput) {
        mpPendingOutput = new char[strlen(r_conn.mpPendingOutput) + 1];
        strcpy(mpPendingOutput, r_conn.mpPendingOutput);
    }
    return *this;
}

/*!
 HandleInput is called to order a connection to process any input
 waiting on its socket.  Input is parsed into lines based on the
 occurance of the CRLF terminator and pushed into a buffer
 which is a list of lines.  The buffer expands dynamically as input
 is processed.  Input that has yet to see a CRLF terminator
 is left in the Connection's pPendingInput array.
 
 \notes
 Unix/BSD portability notes:
   - Either read() or recv() may be used.
   - Winsock errors are prepended with WSA modify accordingly.
   - WSAGetLastError() should be replaced with global errno variable.
 
 \see Server::Run
 */ 
//##ModelId=3ED44BF10260
void Connection::HandleInput() {
    string strin;
    char inbuf[4096];
    // If there was input leftover from the last call to HandleInput
    // prepend it to the input string and clear it.
    if (mpPendingInput) {
        strin.append(mpPendingInput);
        delete[] mpPendingInput;
        mpPendingInput = NULL;
    }
    // Loop to gather all socket input in 4K chunks.
    while (true) {
        int nbytes = recv (mSock, inbuf, 4096, 0);
        if (nbytes == 0) {
            // Connection is scheduled for disconnection, and we'll exit
            mDisconnectable = true;
            return ;
        } else if (nbytes == SOCKET_ERROR) {
            int err = WSAGetLastError ();
            if (err == WSAEWOULDBLOCK) {
                // Indicates no more input can be squeezed from this socket.
                // Let's bust this loop
                break;
            } else {
                // We have a serious error
                // Connection is scheduled for disconnection, and we'll exit
                cout << "Error-Connection(recv):" << err << " on " << mSock << endl;
                mDisconnectable = true;
                return ;
            }
        }
        // Append everything read to our string.
        inbuf[nbytes] = '\0';
        strin.append(inbuf);
    }
    // Loop to parse all input into lines for our buffer
    while (true) {
        int pos = strin.find("\r\n");
        if (pos != (int)strin.npos) {
            // A line was found; save it to buffer and remove from input string
            mInBuffer.push_back(strin.substr(0, pos));
            strin.erase(0, pos + 2);
        } else {
            // No	more complete lines found
            if (!strin.empty()) {
                // Save leftovers into pending input array
                mpPendingInput = new char[strin.size() + 1];
                strcpy(mpPendingInput, strin.c_str());
            }
            break;
        }
    }
}

/*!
 HandleOutput is called to order a connection to process any output
 waiting on its socket.
 
 \notes
 Unix/BSD portability notes:
   - Either send() or write() may be used.
   - Winsock errors are prepended with WSA modify accordingly.
   - WSAGetLastError() should be replaced with global errno variable.
 
 \see Server::Run
 */ 
//##ModelId=3ED44BF10268
void Connection::HandleOutput() {
    string strout;
    // If there is output left to be sent from last time append
    // it to our output string
    if (mpPendingOutput) {
        strout.append(mpPendingOutput);
        delete[] mpPendingOutput;
        mpPendingOutput = NULL;
    }
    // If there is any output waiting in line buffer append the first line
    if (!mOutBuffer.empty()) {
        strout.append(mOutBuffer.front());
        mOutBuffer.pop_front();
    }
    // Loop until our output string is empty
    while (!strout.empty()) {
        const char * pstr = strout.c_str();
        while (pstr) {  // Loop until we have written this string
            int nbytes = send (mSock, pstr, strlen(pstr), 0);
            if (nbytes == SOCKET_ERROR) {
                int err = WSAGetLastError ();
                if (err == WSAEWOULDBLOCK) {
                    // socket will block - save unwritten output in pending array
                    // and leave
                    mpPendingOutput = new char[strlen(pstr) + 1];
                    strcpy(mpPendingOutput, pstr);
                    return ;
                }
                // severe error set socket as disconnectable and leave
                cout << "Error-Connection(recv):" << err << " on " << mSock << endl;
                mDisconnectable = true;
                return ;
            } else if (nbytes < (int)strlen(pstr)) {
                pstr += nbytes;
            } else {
                pstr = NULL;
            }
        }
        strout.erase();
        // fetch next line in buffer if not empty
        if (!mOutBuffer.empty()) {
            strout.append(mOutBuffer.front());
            mOutBuffer.pop_front();
        }
    }
}

/*!
 Disconnect is called to shutdown a Connection's socket.
 */ 
//##ModelId=3ED44BF10272
void Connection::Disconnect() {
    char inbuf[4096];
    bool done = false;
    shutdown(mSock, SD_SEND);
    while (!done) {
        int nbytes = recv (mSock, inbuf, 4096, 0);
        if (!nbytes || nbytes == SOCKET_ERROR)
            done = true;
    }
    closesocket(mSock);
}

/*!
 SendMsg places a message on the Connection's output buffer.
 
 \param r_msg the message, a reference to a string
 */ 
//##ModelId=3ED44BF10274
void Connection::SendMsg(const string& r_msg) {
    mOutBuffer.push_back(r_msg);
}

/*!
 ReadMsg removes a message from the Connection's input buffer.
 
 \return the message, a new string or NULL if no messages
 */ 
//##ModelId=3ED44BF1027D
string* Connection::ReadMsg() {
    if (!mInBuffer.empty()) {
        string msg = mInBuffer.front();
        mInBuffer.pop_front();
        return new string(msg);
    }
    return NULL;
}

/*!
 CanBeDisconnected is called to query the Connection in order to see
 whether it wants to be disconnected.  Currently this status is
 set only when there is an error.
 
 \return a boolean value indicating whether the connection is to be
         disconnected.
 */ 
//##ModelId=3ED44BF1026A
bool Connection::CanBeDisconnected() {
    return mDisconnectable;
}

/*!
 GetSocket
 
 \return the Connection's SOCKET value.
 */ 
//##ModelId=3ED44BF10286
SOCKET Connection::GetSocket() {
    return mSock;
}