//
// Copyright (C) 1999-2002 Toshikaz Hirabayashi
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
// TOSHIKAZ HIRABAYASHI BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF
// OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
// Except as contained in this notice, the name of Toshikaz Hirabayashi shall
// not be used in advertising or otherwise to promote the sale, use or other
// dealings in this Software without prior written authorization from
// Toshikaz Hirabayashi.

#include <windows.h>
#include <WScom.h>
#include <win/WSDwinAppDev.h>
#include <win/WSDwinsocket.h>
#include <winsock2.h>

WSMFclassInit(WSDwinsocket,WSDwinsocket);

WSDsocket* _winsocket_create_handler(){
  WSDwinsocket* sock = new WSDwinsocket();
//printf("WSDwinsocket create\n");
  return sock;
};

static WSCbool initialized = False;

class  _winsocket_init {
  public:
  _winsocket_init(){
    WSDsocket::setCreateHandler(_winsocket_create_handler);
//printf("WSDwinsocket init\n");
  };
  ~_winsocket_init(){
     if (initialized != False){
       WSACleanup();
       initialized = False;
     }
  };
};
_winsocket_init   WSGIappXsocketInit;

WSDwinsocket::WSDwinsocket(){
  _socket = 0; 
  _listened = False;
  _sem = NULL;
}

WSDwinsocket::~WSDwinsocket(){
  if (_sem != NULL){
    long semcnt;
    ReleaseSemaphore(_sem,1,&semcnt);
    _sem = 0;
  }
  destroy();
}

long WSDwinsocket::destroy(){
  if (_socket != 0){
    closesocket(_socket);
    seterr();
    _socket = 0;
  }
  _listened = False;
  _started_accept = False;
  return WSDsocket::destroy();
}

long WSDwinsocket::connect(){
  if (_socket == 0){
    initialize();
    if (_socket == 0){
      return WS_ERR;
    }
  }
  if (_udp != False){
    return WS_ERR;
  }
  struct sockaddr_in server;
  struct hostent  *hp;
  if (!strcmp(_dest_naddr,"")){
    hp = gethostbyname("localhost");
  }else{
    hp = gethostbyname((char*)_dest_naddr);
  }
  server.sin_family = AF_INET;
  if (hp == NULL){
    server.sin_addr.s_addr = (unsigned long)inet_addr((char*)_dest_naddr);
    if ( server.sin_addr.s_addr == (unsigned long)-1 ) {
      return WS_ERR;
    }
  }else{
    server.sin_addr.s_addr = *(unsigned long *)hp->h_addr;
  }
  server.sin_port = htons( _dest_port );

  if( ::connect(_socket, (struct sockaddr *)&server,sizeof server) < 0 ){
    seterr();
    return WS_ERR;
  }
  return WS_NO_ERR;
}

long WSDwinsocket::listen(){
  if (_socket == 0){
    initialize();
    if (_socket == 0){
      return WS_NO_ERR;
    }
  }
  if (_udp != False){
    return WS_ERR;
  }
  struct sockaddr_in server;

  server.sin_family = AF_INET;
  if (!strcmp((char*)_naddr,"")){
    server.sin_addr.s_addr = INADDR_ANY;
  }else{
    server.sin_addr.s_addr = 
        (unsigned long)inet_addr((char*)_naddr);
  }
  server.sin_port = htons( _port );
  int val;
  val = 1;
  setsockopt(_socket, SOL_SOCKET, SO_REUSEADDR, (char *)&val, sizeof val);

  if( bind(_socket, (struct sockaddr *)&server, sizeof server) < 0){
    seterr();
    return WS_ERR;
  }
  OSVERSIONINFO osvi;
  osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
  GetVersionEx(&osvi);
  if (osvi.dwPlatformId == VER_PLATFORM_WIN32_NT){
    int len;
    if(getsockname(_socket, (struct sockaddr *)&server, &len) < 0){
      seterr();
      return WS_ERR;
    }
  }
  if ( ::listen(_socket, 5) != 0){
    seterr();
    return WS_ERR;
  }
  _listened = True;
  seterr();
  return WS_NO_ERR;
}
void* WSDwinsocket::accept(){
  if (_socket == 0){
    initialize();
    if (_socket == 0){
      return 0;
    }
  }
  if (_udp != False){
    return 0;
  }
  if (_listened == False){
    if (listen() != WS_NO_ERR){
      return 0;
    }
  }
  struct sockaddr_in sock_addr;
  int hlen = sizeof(sock_addr);

  int sock = ::accept(_socket,(sockaddr*)&sock_addr,&hlen);
  seterr();
  if (sock < 0){
    return 0;
  }
//  if (from_addr != NULL){
//    *from_addr = sock_addr.sin_addr.s_addr;
//  }
  _set_dest_addr_(htonl( sock_addr.sin_addr.s_addr));
  return (void*)sock;
}

