/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
 *
 * The contents of this file are subject to the Netscape Public License
 * Version 1.0 (the "License"); you may not use this file except in
 * compliance with the License.  You may obtain a copy of the License at
 * http://www.mozilla.org/NPL/
 *
 * Software distributed under the License is distributed on an "AS IS"
 * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied.  See
 * the License for the specific language governing rights and limitations
 * under the License.
 *
 * The Original Code is Mozilla Communicator client code.
 *
 * The Initial Developer of the Original Code is Netscape Communications
 * Corporation.  Portions created by Netscape are Copyright (C) 1998
 * Netscape Communications Corporation.  All Rights Reserved.
 */
#ifdef XP_PC
#include <windows.h>
#ifdef WIN32_LEAN_AND_MEAN
#include <winsock2.h>
#endif
#include "net.h"
#include "nsISupports.h"
#include "merrors.h"
#include "prnetdb.h"
#include "plstr.h"
#include "prmem.h"
#include "prlog.h"
#include "nslocks.h"

#ifdef DEBUG
static PRLogModuleInfo* gDNSLogModuleInfo;

#define DNS_TRACE_LOOKUPS 0x1
#define DNS_TRACE_SLOW    0x2   // XXX not yet implemented

#define DNS_LOG_TEST(_lm,_bit) (PRIntn((_lm)->level) & (_bit))

#define DNS_TRACE(_bit,_args)                   \
  PR_BEGIN_MACRO                                \
    if (DNS_LOG_TEST(gDNSLogModuleInfo,_bit)) { \
      PR_LogPrint _args;                        \
    }                                           \
  PR_END_MACRO

#else
#define DNS_TRACE(_bit,_args)
#endif

struct SocketWaiter {
  void Init(PRFileDesc* fd) {
    mSocket = fd;
    mNext = NULL;
  }

  PRFileDesc* mSocket;
  SocketWaiter* mNext;
};

struct DNSCacheEntry {
  nsresult Init(void* aContext, const char* aHost, PRFileDesc* fd);

  void Destroy();

  static DNSCacheEntry** Lookup(const char* aHost);

  void* mContext;
  char* mHost;
  SocketWaiter* mSockets;
  PRHostEnt* mHostEnt;
  int mError;
  PRBool mFinished;
  HANDLE mHandle;
  DNSCacheEntry* mNext;
};

extern "C" {
static UINT gMSGFoundDNS;
UINT gMSGAsyncSelect;
HWND gDNSWindow;
};

static DNSCacheEntry* gDNSCache;

void
DNSCacheEntry::Destroy()
{
  PR_Free(mHost);
  PR_Free(mHostEnt);
  SocketWaiter* sw = mSockets;
  while (NULL != sw) {
    SocketWaiter* next = sw->mNext;
    PR_Free(sw);
    sw = next;
  }
}

nsresult
DNSCacheEntry::Init(void* aContext, const char* aHost, PRFileDesc* fd)
{
  mContext = aContext;
  mError = 0;
  mFinished = PR_FALSE;
  mHandle = NULL;
  mNext = NULL;

  mHost = PL_strdup(aHost);
  mHostEnt = (PRHostEnt*) PR_Malloc(sizeof(char) * PR_NETDB_BUF_SIZE);
  mSockets = (SocketWaiter*) PR_Malloc(sizeof(SocketWaiter));
  mSockets->Init(fd);

  if ((NULL == mHost) || (NULL == mHostEnt) || (NULL == mSockets)) {
    return NS_ERROR_FAILURE;
  }
  mHostEnt->h_name = NULL;
  mHostEnt->h_aliases = NULL;
  mHostEnt->h_addr_list = NULL;
  return NS_OK;
}

DNSCacheEntry**
DNSCacheEntry::Lookup(const char* aHost)
{
  DNSCacheEntry** pentry = &gDNSCache;
  DNSCacheEntry* entry = *pentry;
  while (NULL != entry) {
    if (PL_strcasecmp(aHost, entry->mHost) == 0) {
      return pentry;
    }
    pentry = &entry->mNext;
    entry = entry->mNext;
  }
  return pentry;
}

