/* -*- 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 "NPL"); you may not use this file except in
 * compliance with the NPL.  You may obtain a copy of the NPL at
 * http://www.mozilla.org/NPL/
 *
 * Software distributed under the NPL is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the NPL
 * for the specific language governing rights and limitations under the
 * NPL.
 *
 * The Initial Developer of this code under the NPL is Netscape
 * Communications Corporation.  Portions created by Netscape are
 * Copyright (C) 1998 Netscape Communications Corporation.  All Rights
 * Reserved.
 */


#include "nsIBlockingNotification.h"
#include "nsIURL.h"
#include "nsIWebShell.h"
#include "nsNetSupportDialog.h"

#include "nsIEventQueueService.h"
#include "nsIServiceManager.h"
#include "nsXPComFactory.h"

// Class IDs...
static NS_DEFINE_CID(kEventQueueServiceCID,  NS_EVENTQUEUESERVICE_CID);
static NS_DEFINE_CID(kNetSupportDialogCID, NS_NETSUPPORTDIALOG_CID);

// Interface IDs...
static NS_DEFINE_IID(kIEventQueueServiceIID, NS_IEVENTQUEUESERVICE_IID);
static NS_DEFINE_IID(kIBlockingNotificationObserverIID, 
                                             NS_IBLOCKINGNOTIFICATION_OBSERVER_IID);
static NS_DEFINE_IID(kIBlockingNotificationIID, NS_IBLOCKINGNOTIFICATION_IID);


// Forward declarations...
class nsDefaultProtocolHelper;

/*-------------------- Notification Event Class ----------------------------*/

struct NotificationEvent : public PLEvent 
{
  NotificationEvent(nsDefaultProtocolHelper *aSelf,
                    nsIBlockingNotification *aCaller, 
                    nsIURI *aUrl, 
                    PRInt32 aCode, 
                    nsISupports *aExtraInfo);
  ~NotificationEvent();
  PRStatus Fire(nsIEventQueue* aEventQ);

  static void PR_CALLBACK HandlePLEvent(PLEvent* aEvent);
  static void PR_CALLBACK DestroyPLEvent(PLEvent* aEvent);

  nsDefaultProtocolHelper *mSelf;
  nsIBlockingNotification *mCaller;
  nsIURI *mUrl;
  PRInt32 mCode;
  nsISupports *mExtraInfo;
};



/*----------------------------- nsDefaultProtocolHelper ---------------------*/


class nsDefaultProtocolHelper : public nsIBlockingNotificationObserver
{
public:
  nsDefaultProtocolHelper();

  /* nsISupports interface... */
  NS_DECL_ISUPPORTS

  /* nsIBlockingNotificationObserver interface... */
  NS_IMETHOD Notify(nsIBlockingNotification *aCaller,
                    nsIURI *aUrl,
                    PRThread *aThread,
                    PRInt32 aCode,
                    void *aExtraInfo);

  NS_IMETHOD CancelNotify(nsIURI *aUrl);

  /* class methods... */
  nsresult HandleNotification(nsIBlockingNotification *aCaller,
                              nsIURI *aUrl,
                              PRInt32 aCode,
                              void *aExtraInfo);

protected:
  virtual ~nsDefaultProtocolHelper();

private:
  nsIEventQueueService *mEventQService;
};


nsDefaultProtocolHelper::nsDefaultProtocolHelper()
{
  NS_INIT_REFCNT();

  /* 
   * Cache the EventQueue service... 
   *
   * Ignore failure since there is nothing that can be done...
   * Instead, all of the code that uses mEventQService *must*
   * check that it was initialized...
   */
  (void) nsServiceManager::GetService(kEventQueueServiceCID,
                                      kIEventQueueServiceIID,
                                      (nsISupports **)&mEventQService);
}

nsDefaultProtocolHelper::~nsDefaultProtocolHelper()
{
  if (mEventQService) {
    nsServiceManager::ReleaseService(kEventQueueServiceCID, mEventQService);
    mEventQService = nsnull;
  }
}

/* 
 * Implementations of nsISupports interface methods...
 */
NS_IMPL_ADDREF(nsDefaultProtocolHelper);
NS_IMPL_RELEASE(nsDefaultProtocolHelper);
NS_IMPL_QUERY_INTERFACE(nsDefaultProtocolHelper, kIBlockingNotificationObserverIID);



/*
 * Implementations of nsIBlockingNotificationObserver interface methods... 
 */
NS_IMETHODIMP
nsDefaultProtocolHelper::Notify(nsIBlockingNotification *aCaller,
                                nsIURI *aUrl,
                                PRThread *aThread,
                                PRInt32 aCode,
                                void *aExtraInfo)
{
  nsresult rv;
  NotificationEvent *ev;

  /* 
   * Initialize the return code to indicate that the notification was not
   * processed synchronously...
   */
  rv = NS_NOTIFY_BLOCKED;
    
  /*
   * If no thread switch is necessary, then handle the notification
   * immediately...
   */
#if 0 // The mac netlib is on the same thread as the UI thread but you crash if you block right here
  PRThread* currentThread = PR_GetCurrentThread();
  if ( currentThread == aThread) {
    rv = HandleNotification(aCaller, aUrl, aCode, (void *)aExtraInfo);
  }
  else 
#endif 
  {
  
    /*
     * Post a message to the appropriate thread event queue to
     * handle the notification...
     */
    nsIEventQueue *evQ = nsnull;

    /* locate the event queue for the thread... */
    if (mEventQService) {
      mEventQService->GetThreadEventQueue(aThread, &evQ);
    }

    /* Create and dispatch the notification event... */
    if (evQ) {
      ev = new NotificationEvent(this, aCaller, aUrl, aCode, (nsISupports *) aExtraInfo);
      if (ev) {
        PRStatus status;

        /* dispatch the event into the appropriate event queue... */
        status = ev->Fire(evQ);

        if (PR_SUCCESS != status) {
          /* If the event was not dispatched, then clean up... */
          NotificationEvent::DestroyPLEvent(ev);
          rv = NS_ERROR_FAILURE;
        }
      }
      else {
        /* allocation of the Notification event failed... */ 
        rv = NS_ERROR_OUT_OF_MEMORY;
      }

			NS_IF_RELEASE(evQ);
    } else {
      /* No event queue was found! */
      NS_ASSERTION(0, "No Event Queue is available!");
      rv = NS_ERROR_FAILURE;
    }
  }
  return rv;
}


