/**********************************************************************
 *
 * netio.cpp
 * Copyright (C) 1996
 * 
 * A component of the fnord webserver written by bmorin@wpi.edu.
 *
 * Altered for use with the Greenstone digital library software by the
 * New Zealand Digital Library Project at the University of Waikato,
 * New Zealand.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 *********************************************************************/

#include <windows.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <memory.h>
#pragma hdrstop
#include "netio.h"
#include "locate.h"
#include "d_winsock.h"
#include "settings.h"

//Private Variables and Declarations

//Max string size for a host name
#define MAXHOSTNAME 100

//Timeout for waiting between "packets" in seconds
#define NETIO_CONN_TIMEOUT 20 //3 sec  was 180 @@@@@ WJR

#define HTTP_DNS_MSG WM_USER + 1
int dns_msg_received = 0;
int dns_msg_error = 0;

DWORD LocalIPNumber;
char LocalName[MAXHOSTNAME];

//Private Function Declarations
DWORD GetHostID();

// returns 0 on success,
// WSASYSNOTREADY, WSAVERNOTSUPPORTED, or WSAEINVAL on failure
int InitNetIO() {
  WSADATA Data;
  
  // Load Winsock
  int err = d_WSAStartup(MAKEWORD(1, 1), &Data);
  
  // get local IP number
  if (err == 0) LocalIPNumber = GetHostID();
  else LocalIPNumber = INADDR_ANY;
  
  // set the Local Name to "" for now
  LocalName[0] = 0;
  
  return err;
}


void CleanUpNetIO() {
  //Experimental, heard this will better clean up, especialy if a call is
  //blocking....
  d_WSAUnhookBlockingHook();

  //Clean up Winsock
  d_WSACleanup();
}


void ResetNetIO() {
  //Figure out the local name on first querry, set to "" until then...
  LocalName[0] = 0;
}


DWORD GetLocalIP() {
  return LocalIPNumber;
}

LRESULT __stdcall DNSWindowProc(HWND hWnd,UINT wMsg, WPARAM wParam, LPARAM lParam) {
  if (wMsg == HTTP_DNS_MSG) {
    dns_msg_received = 1;
    dns_msg_error = WSAGETASYNCERROR(lParam);
    return 0;
  }
  
  return DefWindowProc(hWnd, wMsg, wParam, lParam);
}

