// BinSocketInputStream.cpp
// 
// This file was developed from Apache code for the
// ImageGuide system software.
//
//	14 July 2003 - gtw - Moved to "Socket" directory from
//							         "WinSock" directory to enable use
//                       of this code in the Unix build.

#ifdef _WINDOWS
#define _WINSOCKAPI_
#define INCL_WINSOCK_API_TYPEDEFS 1
#include <winsock2.h>
#include <windows.h>
#include <tchar.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#include <xercesc/util/PlatformUtils.hpp>
#include <xercesc/util/XMLNetAccessor.hpp>
#include <xercesc/util/NetAccessors/Socket/BinSocketInputStream.hpp>
#include <xercesc/util/XMLString.hpp>
#include <xercesc/util/XMLExceptMsgs.hpp>
#include <xercesc/util/Janitor.hpp>
#include <xercesc/util/XMLUniDefs.hpp>
#define CLOSESOCKET closesocket
#define IOCTLSOCKET ioctlsocket
#else // Not Windows.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#if !defined(XML_BEOS)
  #include <netinet/in.h>
  #include <arpa/inet.h>
#endif
#include <netdb.h>
#include <errno.h>

#include <xercesc/util/XMLNetAccessor.hpp>
#include <xercesc/util/NetAccessors/Socket/BinSocketInputStream.hpp>
#include <xercesc/util/XMLString.hpp>
#include <xercesc/util/XMLExceptMsgs.hpp>
#include <xercesc/util/Janitor.hpp>
#include <xercesc/util/XMLUniDefs.hpp>
#include <xercesc/util/TransService.hpp>
#include <xercesc/util/TranscodingException.hpp>
#include <xercesc/util/PlatformUtils.hpp>
#define CLOSESOCKET close
#define IOCTLSOCKET ioctl
#define SD_BOTH SHUT_RDWR 
#endif // _WINDOWS


XERCES_CPP_NAMESPACE_BEGIN


bool BinSocketInputStream::fInitialized = false;
XMLMutex* BinSocketInputStream::fInitMutex = 0;

void BinSocketInputStream::Initialize() {
#ifdef _WINDOWS
    //
    // Initialize the WinSock library here.
    //
    WORD        wVersionRequested = MAKEWORD(2,2);
    WSADATA     wsaData;
    WSAStartup(wVersionRequested, &wsaData);
#endif // _WINDOWS
    fInitialized = true;
}

void BinSocketInputStream::Cleanup() {
	if(fInitialized)
	{

      fInitialized = false;
      delete fInitMutex;
      fInitMutex = 0;

	}
}