NS_IMETHODIMP
nsDefaultProtocolHelper::CancelNotify(nsIURI *aUrl)
{
  /* XXX: does not support interrupting a notification yet... */
  return NS_ERROR_FAILURE;
}


nsresult nsDefaultProtocolHelper::HandleNotification(nsIBlockingNotification *aCaller,
                                                     nsIURI *aUrl,
                                                     PRInt32 aCode,
                                                     void *aExtraInfo)
{
  /* XXX this definition must match the one in network/protocol/http/mkhttp.c 
     this will go away as netlib cleanup continues */

  typedef struct _NET_AuthClosure {
    char * msg;
    char * user;
    char * pass;
    void * _private;
  } NET_AuthClosure;

  NET_AuthClosure * auth_closure = (NET_AuthClosure *) aExtraInfo;

  nsAutoString aText(auth_closure->msg);
  nsAutoString aUser;
  nsAutoString aPass(auth_closure->pass);

  // create a dialog
	PRBool bResult = PR_FALSE;
	nsString password, user;
 	PRInt32 result;
  nsresult rv;
  
#ifdef NECKO
  NS_WITH_SERVICE(nsIPrompt, dialog, kNetSupportDialogCID, &rv);
#else
  NS_WITH_SERVICE(nsINetSupportDialogService, dialog, kNetSupportDialogCID, &rv);
#endif
  
 	if (NS_FAILED(rv))
 		return NS_ERROR_FAILURE;
  if ( dialog ) {
#ifdef NECKO
    PRUnichar* usr;
    PRUnichar* pwd;
   	rv = dialog->PromptUsernameAndPassword(aText.GetUnicode(), &usr, &pwd, &result); 
    if (NS_SUCCEEDED(rv)) {
      aUser = usr;
      delete[] usr;
      aPass = pwd;
      delete[] pwd;
    }
#else
   	dialog->PromptUserAndPassword( aText, aUser, aPass, &result ); 
#endif
  }
  if ( result == 1 )
  	bResult =  NS_NOTIFY_SUCCEEDED;
  else
  	bResult = NS_ERROR_FAILURE;

  auth_closure->user = aUser.ToNewCString();
  auth_closure->pass = aPass.ToNewCString();

  aCaller->Resume(aUrl, (void *) auth_closure);

  return bResult;
  // delete aUser;
  // delete aPass;
  // delete aText;
  // dialog->Release();

  return NS_NOTIFY_BLOCKED;
}



/*-------------------- Notification Event Class ----------------------------*/



NotificationEvent::NotificationEvent(nsDefaultProtocolHelper *aSelf,
                                     nsIBlockingNotification *aCaller,
                                     nsIURI *aUrl,
                                     PRInt32 aCode,
                                     nsISupports *aExtraInfo)
{
  mSelf      = aSelf;
  mCaller    = aCaller;
  mUrl       = aUrl;
  mCode      = aCode;
  mExtraInfo = aExtraInfo;

  NS_IF_ADDREF(mSelf);
  NS_IF_ADDREF(mCaller);
  NS_IF_ADDREF(mUrl);
//  NS_IF_ADDREF(mExtraInfo);
}


NotificationEvent::~NotificationEvent()
{
  NS_IF_RELEASE(mSelf);
  NS_IF_RELEASE(mCaller);
  NS_IF_RELEASE(mUrl);
 // NS_IF_RELEASE(mExtraInfo);
}

void PR_CALLBACK NotificationEvent::HandlePLEvent(PLEvent* aEvent)
{
  NotificationEvent *ev = (NotificationEvent*)aEvent;

  (void)ev->mSelf->HandleNotification(ev->mCaller, ev->mUrl, ev->mCode, 
                                      (void *)ev->mExtraInfo);
}

void PR_CALLBACK NotificationEvent::DestroyPLEvent(PLEvent* aEvent)
{
  NotificationEvent *ev = (NotificationEvent*)aEvent;

  delete ev;
}

PRStatus NotificationEvent::Fire(nsIEventQueue* aEventQ) 
{
  PL_InitEvent(this, nsnull,
               (PLHandleEventProc)  NotificationEvent::HandlePLEvent,
               (PLDestroyEventProc) NotificationEvent::DestroyPLEvent);

  PRStatus status = aEventQ->PostEvent(this);
  return status;
}



//----------------------------------------------------------------------

// Entry point to create nsAppShellService factory instances...
NS_DEF_FACTORY(DefaultProtocolHelper,nsDefaultProtocolHelper)

nsresult NS_NewDefaultProtocolHelperFactory(nsIFactory** aResult)
{
  nsresult rv = NS_OK;
  nsIFactory* inst;
  
  inst = new nsDefaultProtocolHelperFactory;
  if (nsnull == inst) {
    rv = NS_ERROR_OUT_OF_MEMORY;
  }
  else {
    NS_ADDREF(inst);
  }
  *aResult = inst;
  return rv;
}


