/*
  TCP/IP 制御
  Satofumi KAMIMURA
  $Id: tcpip_device.c 286 2008-10-20 09:40:22Z satofumi $
*/

#include "tcpip_device.h"
#include "connect_device.h"
#include "detect_os.h"
#include <stdlib.h>
#ifdef NoSDL_Linux
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/poll.h>
#include <unistd.h>
#include <stdio.h>
#elif !HAVE_CONFIG_H || HAVE_LIBSDL_NET
#include <SDL_net.h>
#endif


#ifndef NoSDL_Linux
static int Initialized = 0;
#endif
typedef struct {
#ifdef NoSDL_Linux
  struct sockaddr_in address;
  struct pollfd nfds;
#elif !HAVE_CONFIG_H || HAVE_LIBSDL_NET
  TCPsocket socket;
  SDLNet_SocketSet set;
#endif
} ids_info_t;
static ids_info_t ids[ID_MAX];
static int last_id = 0;


#ifndef NoSDL_Linux
static void initTcpipDevice(void) {
#if !HAVE_CONFIG_H || HAVE_LIBSDL_NET
  if (SDLNet_Init() < 0) {
    return;
  }
  atexit(SDLNet_Quit);
  Initialized = 1;
#endif
}
#endif


/*!
  \todo ID_MAX を越えたときの戻り値をマクロに置き換える
  \todo localhost 等のホスト指定で繋がるようにする
*/
int tcpip_open(const char *host, unsigned short port) {
#ifdef NoSDL_Linux
  int len = sizeof(ids[0].address);
  int fd = socket(AF_INET, SOCK_STREAM, 0);
  int ret;

  ids[last_id].nfds.fd = fd;
  ids[last_id].address.sin_family = AF_INET;
  ids[last_id].address.sin_addr.s_addr = inet_addr(host);
  ids[last_id].address.sin_port = htons(port);

  ret = connect(fd, (struct sockaddr*)&ids[last_id].address, len);
  if (ret < 0) {
    perror("connect");
    return -1;
  }
  // poll() 用の設定
  ids[last_id].nfds.events = POLLIN | POLLPRI | POLLERR | POLLHUP | POLLNVAL;
  ids[last_id].nfds.revents = 0;

  return last_id++;

#elif !HAVE_CONFIG_H || HAVE_LIBSDL_NET
  IPaddress ip;

  if (last_id >= ID_MAX) {
    return -1;
  }
  ids[last_id].socket = NULL;
  if (!Initialized) {
    initTcpipDevice();
  }

  SDLNet_ResolveHost(&ip, host, port);
  ids[last_id].socket = SDLNet_TCP_Open(&ip);
  if (!ids[last_id].socket) {
    return DEVICE_OPEN_ERROR;
  }

  ids[last_id].set = SDLNet_AllocSocketSet(1);
  SDLNet_TCP_AddSocket(ids[last_id].set, ids[last_id].socket);

  return last_id++;
#else
  return -1;
#endif
}


void tcpip_close(int id) {
#ifdef NoSDL_Linux
#elif !HAVE_CONFIG_H || HAVE_LIBSDL_NET
  if (ids[id].socket) {
    SDLNet_FreeSocketSet(ids[id].set);
    SDLNet_TCP_Close(ids[id].socket);
    ids[id].socket = NULL;
  }
#endif
}


int tcpip_is_connected(int id) {
#ifdef NoSDL_Linux
  return ids[id].nfds.fd >= 0;
#elif !HAVE_CONFIG_H || HAVE_LIBSDL_NET
  return (ids[id].socket) ? 1 : 0;
#else
  return 0;
#endif
}


int tcpip_recv(int id, char *data, int size, int timeout) {
#ifdef NoSDL_Linux
  int fd = ids[id].nfds.fd;
  int filled = 0;
  int n;

  while (filled < size) {
    if (poll(&ids[id].nfds, 1, timeout) == 0) {
      break;                    // timeout
    }
    n = read(fd, &data[filled], size - filled);
    if (n <= 0) {
      break;
    }
    filled += n;
  }
  return filled;

#elif !HAVE_CONFIG_H || HAVE_LIBSDL_NET
  int n;

  if (SDLNet_CheckSockets(ids[id].set, timeout) <= 0) {
    return 0;
  }
  if (SDLNet_SocketReady(ids[id].socket)) {
    n = SDLNet_TCP_Recv(ids[id].socket, data, size);
    if (n <= 0) {
      tcpip_close(id);
      return NO_CONNECT;
    }
  }
  return n;
#else
  return 0;
#endif
}


int tcpip_send(int id, const char *data, int length) {
#ifdef NoSDL_Linux
  return write(ids[id].nfds.fd, data, length);
#elif !HAVE_CONFIG_H || HAVE_LIBSDL_NET
  return SDLNet_TCP_Send(ids[id].socket, (char*)data, length);
#else
  return 0;
#endif
}