void
BinSocketInputStream::WinSocketConstructor(const XMLURL& urlSource)
{
#ifdef _WINDOWS
	if(!fInitialized)
    {
        if (!fInitMutex)
        {
            XMLMutex* tmpMutex = new XMLMutex;
            if (XMLPlatformUtils::compareAndSwap((void**)&fInitMutex, tmpMutex, 0))
            {
                // Someone beat us to it, so let's clean up ours
                delete tmpMutex;
            }
         }
         XMLMutexLock lock(fInitMutex);
         if (!fInitialized)
         {
             Initialize();
         }
    }

    //
    // Pull all of the parts of the URL out of th urlSource object, and transcode them
    //   and transcode them back to ASCII.
    //
	  XMLURL::Protocols   theProtocol = urlSource.getProtocol() ;
    const XMLCh*        hostName = urlSource.getHost();
    char*               hostNameAsCharStar = XMLString::transcode(hostName);
    ArrayJanitor<char>  janBuf1(hostNameAsCharStar);

    const XMLCh*        path = urlSource.getPath();
    char*               pathAsCharStar = XMLString::transcode(path);
    ArrayJanitor<char>  janBuf2(pathAsCharStar);

    const XMLCh*        fragment = urlSource.getFragment();
    char*               fragmentAsCharStar = 0;
    if (fragment)
        fragmentAsCharStar = XMLString::transcode(fragment);
    ArrayJanitor<char>  janBuf3(fragmentAsCharStar);

    const XMLCh*        query = urlSource.getQuery();
    char*               queryAsCharStar = 0;
    if (query)
        queryAsCharStar = XMLString::transcode(query);
    ArrayJanitor<char>  janBuf4(queryAsCharStar);		

    unsigned short      portNumber = (unsigned short) urlSource.getPortNum();

    //
    // Set up a socket.
    //
    struct hostent*     hostEntPtr = 0;
    struct sockaddr_in  sa;
    if ((hostEntPtr = gethostbyname(hostNameAsCharStar)) == NULL)
    {
        unsigned long  numAddress = inet_addr(hostNameAsCharStar);
        if (numAddress == INADDR_NONE)
        {
            ThrowXML1(NetAccessorException,
                     XMLExcepts::NetAcc_TargetResolution, hostName);
        }
        if ((hostEntPtr =
                gethostbyaddr((const char *) &numAddress,
                              sizeof(unsigned long), AF_INET)) == NULL)
        {
            ThrowXML1(NetAccessorException,
                     XMLExcepts::NetAcc_TargetResolution, hostName);
        }
    }

    memcpy((void *) &sa.sin_addr,
           (const void *) hostEntPtr->h_addr, hostEntPtr->h_length);
    sa.sin_family = hostEntPtr->h_addrtype;
    sa.sin_port = htons(portNumber);

    SOCKET s ;
    s = socket(AF_INET, SOCK_STREAM, 0);

    if (s == INVALID_SOCKET)
    {
        ThrowXML1(NetAccessorException,
                 XMLExcepts::NetAcc_CreateSocket, urlSource.getURLText());
    }
		// Set the socket to blocking.

	    int status = 1 ;
	    int trycount = 0 ;
	    while ( ( status != 0 ) && ( trycount < 3 ) )  {
        status = bind(s, (struct sockaddr *) &sa, sizeof(sa) );
	      if ( status != 0 ) {
		      printf ("bind returned status: %d  error:%d\n", status, WSAGetLastError());
          CLOSESOCKET(s);
	        trycount++ ;
				}
      }
	    // Set the socket state to listening.
      status = listen( s, 100 ) ; // at most 1 client.
	    if ( status != 0 ) {
		    printf ("listen returned status: %d  error:%d\n", status, WSAGetLastError());
      }

      // if server, set to blocking.  Windows is different in debug/release build modes.
			if(0)
      if ( theProtocol == XMLURL::SSOCKET ) {
		    fd_set block_set ;
				memset((void*)&block_set,0,sizeof(block_set));
        FD_SET(s,&block_set);
//      status = select(1, &block_set, &block_set, &block_set, NULL ) ;
        status = 0 ;
				if ( status != 0 ) {
		      printf ("select returned status: %d  error:%d\n", status, WSAGetLastError());
        }
			  u_long parm = 0 ;//non-zero should cause blocking.
        status = IOCTLSOCKET(s, FIONBIO, &parm);
			  if ( status != 0 ) {
		      printf ("ioctlsocket returned status: %d  error:%d\n", status, WSAGetLastError());
        }
      }

      // wait for a client to connect.
	    int ssize ;
      fSocketHandle = accept(s, (struct sockaddr* )&sa, &ssize );	
      printf("accept returned 0x%08x\n", (int)fSocketHandle);
			fd_set block_set ;
			memset((void*)&block_set,0,sizeof(block_set));
      FD_SET(s,&block_set);
      if ( fSocketHandle == INVALID_SOCKET ) {
		      printf ("accept return INVALID_SOCKET select returned error:%d\n", WSAGetLastError());
			}
			else {
				// Set blocking operation.
				status = 0 ;
#ifndef _DEBUG
      //  status = select(1, &block_set, &block_set, &block_set, NULL ) ;
#endif
				if ( status != 0 ) {
		      printf ("select returned status: %d  error:%d\n", status, WSAGetLastError());
				}
				u_long parm = 0 ;//non-zero should cause blocking.
        status = IOCTLSOCKET(fSocketHandle, FIONBIO, &parm);
				if ( status != 0 ) {
		      printf ("ioctlsocket returned status: %d  error:%d\n", status, WSAGetLastError());
				}

			}
    fClientSocketHandle = (unsigned int) s;
#else
		fClientSocketHandle = -1;
#endif // _WINDOWS
}

