/*! \file server.cpp
This is the Server class implementation.
It implements a standard non-blocking select() server.

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

#include "sysconfig.h"
#include "server.h"

//##ModelId=3ED44BF102A6
Server* Server::sInstance = 0;


/*!
  Constructor for Server
 */ 
//##ModelId=3ED44BF10327
//##ModelId=3ED44BF10330
Server::Server() : mShutdown(false) {}

/*!
  Destructor for Server
 */ 
//##ModelId=3ED44BF1032F
Server::~Server() {
    ConnList::iterator i;
    for (i = mUsers.begin(); i != mUsers.end();) {
        delete (*i);
    }
}

/*!
  Instance retrieves the singleton instance of Server
 
  \return A pointer to the singleton instance of Server
 */ 
//##ModelId=3ED44BF10311
Server* Server::Instance() {
    if (sInstance == 0)
        sInstance = new Server;
    return sInstance;
}

/*!
  Destroys the singleton instance of Server
 */ 
//##ModelId=3ED44BF10325
void Server::Destroy() {
    delete sInstance;
}

/*!
  Boot initializes the Server and gets it ready to accept incoming
  Connections.
 
  \return true if server boots correctly, false if an error occurs
 */ 
//##ModelId=3ED44BF10313
bool Server::Boot(unsigned short port) {
    unsigned long flags = 1;
    int opt = 1;
    mTo.sin_family = AF_INET;
    mTo.sin_addr.s_addr = INADDR_ANY;
    mTo.sin_port = htons (port);

    // Get a socket for the server to listen on.
    mAcceptor = socket (AF_INET, SOCK_STREAM, 0);
    if (mAcceptor == INVALID_SOCKET) {
        cout << "ERROR-Server(socket):" << WSAGetLastError () << endl;
        return false;
    }
    // set it up to be non-blocking
    if (ioctlsocket (mAcceptor, FIONBIO, &flags) == SOCKET_ERROR) {
        cout << "ERROR-Server(ioctlsocket):" << WSAGetLastError () << endl;
        return false;
    }
    if (setsockopt (mAcceptor, SOL_SOCKET, SO_REUSEADDR, (char *) &opt, sizeof (opt)) == SOCKET_ERROR) {
        cout << "ERROR-Server(setsockopt):" << WSAGetLastError () << endl;
        closesocket (mAcceptor);
        return false;
    }
    if (bind (mAcceptor, (struct sockaddr *) &mTo, sizeof (mTo)) == SOCKET_ERROR) {
        cout << "ERROR-Server(bind):" << WSAGetLastError () << endl;
        closesocket (mAcceptor);
        return false;
    }
    if (listen (mAcceptor, 5) == SOCKET_ERROR) {
        cout << "ERROR-Server(listen):" << WSAGetLastError () << endl;
        return false;
    }
    return true;
}

/*!
  Run starts the Server running to process incomming connection,
  input and output requests.  It also executes commands from
  input requests.
 
 */ 
//##ModelId=3ED44BF1031C
void Server::Run() {
    struct timeval timeout;
    timeout.tv_sec = 60;
    timeout.tv_usec = 0;
    ConnList::iterator i, curr;

    while (!mShutdown) {
        InitFDs();
        if (select(0, &mInputFDs, &mOutputFDs, NULL, &timeout) == SOCKET_ERROR) {
            cout << "ERROR-Server(select):" << WSAGetLastError () << endl;
            break;
        }
        Execute();
        if (FD_ISSET(mAcceptor, &mInputFDs)) {
            Connection * c = AcceptConnection();
            if (c) {
                c->SendMsg("Welcome!\r\n");
                mUsers.push_back(c);
            }
        }
        for (i = mUsers.begin(); i != mUsers.end(); i++) {
            if (FD_ISSET((*i)->GetSocket(), &mOutputFDs))
                (*i)->HandleOutput();
            if (FD_ISSET((*i)->GetSocket(), &mInputFDs))
                (*i)->HandleInput();
        }
        for (i = mUsers.begin(); i != mUsers.end();) {
            curr = i++;
            if ((*curr)->CanBeDisconnected()) {
                (*curr)->Disconnect();
                delete (*curr);
                mUsers.erase(curr);
            }
        }
    }
    ShutdownServer();
}

/*!
  AcceptConnection handles an incoming connection request on the server's
  listening port by creating a Connection object for the socket.
 
  \return A pointer to the new connection
 */ 
//##ModelId=3ED44BF10344
Connection* Server::AcceptConnection() {
    struct sockaddr_in from;
    Connection *conn;
    int fromlen = sizeof (from);
    SOCKET psock = accept (mAcceptor, (struct sockaddr *) & from, &fromlen);
    if (psock == INVALID_SOCKET) {
        return NULL;
    } else {
        unsigned long flags = 1;
        ioctlsocket (psock, FIONBIO, &flags);
        conn = new Connection(psock);
        return conn;
    }
}

/*!
  InitFDs initializes the server's select queue and then re-registers
  interest in all the connection's sockets in the server's user list
  including the server's listening port itself.
 
  \note
  It may well be more efficient to register or unregister interest
  on a connection by connection basis in the various connection
  handling routines.
 */ 
//##ModelId=3ED44BF1034D
void Server::InitFDs () {
    FD_ZERO (&mInputFDs);
    FD_ZERO (&mOutputFDs);
    FD_SET (mAcceptor, &mInputFDs);
    for (ConnList::iterator i = mUsers.begin(); i != mUsers.end(); i++) {
        FD_SET ((*i)->GetSocket(), &mInputFDs);
        FD_SET ((*i)->GetSocket(), &mOutputFDs);
    }
}

/*!
  Execute processes the messages contained in all the input queues of
  all the connections in the server's user list.
 
  \remarks
  The are only two kinds of messages.
    - @shutdown - requests the server to shutdown
    - everything else is interpreted as public conversation
 */ 
//##ModelId=3ED44BF1034E
void Server::Execute() {
    ConnList::iterator i, o;
    string* msg;
    for (i = mUsers.begin(); i != mUsers.end(); i++) {
        while ((msg = (*i)->ReadMsg()) != NULL) {
            if (!msg->compare("@shutdown")) {
                mShutdown = true;
                for (o = mUsers.begin(); o != mUsers.end(); o++) {
                    (*o)->SendMsg("Shutting down..BYE.\r\n");
                }
                delete msg;
                return ;
            }
            msg->append("\r\n");
            for (o = mUsers.begin(); o != mUsers.end(); o++) {
                (*o)->SendMsg(*msg);
            }
            delete msg;
        }
    }
}

/*!
  ShutdownServer requests each of the connections to disconnect in the
  server's user list, deletes the connections, and erases them from
  the user list.  It then closes its own listening port.
 
 */ 
//##ModelId=3ED44BF10357
void Server::ShutdownServer() {
    ConnList::iterator i, curr;
    for (i = mUsers.begin(); i != mUsers.end();) {
        curr = i++;
        (*curr)->Disconnect();
        delete (*curr);
        mUsers.erase(curr);
    }
    closesocket(mAcceptor);
}