/*!
  \file
  \brief 接続の受付

  \author Satofumi KAMIMURA

  $Id: TcpipAccepter.cpp 1343 2009-09-28 07:36:33Z satofumi $
*/

#include "TcpipAccepter.h"
#include "SdlNetInit.h"
#include "DetectOS.h"
#ifdef WINDOWS_OS
#include <windows.h>
#endif
#include "TcpipSocket.h"
#include <boost/lexical_cast.hpp>
#include <SDL_net.h>
#include <string>

using namespace qrk;
using namespace boost;
using namespace std;


struct TcpipAccepter::pImpl : private SdlNetInit
{
  string error_message_;
  IPaddress ip_address_;
  TCPsocket accept_socket_;
  SDLNet_SocketSet accept_set_;
  bool is_activated_;
  string server_name_;


  pImpl(void)
    : error_message_("no error."),
      accept_socket_(NULL), accept_set_(NULL), is_activated_(false),
      server_name_("unknown")
  {
  }


  bool activate(long port)
  {
    if (is_activated_) {
      deactivate();
    }

    if (SDLNet_ResolveHost(&ip_address_, NULL, static_cast<Uint16>(port)) < 0) {
      error_message_ =
        "activate(): can not assign port " + lexical_cast<string>(port)
        + " :" + SDLNet_GetError();
      return false;
    }

    accept_socket_ = SDLNet_TCP_Open(&ip_address_);
    if (accept_socket_ == NULL) {
      error_message_ =
        SDLNet_GetError() + (" (" + lexical_cast<string>(port) + ")");
      return false;
    }

    accept_set_ = SDLNet_AllocSocketSet(1);
    if (accept_set_ == NULL) {
      error_message_ = string("accept(): could not allock socket set.")
        + ": " + SDLNet_GetError();
      return false;
    }

    if (SDLNet_TCP_AddSocket(accept_set_, accept_socket_) < 0) {
      error_message_ = string("accept(): could not add socket.")
        + " :" + SDLNet_GetError();
      return false;
    }

    is_activated_ = true;

    // サーバ名の取得
#if defined(LINUX_OS)
    enum { BufferSize = 256 };
    char host[BufferSize];
    if (gethostname(host, BufferSize) == 0) {
      host[BufferSize - 1] = '\0';
      server_name_ = host;
    }
#else
    // !!! Windows 用の実装
#endif

    return true;
  }


  void deactivate(void)
  {
    if (! is_activated_) {
      return;
    }

    if (SDLNet_TCP_DelSocket(accept_set_, accept_socket_) < 0) {
      error_message_ = "deactivate(): SDLNet_GetError()";
      return;
    }

    SDLNet_FreeSocketSet(accept_set_);
    accept_set_ = NULL;

    SDLNet_TCP_Close(accept_socket_);
    accept_socket_ = NULL;

    is_activated_ = false;
    server_name_ = "unknown";
  }


  TcpipSocket* accept(int timeout)
  {
    if (is_activated_ == false) {
      error_message_ = "call activate() before accept() called.";
      return NULL;
    }

    int n = SDLNet_CheckSockets(accept_set_, timeout);
    if ((n <= 0) || (! SDLNet_SocketReady(accept_socket_))) {
      // 接続なし
      return NULL;
    }

    TCPsocket socket = SDLNet_TCP_Accept(accept_socket_);
    if (! socket) {
      return NULL;
    }

    return new TcpipSocket(socket);
  }
};


TcpipAccepter::TcpipAccepter(void) : pimpl(new pImpl)
{
}


TcpipAccepter::~TcpipAccepter(void)
{
}


const char* TcpipAccepter::what(void) const
{
  return pimpl->error_message_.c_str();
}


bool TcpipAccepter::activate(long port)
{
  return pimpl->activate(port);
}


void TcpipAccepter::deactivate(void)
{
  pimpl->deactivate();
}


TcpipSocket* TcpipAccepter::accept(int timeout)
{
  return pimpl->accept(timeout);
}


const char* TcpipAccepter::name(void)
{
  return pimpl->server_name_.c_str();
}
