/* -*- 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.
 */
#include "prmon.h"
#include "nsIEventQueueService.h"
#include "nsHashtable.h"
#include "nsXPComFactory.h"


static NS_DEFINE_IID(kIEventQueueServiceIID, NS_IEVENTQUEUESERVICE_IID);



// XXX move to nsID.h or nsHashtable.h? (copied from nsRepository.cpp)
class ThreadKey: public nsHashKey {
private:
    const PRThread* id;
  
public:
    ThreadKey(const PRThread* aID) {
        id = aID;
    }
  
    PRUint32 HashValue(void) const {
        return (PRUint32)id;
    }

    PRBool Equals(const nsHashKey *aKey) const {
        return (id == ((const ThreadKey *) aKey)->id);
    }

    nsHashKey *Clone(void) const {
        return new ThreadKey(id);
    }
};


/*
 * This class maintains the data associated with each entry in the EventQueue
 * service's hash table...
 *
 * It derives from nsISupports merely as a convienence since the entries are
 * reference counted...
 */
class EventQueueEntry : public nsISupports 
{
public:
  EventQueueEntry();
  ~EventQueueEntry();

  // nsISupports interface...
  NS_DECL_ISUPPORTS

  PLEventQueue* GetEventQueue(void);

private: 
  PLEventQueue* mEventQueue; 
};

/* nsISupports interface implementation... */
NS_IMPL_ISUPPORTS(EventQueueEntry,kISupportsIID);

EventQueueEntry::EventQueueEntry()
{
  NS_INIT_REFCNT();

#ifdef XP_PC
  // XXX: For now only use the main eventQ...  When the event queue is
  //      created via PL_CreateNativeEventQueue(...) this can go away...
  PL_InitializeEventsLib("");
  mEventQueue = PL_GetMainEventQueue();
#else
  mEventQueue = PL_CreateEventQueue("Thread event queue...", PR_GetCurrentThread());
#endif
}

EventQueueEntry::~EventQueueEntry()
{
  if (NULL != mEventQueue) {
    PL_DestroyEventQueue(mEventQueue);
  }
}

PLEventQueue* EventQueueEntry::GetEventQueue(void)
{
  return mEventQueue;
}



class nsEventQueueServiceImpl : public nsIEventQueueService
{
public:
  nsEventQueueServiceImpl();

  // nsISupports interface...
  NS_DECL_ISUPPORTS

  // nsIEventQueueService interface...
  NS_IMETHOD CreateThreadEventQueue(void);
  NS_IMETHOD DestroyThreadEventQueue(void);
  NS_IMETHOD GetThreadEventQueue(PRThread* aThread, PLEventQueue** aResult);

protected:
  ~nsEventQueueServiceImpl();

private:
  nsHashtable* mEventQTable;
  PRMonitor*   mEventQMonitor;
};


nsEventQueueServiceImpl::nsEventQueueServiceImpl()
{
  NS_INIT_REFCNT();

  mEventQTable   = new nsHashtable(16);
  mEventQMonitor = PR_NewMonitor();
}

static PRBool
DeleteEntry(nsHashKey *aKey, void *aData, void* closure)
{
  EventQueueEntry* evQueueEntry = (EventQueueEntry*)aData;

  if (NULL != evQueueEntry) {
    delete evQueueEntry;
  }
  return PR_TRUE;
}

nsEventQueueServiceImpl::~nsEventQueueServiceImpl()
{
  // Destroy any remaining PLEventQueue's still in the hash table...
  mEventQTable->Enumerate(DeleteEntry);
  delete mEventQTable;

  PR_DestroyMonitor(mEventQMonitor);
}

/* nsISupports interface implementation... */
NS_IMPL_ISUPPORTS(nsEventQueueServiceImpl,kIEventQueueServiceIID);

/* nsIEventQueueService interface implementation... */

NS_IMETHODIMP
nsEventQueueServiceImpl::CreateThreadEventQueue(void)
{
  nsresult rv = NS_OK;
  ThreadKey  key(PR_GetCurrentThread());
  EventQueueEntry* evQueueEntry;

  /* Enter the lock which protects the EventQ hashtable... */
  PR_EnterMonitor(mEventQMonitor);

  /* Only create one event queue per thread... */
  evQueueEntry = (EventQueueEntry*)mEventQTable->Get(&key);
  if (NULL == evQueueEntry) {
    evQueueEntry = new EventQueueEntry();  
    if (NULL == evQueueEntry) {
      rv = NS_ERROR_OUT_OF_MEMORY;
      goto done;
    }

    mEventQTable->Put(&key, evQueueEntry);
  }
  NS_ADDREF(evQueueEntry);

done:
  // Release the EventQ lock...
  PR_ExitMonitor(mEventQMonitor);
  return rv;
}

