/*
   Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009 Her Majesty the Queen in
   Right of Canada (Communications Research Center Canada)
   */
/*
   This file is part of ODR-DabMux.

   ODR-DabMux 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 3 of the
   License, or (at your option) any later version.

   ODR-DabMux 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 ODR-DabMux.  If not, see <http://www.gnu.org/licenses/>.
   */

#include "TcpServer.h"
#include <iostream>
#include <stdio.h>
#include <errno.h>

#ifdef _WIN32
#else
#   include <sys/socket.h>
#endif

#ifdef TRACE_ON
# ifndef TRACE_CLASS
#  define TRACE_CLASS(class, func) cout <<"-" <<(class) <<"\t(" <<this <<")::" <<(func) <<endl
#  define TRACE_STATIC(class, func) cout <<"-" <<(class) <<"\t(static)::" <<(func) <<endl
# endif
#else
# ifndef TRACE_CLASS
#  define TRACE_CLASS(class, func)
#  define TRACE_STATIC(class, func)
# endif
#endif


/// Must be call once before doing any operation on sockets
int TcpServer::init()
{
#ifdef _WIN32
  WSADATA wsaData;
  WORD wVersionRequested = wVersionRequested = MAKEWORD( 2, 2 );
  
  int res = WSAStartup( wVersionRequested, &wsaData );
  if (res) {
    setInetError("Can't initialize winsock");
    return -1;
  }
#endif
  return 0;
}


/// Must be call once before leaving application
int TcpServer::clean()
{
#ifdef _WIN32
  int res = WSACleanup();
  if (res) {
    setInetError("Can't initialize winsock");
    return -1;
  }
#endif
  return 0;
}


/**
 *  Two step constructor. Create must be called prior to use this
 *  socket.
 */
TcpServer::TcpServer() :
  listenSocket(INVALID_SOCKET)
{
  TRACE_CLASS("TcpServer", "TcpServer()");
}


/**
 *  One step constructor.
 *  @param port The port number on which the socket will be bind
 *  @param name The IP address on which the socket will be bind.
 *              It is used to bind the socket on a specific interface if
 *              the computer have many NICs.
 */
TcpServer::TcpServer(int port, const char *name) :
  listenSocket(INVALID_SOCKET)
{
  TRACE_CLASS("TcpServer", "TcpServer(int, char*)");
  create(port, name);
}


/**
 *  Two step initializer. This function must be called after the constructor
 *  without argument as been called.
 *  @param port The port number on which the socket will be bind
 *  @param name The IP address on which the socket will be bind.
 *              It is used to bind the socket on a specific interface if
 *              the computer have many NICs.
 *  @return 0  if ok
 *          -1 if error
 */
int TcpServer::create(int port, const char *name)
{
  TRACE_CLASS("TcpServer", "create(int, char*)");
  if (listenSocket != INVALID_SOCKET)
    closesocket(listenSocket);
  address.setAddress(name);
  address.setPort(port);
  if ((listenSocket = socket(PF_INET, SOCK_STREAM, 0)) == INVALID_SOCKET) {
    setInetError("Can't create socket");
    return -1;
  }
  reuseopt_t reuse = 1;
  if (setsockopt(listenSocket, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse))
      == SOCKET_ERROR) {
    setInetError("Can't reuse address");
    return -1;
  }
  if (bind(listenSocket, address.getAddress(), sizeof(sockaddr_in)) == SOCKET_ERROR) {
    setInetError("Can't bind socket");
    closesocket(listenSocket);
    listenSocket = INVALID_SOCKET;
    return -1;
  }
  return 0;
}


/**
 *  Close the underlying socket.
 *  @return 0  if ok
 *          -1 if error
 */
int TcpServer::close()
{
    TRACE_CLASS("TcpServer", "close()");
    if (listenSocket != INVALID_SOCKET) {
        int res = closesocket(listenSocket);
        if (res != 0) {
            setInetError("Can't close socket");
            return -1;
        }
        listenSocket = INVALID_SOCKET;
    }
    return 0;    
}


/// Destructor
TcpServer::~TcpServer()
{
  TRACE_CLASS("TcpServer", "~TcpServer()");
  close();
}


int TcpServer::listen()
{
  TRACE_CLASS("TcpServer", "listen()");
  if (::listen(listenSocket, 1) == SOCKET_ERROR) {
    setInetError("Can't listen on socket");
    return -1;
  }
  return 0;
}


TcpSocket* TcpServer::accept()
{
  SOCKET socket;
  TcpSocket* client = NULL;
  InetAddress addr;
  socklen_t addrLen = sizeof(sockaddr_in);

  socket = ::accept(listenSocket, addr.getAddress(), &addrLen);
  if (socket == SOCKET_ERROR) {
    setInetError("Can't accept connection on socket");
  } else {
    client = new TcpSocket();
    client->setSocket(socket, addr);
  }
  return client;
}

/*
WSAEINTR
WSAEBADF
WSAEACCES
WSAEFAULT
WSAEINVAL
WSAEMFILE
WSAEWOULDBLOCK
WSAEINPROGRESS
WSAEALREADY
WSAENOTSOCK
WSAEDESTADDRREQ
WSAEMSGSIZE
WSAEPROTOTYPE
WSAENOPROTOOPT
WSAEPROTONOSUPPORT
WSAESOCKTNOSUPPORT
WSAEOPNOTSUPP
WSAEPFNOSUPPORT
WSAEAFNOSUPPORT
WSAEADDRINUSE
WSAEADDRNOTAVAIL
WSAENETDOWN
WSAENETUNREACH
WSAENETRESET
WSAECONNABORTED
WSAECONNRESET
WSAENOBUFS
WSAEISCONN
WSAENOTCONN
WSAESHUTDOWN
WSAETOOMANYREFS
WSAETIMEDOUT
WSAECONNREFUSED
WSAELOOP
WSAENAMETOOLONG
WSAEHOSTDOWN
WSAEHOSTUNREACH
WSAENOTEMPTY
WSAEPROCLIM
WSAEUSERS
WSAEDQUOT
WSAESTALE
WSAEREMOTE
WSAEDISCON
WSASYSNOTREADY
WSAVERNOTSUPPORTED
WSANOTINITIALISED
*/