char *GetLocalName(HINSTANCE hInstance) {
  // static in case it is written to after the function has finished
  // (I did not error checking on WSACancelAsyncRequest)
  static char buf[MAXGETHOSTSTRUCT];
	
  if (LocalName[0] == 0) {
    hostent *DNSResult = NULL;
    in_addr LocalInAddr;
    
    // if we failed to get the local IP number
    // use the loop-back device
    if (LocalIPNumber == INADDR_ANY) {
      strcpy(LocalName, "127.0.0.1"); // loop-back device
      return LocalName;
    }
    
    //Convert the number to an in_addr struct
    LocalInAddr.s_addr = LocalIPNumber;
    
    // if we fail to find the domain name we will
    // still want the IP address
    strcpy(LocalName, d_inet_ntoa(LocalInAddr));
    
    // make sure they actually passed in an instance handle
    if (hInstance == NULL) return LocalName;
    
    // do a async domain name lookup so that we
    // can control the timeout
    
    // create a window class and window to handle the async messages
    WNDCLASS wc; HWND hwndDNS;
    
    wc.style = 0;
    wc.lpfnWndProc = DNSWindowProc;
    wc.cbClsExtra = 0;
    wc.cbWndExtra = 0;
    wc.hInstance = hInstance;
    wc.hIcon = NULL;
    wc.hCursor = NULL;
    wc.hbrBackground = NULL;
    wc.lpszMenuName = NULL;
    wc.lpszClassName = "GSDL DNS Window";
    if (!RegisterClass(&wc)) return LocalName;
    
    hwndDNS = CreateWindow("GSDL DNS Window", "", 
			   WS_OVERLAPPEDWINDOW, 0, 0, 100, 100, 
			   NULL, NULL, hInstance, NULL);
    if (!hwndDNS) return LocalName;
    
    // process all messages currently on the queue
    MSG Message;
    while (PeekMessage(&Message, NULL, 0, 0, PM_REMOVE)) {
      TranslateMessage(&Message); /* translate keyboard messages */
      DispatchMessage(&Message);  /* return control to Windows NT */
    }
    
    //Do a async DNS lookup on the IP number
    dns_msg_received = 0;
    dns_msg_error = 0;
    HANDLE asyncGetHostReq = d_WSAAsyncGetHostByAddr(
						     hwndDNS, HTTP_DNS_MSG, (char *)&(LocalInAddr), 
						     4, PF_INET, buf, MAXGETHOSTSTRUCT);
    
    if (asyncGetHostReq != NULL) {
      // wait 5 seconds for the request to complete
      int now = GetTickCount();
      while ((DiffTickCounts(now, GetTickCount()) < 5000) && !dns_msg_received) {
	if (PeekMessage(&Message, NULL, 0, 0, PM_REMOVE)) {
	  TranslateMessage(&Message); /* translate keyboard messages */
	  DispatchMessage(&Message);  /* return control to Windows NT */
	} else {
	  Sleep(1);
	}
      }
      if (!dns_msg_received)
	d_WSACancelAsyncRequest(asyncGetHostReq);
    }
    
    DestroyWindow(hwndDNS);
    
    if (dns_msg_received && (dns_msg_error == 0)) {
      //Worked, use the primary name
      strcpy(LocalName, ((hostent *)(buf))->h_name);
      //Convert it to lower case for cosmedic reasons
      CharLower(LocalName);
    }
  }
  return LocalName;
}


// returns 0 on success, and a WSA error message on failure.
// possible error messages include:
// WSANOTINITIALISED, WSAENETDOWN, WSAEAFNOSUPPORT, WSAEINPROGRESS, WSAEMFILE,
// WSAENOBUFS, WSAEPROTONOSUPPORT, WSAEPROTOTYPE, WSAESOCKTNOSUPPORT,
// WSAEADDRINUSE, WSAEINVAL, WSAEISCONN, WSAENOTSOCK, WSAEOPNOTSUPP

#define MAXBINDCOUNT 4096
int CreateListeningSocket(int &PortNum, HWND MsgWindow, 
			  WORD SocketMsg, SOCKET &ServerSocket) {
  int err = 0;
  SOCKADDR_IN ServerSockAddr;
  int bind_port, bind_count;
  
  //Create the Server Socket
  ServerSocket = d_socket(AF_INET, SOCK_STREAM, 0);
  if (ServerSocket == INVALID_SOCKET) return d_WSAGetLastError();
  
  bind_port = PortNum;
  for (bind_count=0; bind_count < MAXBINDCOUNT; ++bind_count) {
    // Set up the Server Socket Address
    memset(&ServerSockAddr, 0, sizeof(ServerSockAddr)); //Needed?
    ServerSockAddr.sin_port = d_htons( (WORD) bind_port);
    ServerSockAddr.sin_family = AF_INET;
    if (gsdl_external_access) {
      ServerSockAddr.sin_addr.s_addr = d_htonl(INADDR_ANY);
    }
    else {
      ServerSockAddr.sin_addr.s_addr = d_htonl(INADDR_LOOPBACK);
    }


    // Try to bind the socket with the address
    if (d_bind(ServerSocket, (LPSOCKADDR) &ServerSockAddr,
	       sizeof(ServerSockAddr)) != SOCKET_ERROR) {
      PortNum = bind_port;
      break;
    }
    
    // make sure it failed to bind because it was
    // already bound
    err = d_WSAGetLastError ();
    if (err != WSAEADDRINUSE) return err;
    
    // Or choose another port number to try
    if (bind_port == 80) bind_port = IPPORT_RESERVED+1;
    else if (bind_count == 0) bind_port = 8282;
    else ++bind_port;
  }
  
  // return an error in we couldn't find a valid port
  if (bind_count == MAXBINDCOUNT) return WSAEADDRINUSE;
  
  // Start listening for connections
  if (d_listen(ServerSocket, SOMAXCONN) == SOCKET_ERROR)
    return d_WSAGetLastError ();	
  
  // Set up event for new connections
  if (d_WSAAsyncSelect(ServerSocket, MsgWindow, SocketMsg, FD_ACCEPT) == SOCKET_ERROR)
    return d_WSAGetLastError ();
  
  return 0;
}