NS_IMETHODIMP
nsEventQueueServiceImpl::DestroyThreadEventQueue(void)
{
  nsresult rv = NS_OK;
  ThreadKey key(PR_GetCurrentThread());
  EventQueueEntry* evQueueEntry;

  /* Enter the lock which protects the EventQ hashtable... */
  PR_EnterMonitor(mEventQMonitor);

  evQueueEntry = (EventQueueEntry*)mEventQTable->Get(&key);
  if (NULL != evQueueEntry) {
    nsrefcnt refcnt;

    NS_RELEASE2(evQueueEntry, refcnt);
    // Remove the entry from the hash table if this was the last reference...
    if (0 == refcnt) {
      mEventQTable->Remove(&key);
    }
  } else {
    rv = NS_ERROR_FAILURE;
  }

  // Release the EventQ lock...
  PR_ExitMonitor(mEventQMonitor);
  return rv;
}


NS_IMETHODIMP
nsEventQueueServiceImpl::GetThreadEventQueue(PRThread* aThread, PLEventQueue** aResult)
{
  nsresult rv = NS_OK;
  EventQueueEntry* evQueueEntry;
  ThreadKey key(aThread);

  /* Enter the lock which protects the EventQ hashtable... */
  PR_EnterMonitor(mEventQMonitor);

  /* Parameter validation... */
  if ((NULL == aThread) || (NULL == aResult)) {
    rv = NS_ERROR_NULL_POINTER;
    goto done;
  }

  evQueueEntry = (EventQueueEntry*)mEventQTable->Get(&key);
  if (NULL != evQueueEntry) {
    *aResult = evQueueEntry->GetEventQueue();
  } else {
    // XXX: Need error code for requesting an event queue when none exists...
    rv = NS_ERROR_FAILURE;
    goto done;
  }

done:
  // Release the EventQ lock...
  PR_ExitMonitor(mEventQMonitor);
  return rv;
}

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

static nsEventQueueServiceImpl* gServiceInstance = NULL;

class nsEventQueueServiceFactory : public nsFactory<nsEventQueueServiceImpl>
{
public:
  NS_IMETHOD CreateInstance(nsISupports *aOuter,
                            const nsIID &aIID,
                            void **aResult);
};

NS_IMETHODIMP
nsEventQueueServiceFactory::CreateInstance(nsISupports *aOuter,
                                           const nsIID &aIID,
                                           void **aResult)
{
  nsresult rv;
  nsISupports* inst;

  // Parameter validation...
  if (NULL == aResult) {
    rv = NS_ERROR_NULL_POINTER;
    goto done;
  }
  // Do not support aggregatable components...
  *aResult = NULL;
  if (NULL != aOuter) {
    rv = NS_ERROR_NO_AGGREGATION;
    goto done;
  }

  if (NULL == gServiceInstance) {
    // Create a new instance of the component...
    NS_NEWXPCOM(gServiceInstance, nsEventQueueServiceImpl);
    if (NULL == gServiceInstance) {
      rv = NS_ERROR_OUT_OF_MEMORY;
      goto done;
    }
    NS_ADDREF(gServiceInstance);
  }

  // If the QI fails, the component will be destroyed...
  //
  // Use a local copy so the NS_RELEASE() will not null the global
  // pointer...
  inst = gServiceInstance;

  NS_ADDREF(inst);
  rv = inst->QueryInterface(aIID, aResult);
  NS_RELEASE(inst);

done:
  return rv;
}


// Entry point to create nsEventQueueService factory instances...

nsresult NS_NewEventQueueServiceFactory(nsIFactory** aResult)
{
  nsresult rv = NS_OK;
  nsIFactory* inst = new nsEventQueueServiceFactory();
  if (NULL == inst) {
    rv = NS_ERROR_OUT_OF_MEMORY;
  }
  else {
    NS_ADDREF(inst);
  }
  *aResult = inst;
  return rv;
}