struct tmp_data{
  WSDwinsocket* obj;
  void(*hd)(WSDsocket*,void*,WSCulong);
};

void* WSDwinsocket::_accept_thread_(void* ptr){
  tmp_data* dt = (tmp_data*)ptr;
  WSDwinsocket* _this = dt->obj;
  void(*hd)(WSDsocket*,void*,WSCulong) = dt->hd;
  delete dt;

  while(1){
    struct sockaddr_in sock_addr;
    int hlen = sizeof(sock_addr);
//printf("accept start...\n");
    int sock = ::accept(_this->_socket,(sockaddr*)&sock_addr,&hlen);
    _this->seterr();
//printf("accept done...\n");
    if (sock < 0){
//printf("return2\n");
      return 0;
    }
    if (_this->_stop_accept != False){
//printf("return3\n");
      _this->_stop_accept = False;
      return 0;
    }
    _accept_send_type* data = new _accept_send_type;
    data->sock = _this;
    data->socket = sock;
    data->addr = htonl(sock_addr.sin_addr.s_addr);
    data->hd = hd;

    DWORD result;
    LONG ret = SendMessageTimeout( WSGIwinAppDev()->getHWND(),
                                   WM_USER,(WPARAM)WS_WIN_ACCEPT,(LPARAM)data,
                                   SMTO_NORMAL,1000,&result);
    if (ret == False){
//      delete data; //do not delete!
    }    

  }
}
long WSDwinsocket::_continue_udp_read_accept(){
  if (_sem != NULL){
    long semcnt;
    ReleaseSemaphore(_sem,1,&semcnt);
    _sem = 0;
    return WS_NO_ERR;
  }
  return WS_ERR;
}
void* WSDwinsocket::_udp_read_thread_(void* ptr){
  tmp_data* dt = (tmp_data*)ptr;
  WSDwinsocket* _this = dt->obj;
  void(*hd)(WSDsocket*,void*,WSCulong) = dt->hd;
  delete dt;

  if (_this->_udp_binded == False){
    struct sockaddr_in server;
    server.sin_family = AF_INET;
    if (!strcmp((char*)_this->_naddr,"")){
      server.sin_addr.s_addr = INADDR_ANY;
    }else{
      server.sin_addr.s_addr = 
        (unsigned long)inet_addr((char*)_this->_naddr);
    }
    server.sin_port = htons( _this->_port );
//printf("WSDxsocket::_udp_read_thread bind\n");
    if( bind(_this->_socket, (struct sockaddr *)&server, sizeof(server)) < 0){
      _this->seterr();
      return NULL;
    }
    _this->seterr();
    _this->_udp_binded = True;
  }


  while(1){
    struct timeval tm;
    tm.tv_sec = 0xffff;
    tm.tv_usec = 0;

    fd_set read_fds;
    FD_ZERO(&read_fds);
    FD_SET((SOCKET)_this->_socket,&read_fds);
//printf("WSDxsocket::_udp_read_thread before select\n");
    long ret = select(FD_SETSIZE,&read_fds,NULL,NULL,&tm);
    long err = WSAGetLastError();
//printf("WSDxsocket::_udp_read_thread after select\n");
    if (ret < 0 && err != WSAEALREADY){
//printf("WSDxsocket::_udp_read_thread after return1\n");
      _this->seterr();
      return NULL;
    }
    if (ret < 0){
//printf("WSDxsocket::_udp_read_thread after timeout continue\n");
      continue;
    }
    if (ret == 0){
//printf("WSDxsocket::_udp_read_thread after timeout continue2\n");
      continue;
    }

    if (_this->_stop_accept != False){
//printf("WSDxsocket::_udp_read_thread after return2\n");
      _this->seterr();
      _this->_stop_accept = False;
      return 0;
    }

    HANDLE sem = CreateSemaphore(NULL,0,1,"WAIT_READ");
    _this->_sem = sem;
    _accept_send_type* data = new _accept_send_type;
    data->sock = _this;
    data->socket = _this->_socket;
    data->addr = 0;
    data->hd = hd;
//printf("WSDxsocket::_udp_read_thread create sem, send msg\n");

    DWORD result;
//    ret = SendMessageTimeout( WSGIwinAppDev()->getHWND(),
//                                   WM_USER,(WPARAM)WS_WIN_ACCEPT,(LPARAM)data,
//                                   SMTO_NORMAL,1000,&result);
    ret = SendMessage( WSGIwinAppDev()->getHWND(),
                                   WM_USER,(WPARAM)WS_WIN_ACCEPT,(LPARAM)data);
#if 0
    if (ret == False){
      _this->_sem = (HANDLE)NULL;
      CloseHandle(sem);
      delete data;
      return NULL;
    }    
#endif
    WaitForSingleObject(sem,INFINITE);
//printf("WSDxsocket::_udp_read_thread after sem.\n");
    _this->_sem = (HANDLE)NULL;
    CloseHandle(sem);
    if (_this->_stop_accept != False){
      _this->_stop_accept = False;
      return 0;
    }
  }
  return NULL;
}
long WSDwinsocket::acceptEx(void(*hd)(WSDsocket*,void*,WSCulong)){
  if (_started_accept != False){
    return WS_ERR;
  }
  if (hd == NULL){
    return WS_ERR;
  }
  if (_socket == 0){
    initialize();
    if (_socket == 0){
      return WS_ERR;
    }
  }
  if (_udp != False){
    DWORD temp;
    tmp_data* dt = new tmp_data;
    dt->obj = this;
    dt->hd = hd;

    HANDLE thr = CreateThread(0,0,(LPTHREAD_START_ROUTINE)_udp_read_thread_,(void*)dt,0,&temp);
    if (thr == 0){
      return WS_ERR;
    }
    _started_accept = True;

    return WS_NO_ERR;
  }
  if (_listened == False){
    if (listen() != WS_NO_ERR){
      return WS_ERR;
    }
  }
  DWORD temp;
  tmp_data* dt = new tmp_data;
  dt->obj = this;
  dt->hd = hd;

  HANDLE thr = CreateThread(0,0,(LPTHREAD_START_ROUTINE)_accept_thread_,(void*)dt,0,&temp);
  if (thr == 0){
    return WS_ERR;
  }
  _started_accept = True;

  return WS_NO_ERR;
}