#if defined(NO_NETWORK_POLLING)
extern "C" PRFileDesc* net_GetFileDescFromOSFD(PRInt32);
extern "C" void net_AsyncSelect(PRFileDesc* fd, long netMask);
#endif /* NO_NETWORK_POLLING */

static LRESULT CALLBACK 
#if defined(WIN16)
__loadds
#endif
EventProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
  if (gMSGFoundDNS == uMsg) {
    // Get error code now
    int error = WSAGETASYNCERROR(lParam);

    DNS_TRACE(DNS_TRACE_LOOKUPS,
              ("DNS lookup: handle=%p error=%d", (HANDLE)wParam, error));

    // Search cache for matching handle
    LRESULT rv = 1;
    DNSCacheEntry** pentry = &gDNSCache;
    DNSCacheEntry* entry = gDNSCache;
    while (NULL != entry) {
      if (!entry->mFinished && ((HANDLE)wParam == entry->mHandle)) {
        DNS_TRACE(DNS_TRACE_LOOKUPS,
                  ("DNS lookup: finished '%s'", entry->mHost));

        // Found a match
        entry->mFinished = PR_TRUE;
        entry->mError = error;
        if (0 != error) {
          rv = 0;
        }
        SocketWaiter** psw = &entry->mSockets;
        SocketWaiter* sw = entry->mSockets;
        while (NULL != sw) {
          // Poke netlib
          int iWantMore = NET_ProcessNet(sw->mSocket, NET_SOCKET_FD);
          *psw = sw->mNext;
          PR_Free(sw);
          sw = *psw;
        }
#if XXX_DONT_CACHE_DNS_LOOKUPS
        if (NULL == entry->mSockets) {
          // Now that entry is complete, remove it
          *pentry = entry->mNext;
          entry->Destroy();
          PR_Free(entry);
        }
#endif
        return rv;
      }
      pentry = &entry->mNext;
      entry = entry->mNext;
    }
    return rv;
  } 

#if defined(NO_NETWORK_POLLING)
  if (gMSGAsyncSelect == uMsg) {
    PRFileDesc* fd;

    /* 
     * Enter the Libnet lock for the call to net_GetFileDescFromOSFD(...)
     */
    LIBNET_LOCK();

    fd = net_GetFileDescFromOSFD(wParam);
    if (NULL != fd) {
      /*
       * Queue up another asynchronous notification before calling 
       * NET_ProcessNet(...).  This way, we do not need to konw if the socket
       * is still active after the call (or if it was closed...).  If the
       * connection is closed, the asynchronous request will be canceled...
       */
      net_AsyncSelect(fd, 
                      FD_READ | FD_WRITE | FD_ACCEPT | FD_CONNECT | FD_CLOSE | FD_OOB);

      /* Process the connection which has data... */
			NET_ProcessNet(fd, NET_SOCKET_FD);
    }

    LIBNET_UNLOCK();
  }
#endif /* NO_NETWORK_POLLING */

  return DefWindowProc(hwnd, uMsg, wParam, lParam);
}

void net_InitAsyncDNS()
{
  static char *windowClass = "NETLIB:DNSWindow";

#ifdef DEBUG
  gDNSLogModuleInfo = PR_NewLogModule("netlibdns");
#endif

  // Allocate a message id for the event handler
  gMSGFoundDNS    = RegisterWindowMessage("Mozilla:DNSMessage");
  gMSGAsyncSelect = RegisterWindowMessage("Mozilla:AsyncSelectMessage");

  // Register the class for the event receiver window
  WNDCLASS wc;
  wc.style         = 0;
  wc.lpfnWndProc   = EventProc;
  wc.cbClsExtra    = 0;
  wc.cbWndExtra    = 0;
  wc.hInstance     = NULL;
  wc.hIcon         = NULL;
  wc.hCursor       = NULL;
  wc.hbrBackground = (HBRUSH) NULL;
  wc.lpszMenuName  = (LPCSTR) NULL;
  wc.lpszClassName = windowClass;
  RegisterClass(&wc);
        
  // Create the event receiver window
  gDNSWindow = CreateWindow(windowClass,
                            "Mozilla:DNSEventHandler",
                            0, 0, 0, 10, 10,
                            NULL, NULL, NULL,
                            NULL);

  DNS_TRACE(DNS_TRACE_LOOKUPS,
            ("DNS lookup: hidden window=%p msg=%d",
             gDNSWindow, gMSGFoundDNS));
}