int AnswerListeningSocket(SOCKET ServerSocket, SOCKET &ClientSocket, 
			  SOCKADDR_IN &ClientSockAddr, int AddrLen) {
  if (d_WSAIsBlocking()) {
    log_message("rejected connect due blocking\n");
    return -1;
  }
  
  ClientSocket = d_accept(ServerSocket, (LPSOCKADDR) &ClientSockAddr, &AddrLen);
  if (ClientSocket == INVALID_SOCKET) {
    log_message("accept failed - connection lost\n");
    return -1;
  }
  
  log_message("accept success - connection made\n");
  return 0;
}

void DestroyListeningSocket(SOCKET &ServerSocket, HWND MsgWindow) {
  //Remove any message notification
  d_WSAAsyncSelect(ServerSocket, MsgWindow, 0, 0);
  
  //Close the socket
  CloseSocket(ServerSocket);
}

void CloseSocket(SOCKET &TargetSocket) {
  if (TargetSocket != INVALID_SOCKET) {
    //Since we're closing the socket, there's not much I can do about errors
    //now so I'm not gonna bother checking...
    
    //Shutdown both ends, assume we have all data...
    d_shutdown(TargetSocket, 2);
    
    d_closesocket(TargetSocket);
    
    //Make sure we can't use the old handle again...
    TargetSocket = INVALID_SOCKET;
  }
}

int GetData(SOCKET ClientSocket, BYTE *IOBuffer, int IOBufferSize,
	    int ThreadNum) {
  
  int NumRecv;
  int Error;
  struct timeval Timeout;
  fd_set SocketSet;
  
  //Set up a socket set structure with just ClientSocket for  select(..)
  FD_ZERO(&SocketSet);
  FD_SET(ClientSocket, &SocketSet);
  
  //set timeout
  Timeout.tv_sec = NETIO_CONN_TIMEOUT;
  Timeout.tv_usec = 0;
  
  do {
    NumRecv = d_recv(ClientSocket, (char *) IOBuffer, IOBufferSize, 0);
    if (NumRecv == 0) {
      //Lost connect
      return -1;
      
    } else if (NumRecv == SOCKET_ERROR) {
      Error = d_WSAGetLastError();
      if (Error == WSAEWOULDBLOCK) {
	NumRecv = 0;
	//Wait for socket to be readable
	if (d_select(0, &SocketSet, NULL, NULL, &Timeout) != 1) {
	  //Timeout
	  return -1;
	}
	
      } else {
	//Assume connection terminated
	return -1;
      }
    }
  } while(NumRecv == 0);
  return NumRecv;
}

int GetLine(text_t &OutStr, SOCKET ClientSocket, BYTE *IOBuffer, int IOBufferSize,
	    int &BufferIndex, int &DataInBuffer, int ThreadNum) {

  OutStr.clear();
  char CurChar;
  
  do {
    if (BufferIndex == DataInBuffer) { //Need more data
      DataInBuffer = GetData(ClientSocket, IOBuffer, IOBufferSize, ThreadNum);
      if (DataInBuffer == -1) {
	//Lost connect
	return -1;
      }
      BufferIndex = 0;
    }
    CurChar = IOBuffer[BufferIndex];
    ++BufferIndex;
    if ((CurChar != 10) && (CurChar != 13))  {
      OutStr.push_back(CurChar);
    }
  } while (CurChar != 10);
  
  return 0;
}