long WSDwinsocket::close(void* sock){
  if (sock != 0){
    ::closesocket((int)sock);
    seterr();
    return WS_NO_ERR;
  }else{
    destroy();
    return WS_ERR;
  }
}
long WSDwinsocket::initialize(){
  if (_socket == 0){
    if (initialized == False){
      initialized = True;
      WORD wVersionReq;
      WSADATA wsaData;
      wVersionReq = MAKEWORD(2,0);
      int err = WSAStartup(wVersionReq,&wsaData);
      if (err != 0){
        seterr();
        return WS_ERR;
      }
      seterr();
    }
    if (_udp == False){
      _socket = socket(AF_INET,SOCK_STREAM,0);
      seterr();
    }else{
      _socket = socket(AF_INET,SOCK_DGRAM,0);
      seterr();
      _udp_binded = False;
    }
  }
  if (_socket < 0){
    _socket = 0;
    return WS_ERR;
  }
  return WSDsocket::initialize();
}
long WSDwinsocket::read(void* handle,WSCuchar* buf,long size){
  if (_udp != False){
    if (_udp_binded == False){
      struct sockaddr_in server;
      server.sin_family = AF_INET;
      if (!strcmp((char*)_naddr,"")){
        server.sin_addr.s_addr = INADDR_ANY;
      }else{
        server.sin_addr.s_addr = 
          (unsigned long)inet_addr((char*)_naddr);
      }
      server.sin_port = htons( _port );
      if( bind(_socket, (struct sockaddr *)&server, sizeof server) < 0){
        seterr();
        return WS_ERR;
      }
      _udp_binded = True;
    }
  }

  WSCulong timeout = 0;
  if (_socket == (SOCKET)handle){
    timeout = _timeout;
  }else{
    timeout = _cl_timeout;
  }
  if (timeout > 0){
    WSCulong val =1;
    int fl = ioctlsocket((SOCKET)handle,FIONBIO,&val);
    if (fl == SOCKET_ERROR){
      seterr();
      return 0;
    }
  }else{
    WSCulong val =0;
    int fl = ioctlsocket((SOCKET)handle,FIONBIO,&val);
    if (fl == SOCKET_ERROR){
      seterr();
      return 0;
    }
  }
  long read_len = 0;
  long len_to_read = size;
  long read_first = True;
  while(1){
//printf("here-1\n");
    long ret = 0;
    if (_udp == False){
      ret = recv((SOCKET)handle,(char*)&buf[read_len],len_to_read,0);
    }else{
      struct sockaddr_in fromaddr;
      fromaddr.sin_family = AF_INET;
      fromaddr.sin_port = 0;
      fromaddr.sin_addr.s_addr = 0;
      int len = sizeof(fromaddr);
      ret = recvfrom((SOCKET)handle,(char*)&buf[read_len],len_to_read,0,(sockaddr*)&fromaddr,&len);
      _set_dest_addr_(htonl(fromaddr.sin_addr.s_addr));
      _dest_port = htons(fromaddr.sin_port);

    }
    long err = WSAGetLastError();
    if (timeout > 0){
//printf("here-2 ret=%d\n",ret);
      if (ret > 0){
        read_len +=  ret;
        len_to_read -= ret;
        if (len_to_read == 0){
//printf("here4\n");
          seterr(0);
          return read_len;
        }
      }else if (ret == 0){
        if (read_first == False){
//printf("here3\n");
          seterr();
          return read_len;
        }
      }else if (ret < 0){
        if (err != WSAEWOULDBLOCK){
//printf("here2 err=%d\n",err);
          seterr();
          return read_len;
        }
      }
      struct timeval tm;
      tm.tv_sec = timeout;
      tm.tv_usec = 0;

      fd_set read_fds;
      FD_ZERO(&read_fds);
      FD_SET((SOCKET)handle,&read_fds);
      ret = select(FD_SETSIZE,&read_fds,NULL,NULL,&tm);
      err = WSAGetLastError();
//printf("here-3 ret=%d\n",ret);
      if (ret < 0 && err != WSAEALREADY){
//printf("here1\n");
        seterr();
        return read_len;
      }
      if (ret == 0){
        seterr(WSAETIMEDOUT);
        return read_len;
      }
      continue;
    }
    seterr();
    return ret;
  }
  seterr();
  return read_len;
}
long WSDwinsocket::read(WSCuchar* buf,long size){
  if (_socket == 0 && _udp != False){
    initialize();
    if (_socket == 0){
      return -1;
    }
  }

  if (_socket != 0){
    long ret = read((void*)_socket,buf,size);
    return ret;
  }
  return -1;
}
long WSDwinsocket::write(void* handle,WSCuchar* buf,long size){
  if (_udp != False){
    int val;
    val = 1;
    int ret = setsockopt((int)handle, SOL_SOCKET, SO_BROADCAST, (char *)&val, sizeof(val));
    if (ret <0){
      seterr();
      return 0;
    }
//printf("WSDxsocket::write setsockopt... _socket=%d handle=%d ret=%d\n",_socket,handle,ret);
  }

  WSCulong timeout = 0;
  if (_socket == (SOCKET)handle){
    timeout = _timeout;
  }else{
    timeout = _cl_timeout;
  }
  if (timeout > 0){
    WSCulong val =1;
    int fl = ioctlsocket((SOCKET)handle,FIONBIO,&val);
    if (fl == SOCKET_ERROR){
      seterr();
      return 0;
    }
  }else{
    WSCulong val =0;
    int fl = ioctlsocket((SOCKET)handle,FIONBIO,&val);
    if (fl == SOCKET_ERROR){
      seterr();
      return 0;
    }
  }
  long write_len = 0;
  long len_to_write = size;
  long write_first = True;
  while(1){
    long ret = 0;
    if (_udp == False){
      ret = send((SOCKET)handle,(char*)&buf[write_len],len_to_write,0);
    }else if (_udp_binded == False){
      struct sockaddr_in toaddr;
      toaddr.sin_family = AF_INET;
      toaddr.sin_port = htons( _dest_port );

      if (!strcmp(_dest_naddr,"")){
        toaddr.sin_addr.s_addr = htonl(INADDR_BROADCAST);
      }else{
        toaddr.sin_addr.s_addr =(unsigned long)inet_addr((char*)_dest_naddr);
      }

      ret = sendto((int)handle,(char*)&buf[write_len],len_to_write,0,
                   (sockaddr*)&toaddr,sizeof(toaddr));
    }
    long err = WSAGetLastError();
//printf("WSDxsocket::write send ret=%d err=%d\n",ret,err);

    if (timeout > 0){
      if (ret > 0){
        write_len +=  ret;
        len_to_write -= ret;
      }else if (ret == 0){
        if (write_first == False){
          seterr(WSAETIMEDOUT);
          return write_len;
        }
      }else if (ret < 0){
        if (err != WSAEWOULDBLOCK){
          seterr();
          return write_len;
        }
      }
      struct timeval tm;
      tm.tv_sec = timeout;
      tm.tv_usec = 0;

      fd_set write_fds;
      FD_ZERO(&write_fds);
      FD_SET((SOCKET)handle,&write_fds);
      ret = select(FD_SETSIZE,NULL,&write_fds,NULL,&tm);
      err = WSAGetLastError();
      if (ret < 0 && err != WSAEALREADY){
        seterr();
        return write_len;
      }
      if (write_len == size){
        seterr(0);
        return write_len;
      }
      continue;
    }
    seterr();
    return ret;
  }
  seterr();
  return write_len;
}
long WSDwinsocket::write(WSCuchar* buf,long size){
  if (_socket == 0 && _udp != False){
    initialize();
    if (_socket == 0){
      return -1;
    }
  }
  if (_socket != 0){
    long ret = write((void*)_socket,buf,size);
    return ret;
  }
  return -1;
}