void
BinSocketInputStream::UnxSocketConstructor(const XMLURL& urlSource)
{
#ifndef _WINDOWS
    if(!fInitialized)
    {
        if (!fInitMutex)
        {
            XMLMutex* tmpMutex = new XMLMutex;
            if (XMLPlatformUtils::compareAndSwap((void**)&fInitMutex, tmpMutex, 0))
            {
                // Someone beat us to it, so let's clean up ours
                delete tmpMutex;
            }
         }
         XMLMutexLock lock(fInitMutex);
         if (!fInitialized)
         {
             Initialize();
         }
    }

    //
    // Pull all of the parts of the URL out of th urlSource object, and transcode them
    //   and transcode them back to ASCII.
    //
	  XMLURL::Protocols   theProtocol = urlSource.getProtocol() ;
    const XMLCh*        hostName = urlSource.getHost();
    char*               hostNameAsCharStar = XMLString::transcode(hostName);
    ArrayJanitor<char>  janBuf1(hostNameAsCharStar);

    const XMLCh*        path = urlSource.getPath();
    char*               pathAsCharStar = XMLString::transcode(path);
    ArrayJanitor<char>  janBuf2(pathAsCharStar);

    const XMLCh*        fragment = urlSource.getFragment();
    char*               fragmentAsCharStar = 0;
    if (fragment)
        fragmentAsCharStar = XMLString::transcode(fragment);
    ArrayJanitor<char>  janBuf3(fragmentAsCharStar);

    const XMLCh*        query = urlSource.getQuery();
    char*               queryAsCharStar = 0;
    if (query)
        queryAsCharStar = XMLString::transcode(query);
    ArrayJanitor<char>  janBuf4(queryAsCharStar);		

    unsigned short      portNumber = (unsigned short) urlSource.getPortNum();

    //
    // Set up a socket.
    //
    struct hostent*     hostEntPtr = 0;
    struct sockaddr_in  sa;


    if ((hostEntPtr = gethostbyname(hostNameAsCharStar)) == NULL)
    {
        unsigned long  numAddress = inet_addr(hostNameAsCharStar);
        if (numAddress == INADDR_NONE)
        {
            // Call WSAGetLastError() to get the error number.
            ThrowXML1(NetAccessorException,
                     XMLExcepts::NetAcc_TargetResolution, hostName);
        }
        if ((hostEntPtr =
                gethostbyaddr((const char *) &numAddress,
                              sizeof(unsigned long), AF_INET)) == NULL)
        {
            // Call WSAGetLastError() to get the error number.
            ThrowXML1(NetAccessorException,
                     XMLExcepts::NetAcc_TargetResolution, hostName);
        }
    }

    memcpy((void *) &sa.sin_addr,
           (const void *) hostEntPtr->h_addr, hostEntPtr->h_length);
    sa.sin_family = hostEntPtr->h_addrtype;
    sa.sin_port = htons(portNumber);
    int s ;
    s = socket(AF_INET, SOCK_STREAM, 0);
    if ( s < 0 ) 
    {
        ThrowXML1(NetAccessorException,
                 XMLExcepts::NetAcc_CreateSocket, urlSource.getURLText());
    }
		// Set the socket to blocking.

	    int status = 1 ;
	    int trycount = 0 ;
	    while ( ( status != 0 ) && ( trycount < 3 ) )  {
        status = bind(s, (struct sockaddr *) &sa, sizeof(sa) );
	      if ( status != 0 ) {
		      printf ("bind returned status: %d \n", status);
          CLOSESOCKET(s);
	        trycount++ ;
				}
      }
	    // Set the socket state to listening.
      status = listen( s, 100 ) ; // at most 1 client.
	    if ( status != 0 ) {
		    printf ("listen returned status: %d\n", status );
      }

      // if server, set to blocking.
      if ( theProtocol == XMLURL::SSOCKET ) {
		    fd_set block_set ;
        FD_SET(s,&block_set);
        status = select(1, &block_set, &block_set, &block_set, NULL ) ;
 		    if ( status != 0 ) {
		      printf ("select returned status: %d\n", status);
        }
			  u_long parm = 0 ;//non-zero should cause blocking.
        status = IOCTLSOCKET(s, FIONBIO, &parm);
			  if ( status != 0 ) {
		      printf ("ioctlsocket returned status: %d\n", status);
        }
      }



      // wait for a client to connect.
	    int ssize ;
      fSocketHandle = accept(s, (struct sockaddr* )&sa, &ssize );	
      printf("accept returned 0x%08x\n", (int)fSocketHandle);
			fd_set block_set ;
      FD_SET(s,&block_set);
      if ( fSocketHandle < 0 ) {
		      printf ("select returned INVALID_SOCKET\n");
			}
			else {
				// Set blocking operation.
        status = select(1, &block_set, &block_set, &block_set, NULL ) ;
				if ( status != 0 ) {
		      printf ("select returned status: %d\n", status);
				}
				u_long parm = 0 ;//non-zero should cause blocking.
        status = IOCTLSOCKET(fSocketHandle, FIONBIO, &parm);
				if ( status != 0 ) {
		      printf ("ioctlsocket returned status: %d\n", status);
				}	
			}

    fClientSocketHandle = (unsigned int) s;
#else
    fClientSocketHandle = -1 ;
#endif // ifndef _WINDOWS
}