int SendData(SOCKET ClientSocket, BYTE *SendBuffer, int NumToSend,
	     int ThreadNum) {
  
  int NumSent = 0;
  int Error;
  struct timeval Timeout;
  fd_set SocketSet;
  
  char NumSentStr[50];
  itoa(NumToSend, NumSentStr, 10);
  
  //Set up a socket set structure with just ClientSocket for  select(..)
  FD_ZERO(&SocketSet);
  FD_SET(ClientSocket, &SocketSet);
  //set timeout
  Timeout.tv_sec = NETIO_CONN_TIMEOUT;
  Timeout.tv_usec = 0;
  
  while (NumToSend > 0) {
    NumSent = d_send(ClientSocket, (char *) SendBuffer + NumSent, NumToSend, 0);
    if (NumSent == 0) {
      //Lost connect
      return -1;
    }
    else if (NumSent == SOCKET_ERROR) {
      Error = d_WSAGetLastError();
      if (Error == WSAEWOULDBLOCK) {
	NumSent = 0;
	if (d_select(0, NULL, &SocketSet, NULL, &Timeout) != 1) {
	  //Timeout
	  return -1;
	}
      }
      else {
	//Lost Connection
	return -1;
      }
    }
    NumToSend -= NumSent;
  }
  return 0;
}


/******************************************************************************/
//Private Functions
/******************************************************************************/
/*-----------------------------------------------------------
 * Function: GetHostID()
 *
 * Description:
 *  Get the Local IP address using the following algorithm:
 *    - get local hostname with gethostname()
 *    - attempt to resolve local hostname with gethostbyname()
 *    if that fails:
 *    - get a UDP socket
 *    - connect UDP socket to arbitrary address and port
 *    - use getsockname() to get local address
 *
 * Notes: Copyright by Bob Quinn, 1995, taken from his Winsock library
 *        was removed from its original module unmodified
 */
DWORD GetHostID () {
  char szLclHost [MAXHOSTNAME];
  LPHOSTENT lpstHostent;
  SOCKADDR_IN stLclAddr;
  SOCKADDR_IN stRmtAddr;
  int nAddrSize = sizeof(SOCKADDR);
  SOCKET hSock;
  int nRet;
  
  /* Init local address (to zero) */
  stLclAddr.sin_addr.s_addr = INADDR_ANY;
  
  /* Get the local hostname */
  nRet = d_gethostname(szLclHost, MAXHOSTNAME);
  if (nRet != SOCKET_ERROR) {
    /* Resolve hostname for local address */
    lpstHostent = d_gethostbyname((LPSTR)szLclHost);
    if (lpstHostent)
      stLclAddr.sin_addr.s_addr = *((u_long FAR*) (lpstHostent->h_addr));
  }
  
  /* If still not resolved, then try second strategy */
  if (stLclAddr.sin_addr.s_addr == INADDR_ANY) {
    /* Get a UDP socket */
    hSock = d_socket(AF_INET, SOCK_DGRAM, 0);
    if (hSock != INVALID_SOCKET)  {
      /* Connect to arbitrary port and address (NOT loopback) */
      stRmtAddr.sin_family = AF_INET;
      stRmtAddr.sin_port   = d_htons(IPPORT_ECHO);
      stRmtAddr.sin_addr.s_addr = d_inet_addr("128.127.50.1");
      nRet = d_connect(hSock,
		       (LPSOCKADDR)&stRmtAddr,
		       sizeof(SOCKADDR));
      if (nRet != SOCKET_ERROR) {
	/* Get local address */
	d_getsockname(hSock,
		      (LPSOCKADDR)&stLclAddr,
		      (int FAR*)&nAddrSize);
      }
      d_closesocket(hSock);   /* we're done with the socket */
    }
  }
  return (stLclAddr.sin_addr.s_addr);
}