char* msg_no_err = "No error occurred!";
char* msg_unknown_err = "Unknown error";
struct ErrorStrings{
  int err;
  char* msg;
};
ErrorStrings errstrs[] = {
    {WSAEINTR,           "WSAEINTR - Interrupted"},
    {WSAEBADF,		 "WSAEBADF - Bad file number"},		           
    {WSAEFAULT,  	 "WSAEFAULT - Bad address"},          
    {WSAEINVAL,          "WSAEINVAL - Invalid argument"},          
    {WSAEMFILE,          "WSAEMFILE - Too many open files"},          
    {WSAEWOULDBLOCK,  	 "WSAEWOULDBLOCK - Socket marked as non-blocking"},
    {WSAEINPROGRESS,     "WSAEINPROGRESS - Blocking call in progress"},
    {WSAEALREADY,        "WSAEALREADY - Command already completed"},
    {WSAENOTSOCK,        "WSAENOTSOCK - Descriptor is not a socket"},
    {WSAEDESTADDRREQ,    "WSAEDESTADDRREQ - Destination address required"},  
    {WSAEMSGSIZE,        "WSAEMSGSIZE - Data size too large"},     
    {WSAEPROTOTYPE,      "WSAEPROTOTYPE - Protocol is of wrong type for this socket"},   
    {WSAENOPROTOOPT,     "WSAENOPROTOOPT - Protocol option not supported for this socket type"},  
    {WSAEPROTONOSUPPORT, "WSAEPROTONOSUPPORT - Protocol is not supported"},
    {WSAESOCKTNOSUPPORT, "WSAESOCKTNOSUPPORT - Socket type not supported by this address family"},
    {WSAEOPNOTSUPP,      "WSAEOPNOTSUPP - Option not supported"},   
    {WSAEPFNOSUPPORT,    "WSAEPFNOSUPPORT - "}, 
    {WSAEAFNOSUPPORT,    "WSAEAFNOSUPPORT - Address family not supported by this protocol"},
    {WSAEADDRINUSE,      "WSAEADDRINUSE - Address is in use"},   
    {WSAEADDRNOTAVAIL,   "WSAEADDRNOTAVAIL - Address not available from local machine"},
    {WSAENETDOWN,        "WSAENETDOWN - Network subsystem is down"},     
    {WSAENETUNREACH,     "WSAENETUNREACH - Network cannot be reached"},  
    {WSAENETRESET,       "WSAENETRESET - Connection has been dropped"},    
    {WSAECONNABORTED,    "WSAECONNABORTED - "}, 
    {WSAECONNRESET,      "WSAECONNRESET - "},   
    {WSAENOBUFS,         "WSAENOBUFS - No buffer space available"},      
    {WSAEISCONN,         "WSAEISCONN - Socket is already connected"},      
    {WSAENOTCONN,        "WSAENOTCONN - Socket is not connected"},     
    {WSAESHUTDOWN,       "WSAESHUTDOWN - Socket has been shut down"},    
    {WSAETOOMANYREFS,    "WSAETOOMANYREFS - "}, 
    {WSAETIMEDOUT,       "WSAETIMEDOUT - Command timed out"},    
    {WSAECONNREFUSED,    "WSAECONNREFUSED - Connection refused"}, 
    {WSAELOOP,           "WSAELOOP - "},        
    {WSAENAMETOOLONG,    "WSAENAMETOOLONG - "}, 
    {WSAEHOSTDOWN,       "WSAEHOSTDOWN - "},    
    {WSAEHOSTUNREACH,    "WSAEHOSTUNREACH - "}, 
    {WSAENOTEMPTY,       "WSAENOTEMPTY - "},    
    {WSAEPROCLIM,        "WSAEPROCLIM - "},     
    {WSAEUSERS,          "WSAEUSERS - "},       
    {WSAEDQUOT,          "WSAEDQUOT - "},       
    {WSAESTALE,          "WSAESTALE - "},       
    {WSAEREMOTE,         "WSAEREMOTE - "},      
    {WSASYSNOTREADY,     "WSASYSNOTREADY - Network subsystem not ready"},  
    {WSAVERNOTSUPPORTED, "WSAVERNOTSUPPORTED - Version not supported"},
    {WSANOTINITIALISED,  "WSANOTINITIALISED - WSAStartup() has not been successfully called"},

    {WSAHOST_NOT_FOUND,  "WSAHOST_NOT_FOUND - Host not found"},
    {WSATRY_AGAIN,       "WSATRY_AGAIN - Host not found or SERVERFAIL"},
    {WSANO_RECOVERY,     "WSANO_RECOVERY - Non-recoverable error"},
    {WSANO_DATA,         "WSANO_DATA - (or WSANO_ADDRESS) - No data record of requested type"},
    {-1,		 NULL}
};

void WSDwinsocket::seterr(int er){
  int err = WSAGetLastError();
  if (er > -1){
    err = er;
  }
  if (err == 0){
    _error_str = msg_no_err;
    return;
  }
  WSASetLastError(0);

  int i = 0;
  while(errstrs[i].msg){
    if (err == errstrs[i].err){
      _error_str = errstrs[i].msg;
      return;
    }
    i++;
  }
  _error_str = msg_unknown_err;
}
WSCstring WSDwinsocket::getLastError(){
  return _error_str;
}
