/* This file is part of cardwords
   (c) 1999 Tobias Peters
   see file COPYING for the copyright terms.
   
   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 2 of the License, or
   (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/
//cardwords_serversocket.hh

#ifndef CARDWORDS_SERVERSOCKET_HH
#define CARDWORDS_SERVERSOCKET_HH

#define CARDWORDS_SERVER_BACKLOG 2

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <socketbits.h>

#include <fcntl.h>
#include "cardwords_server.hh"
#include "cardwords_select.hh"
#include <vector>
#include "cardwords_exceptions.hh"

class CardWords_ServerSocket : public CardWords_Selectible {
public:
  CardWords_ServerSocket(CardWords_Selector *,
                       unsigned short int port_no = PORT_NUMBER);

  virtual void being_selected(int fd, int mode);
  virtual void timed_out(int, int){assert (0);};
private:
  CardWords_Selector * selector;
  
  int fd;

  int port_no;
  
  unsigned short int get_port_no(void) const;
  
  struct sockaddr_in sock_addr;
};

inline CardWords_ServerSocket::
CardWords_ServerSocket(CardWords_Selector *set_selector,
                     unsigned short int set_port_no = PORT_NUMBER)
  : selector(set_selector),
    port_no(set_port_no)
{
  fd = socket(AF_INET, SOCK_STREAM, 0);
  if (fd == -1) {
    throw CardWords_XLowResources(gettext("Creating Server socket "\
                                        "for incoming connections"));
  }

  sock_addr.sin_family = AF_INET;
  sock_addr.sin_port = htons (port_no);
  sock_addr.sin_addr.s_addr = htonl(INADDR_ANY);
  if (bind (fd, (struct sockaddr *) &sock_addr, sizeof (sock_addr)) < 0)
    {
      switch(errno) {
      case EADDRNOTAVAIL: case EADDRINUSE:
        throw CardWords_XLowResources(gettext("Binding the server socket"));
      case EBADF: case ENOTSOCK: case EINVAL: case EACCES: default:
        throw CardWords_XInternal_Error(gettext("Binding the server socket"));
      }
    }
  if (listen (fd, CARDWORDS_SERVER_BACKLOG) < 0) {
    switch(errno) {
    case EBADF: case ENOTSOCK: case EOPNOTSUPP: default:
      throw CardWords_XInternal_Error(gettext("Server socket: "\
                                              "listen() failed"));
    }
  }

  // Setting the socket to non-blocking operation because of the problem of
  // selected but not acceptible breaking incoming connections:
  int flags = fcntl(fd, F_GETFL);
  if (flags < 0) {
    switch(errno) {
    case EBADF: default:
      throw CardWords_XInternal_Error(gettext("Server socket: "\
                                              "fcntl() failed"));
    }
  }
  if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) < 0) {
    switch(errno) {
    case EBADF: default:
      throw CardWords_XInternal_Error(gettext("Server socket: "\
                                              "fcntl() failed"));
    }
  }

  selector->add(this, fd, READ_MODE, 0);
}

inline unsigned short int
CardWords_ServerSocket::get_port_no(void) const
{
  size_t length = sizeof (sock_addr);  
  if (getsockname (fd, (struct sockaddr *)&sock_addr, &length) < 0) {  
    switch (errno) {
    case ENOBUFS:
      throw CardWords_XLowResources(gettext("Server socket: "
                                          "getsockname() failed"));
    case EBADF: case ENOTSOCK: case EFAULT: default:
      throw CardWords_XInternal_Error(gettext("Server socket: "
                                           "getsockname() failed"));
    }
  }
  return ntohs (sock_addr.sin_port);  
}


#endif