BinSocketInputStream::BinSocketInputStream(const XMLURL& urlSource)

      : fSocketHandle(0)
      , fClientSocketHandle(0)
      , fBytesProcessed(0)
{

#ifdef _WINDOWS
	WinSocketConstructor(urlSource);
#else
	UnxSocketConstructor(urlSource) ;
#endif
	return ;

}



BinSocketInputStream::~BinSocketInputStream()
{
    shutdown(fSocketHandle, SD_BOTH);
    shutdown(fClientSocketHandle, SD_BOTH);
    CLOSESOCKET(fClientSocketHandle);
    CLOSESOCKET(fSocketHandle);
}


//
//  readBytes
//
unsigned int BinSocketInputStream::readBytes(XMLByte* const    toFill
                                    , const unsigned int    maxToRead)
{
    unsigned int len = fBufferEnd - fBufferPos;
    if (len > 0)
    {
        // If there's any data left over in the buffer into which we first
        //   read from the server (to get the http header), return that.
        if (len > maxToRead)
            len = maxToRead;
        memcpy(toFill, fBufferPos, len);
        fBufferPos += len;
    }
    else
    {
        // There was no data in the local buffer.
        // Read some from the socket, straight into our caller's buffer.
        //
#ifdef _WINDOWS
        len = recv((SOCKET) fSocketHandle, (char *) toFill, maxToRead, 0);
        if (len == SOCKET_ERROR)
#else
        len = recv( fSocketHandle, (char *) toFill, maxToRead, 0);
        if (len < 0 )

#endif
        {
            // Call WSAGetLastError() to get the error number.
#ifdef _WINDOWS
            printf("recv failed to read from socket(%d).status:%d\n", maxToRead, WSAGetLastError());
#else
            printf("recv failed to read from socket(%d)\n", maxToRead );
#endif
            ThrowXML(NetAccessorException, XMLExcepts::NetAcc_ReadSocket);
        }
    }

    fBytesProcessed += len;
    return len;
}


XERCES_CPP_NAMESPACE_END