extern "C" int
NET_AsyncDNSLookup(void* aContext,
                   const char* aHostPort,
                   PRHostEnt** aHoststructPtrPtr,
                   PRFileDesc* aSocket);

int
NET_AsyncDNSLookup(void* aContext,
                   const char* aHostPort,
                   PRHostEnt** aHoststructPtrPtr,
                   PRFileDesc* aSocket)
{
  /* DNS initialization failed... */
  if (NULL == gDNSWindow) {
    PR_ASSERT(0);
    return -1;
  }

  *aHoststructPtrPtr = NULL;

  // Look in cache first
  DNSCacheEntry** pentry = DNSCacheEntry::Lookup(aHostPort);
  DNSCacheEntry* entry = *pentry;
  if (NULL != entry) {
    // If lookup returned an error then indicate that to the caller...
    if ((0 != entry->mError) && entry->mFinished) {
      WSASetLastError(entry->mError);
      if (NULL == entry->mSockets) {
        // XXX can't get here, right?
        // Delete entry when last socket is done with it...
        *pentry = entry->mNext;
        entry->Destroy();
        PR_Free(entry);
      }
      DNS_TRACE(DNS_TRACE_LOOKUPS,
                ("DNS lookup: '%s' error=%d\n", entry->mHost, entry->mError));
      return -1;
    }

    // If lookup finished then return the answer
    if ((NULL != entry->mHostEnt->h_name) && entry->mFinished) {
      DNS_TRACE(DNS_TRACE_LOOKUPS,
                ("DNS lookup: '%s' finished\n", entry->mHost));
      *aHoststructPtrPtr = entry->mHostEnt;
      return 0;
    }

    // See if we are already waiting for the answer
    SocketWaiter* sw = entry->mSockets;
    while (NULL != sw) {
      if (sw->mSocket == aSocket) {
        return MK_WAITING_FOR_LOOKUP;
      }
      sw = sw->mNext;
    }

    // Add this socket to the wait list
    sw = (SocketWaiter*) PR_Malloc(sizeof(SocketWaiter));
    if (NULL == sw) {
      return -1;
    }
    sw->Init(aSocket);
    sw->mSocket = aSocket;
    sw->mNext = entry->mSockets;
    entry->mSockets = sw;
    DNS_TRACE(DNS_TRACE_LOOKUPS,
              ("DNS lookup: still waiting for host='%s'\n", entry->mHost));
    return MK_WAITING_FOR_LOOKUP;
  }

  // Create a new cache entry for the host name
  entry = (DNSCacheEntry*) PR_Malloc(sizeof(DNSCacheEntry));
  if (NULL == entry) {
    return -1;
  }
  if (NS_OK != entry->Init(aContext, aHostPort, aSocket)) {
    entry->Destroy();
    PR_Free(entry);
    return -1;
  }

  // Start async lookup on that host name
  HANDLE h = WSAAsyncGetHostByName(gDNSWindow,
                                   gMSGFoundDNS,
                                   entry->mHost,
                                   (char *)entry->mHostEnt,
                                   PR_NETDB_BUF_SIZE);
  DNS_TRACE(DNS_TRACE_LOOKUPS,
            ("DNS lookup: begin waiting for host='%s' handle=%p\n",
             entry->mHost, h));
  if (NULL == h) {
    entry->Destroy();
    PR_Free(entry);
    return -1;
  }
  entry->mHandle = h;
  entry->mNext = *pentry;
  *pentry = entry;
  return MK_WAITING_FOR_LOOKUP;
}

#endif
