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

  \author Satofumi KAMIMURA

  $Id: TcpipAccepter.cpp 1836 2010-05-26 11:23:08Z satofumi $
*/

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

using namespace qrk;
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();
        }

        enum { BufferSize = 256 };
        char buffer[BufferSize];
        snprintf(buffer, BufferSize - 1, "%ld", port);
        if (SDLNet_ResolveHost(&ip_address_,
                               NULL, static_cast<Uint16>(port)) < 0) {
            error_message_ =
                string("activate(): can not assign port ")
                + buffer + " :" + SDLNet_GetError();
            return false;
        }

        accept_socket_ = SDLNet_TCP_Open(&ip_address_);
        if (accept_socket_ == NULL) {
            error_message_ = string(SDLNet_GetError()) + " (" + buffer + ")";
            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)
        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();
}
