#include "cpnet.h"
#include <map>

static CRITICAL_SECTION criticalSection;

class SocketProperties
{
  jboolean blocking;
  jint timeout;
  
public:
  SocketProperties() : blocking(JNI_TRUE), timeout(0)
  {
  }

  inline jboolean isBlocking() const
  {
    return this->blocking;
  }

  inline void setBlocking(jboolean b)
  {
    this->blocking = b;
  }

  inline void setTimeout(jint t)
  {
    this->timeout = t;
  }

  inline jint getTimeout() const
  {
    return this->timeout;
  }


  static inline void lock()
  {
    EnterCriticalSection(&::criticalSection);
  }

  static inline void unlock()
  {
    LeaveCriticalSection(&criticalSection);
  }

};

static std::map<SOCKET, SocketProperties*> propMap;

extern "C"
void init_socket_properties ()
{
  InitializeCriticalSection(&criticalSection);
}

/**
 * ソケットのブロッキングモードを変更する
 */
extern "C"
int
cpnet_set_socket_blocking (SOCKET sock, jboolean blocking)
{
  int result = 0;

  SocketProperties::lock();

  std::map<SOCKET, SocketProperties*>::iterator iter = ::propMap.find(sock);

  if (iter == ::propMap.end())
  {
    if (! blocking)
    {
      SocketProperties* prop = new SocketProperties();
      prop->setBlocking(blocking);
      ::propMap.insert(std::make_pair(sock, prop));
    }
  }
  else
  {
    SocketProperties* prop = (*iter).second;
    prop->setBlocking(blocking);
  }

  // ブロッキングモードを変更する
  u_long arg = blocking ? 0 : 1;
  if (ioctlsocket (sock, FIONBIO, &arg) == SOCKET_ERROR)
    {
      result = WSAGetLastError ();
    }

  SocketProperties::unlock();

  return result;
}

/**
 * ソケットがブロッキングモードかどうかを返す。
 *
 * @param	sock	ソケット
 * @return	ブロッキングモードの場合はTRUE
 */
extern "C"
jboolean
cpnet_is_socket_blocking (SOCKET sock)
{
  jboolean result = JNI_TRUE;

  SocketProperties::lock();
  std::map<SOCKET, SocketProperties*>::iterator iter = ::propMap.find(sock);

  if (iter != ::propMap.end())
  {
    SocketProperties* prop = (*iter).second;
    result = prop->isBlocking();
  }

  SocketProperties::unlock();

  return result;
}

/**
 * ソケットタイムアウト値を取得する
 */
extern "C"
jint
cpnet_get_socket_timeout (SOCKET sock)
{
  jint result = 0;

  SocketProperties::lock();
  std::map<SOCKET, SocketProperties*>::iterator iter = ::propMap.find(sock);

  if (iter != ::propMap.end())
  {
    SocketProperties* prop = (*iter).second;
    result = prop->getTimeout();
  }

  SocketProperties::unlock();

  return result;
}

/**
 * ソケットのタイムアウト値を設定する
 */
extern "C"
void
cpnet_set_socket_timeout(SOCKET sock, jint timeout)
{
  SocketProperties::lock();

  std::map<SOCKET, SocketProperties*>::iterator iter = ::propMap.find(sock);

  if (iter == ::propMap.end())
  {
    SocketProperties* prop = new SocketProperties();
    prop->setTimeout(timeout);
    ::propMap.insert(std::make_pair(sock, prop));
  }
  else
  {
    SocketProperties* prop = (*iter).second;
    prop->setTimeout(timeout);
  }
  SocketProperties::unlock();
}


extern "C"
void cpnet_clear_socket_property(SOCKET sock)
{
  SocketProperties::lock();

  std::map<SOCKET, SocketProperties*>::iterator iter = ::propMap.find(sock);
  if (iter != ::propMap.end())
  {
    SocketProperties* prop = (*iter).second;
    delete prop;
    ::propMap.erase(iter);
  }
  SocketProperties::unlock();
}
