/* -*- Mode: C++; tab-width: 4; 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 <stdlib.h>
#include <iostream.h>
#include "plstr.h"
#include "prlink.h"
#include "nsRepository.h"
#ifdef USE_NSREG
#define STANDALONE_REGISTRY
#define XP_BEGIN_PROTOS extern "C" {
#define XP_END_PROTOS };
#include "NSReg.h"
#endif

nsHashtable *NSRepository::factories = NULL;
PRMonitor *NSRepository::monitor = NULL;

static NS_DEFINE_IID(kFactoryIID, NS_IFACTORY_IID);
static NS_DEFINE_IID(kFactory2IID, NS_IFACTORY2_IID);

class FactoryEntry {
public:
  nsCID cid;
  nsIFactory *factory;
  char *library;
  PRLibrary *instance;

  FactoryEntry(const nsCID &aClass, nsIFactory *aFactory, 
               const char *aLibrary) {
    cid = aClass;
    factory = aFactory;
    if (aLibrary != NULL) {
      library = PL_strdup(aLibrary);
    } else {
      library = NULL;
    }
    instance = NULL;
  }
  ~FactoryEntry() {
    if (library != NULL) {
      free(library);
    }
    if (instance != NULL) {
      PR_UnloadLibrary(instance);
    }
    if (factory != NULL) {
      factory->Release();
    }
  }
};

class IDKey: public nsHashKey {
private:
  nsID id;
  
public:
  IDKey(const nsID &aID) {
    id = aID;
  }
  
  PRUint32 HashValue() {
    return id.m0;
  }

  PRBool Equals(nsHashKey *aKey) {
    return (id.Equals(((IDKey *) aKey)->id));
  }

  nsHashKey *Clone() {
    return new IDKey(id);
  }
};

#ifdef USE_NSREG
#define USE_REGISTRY

static nsresult platformRegister(const nsCID &aCID, const char *aLibrary)
{
  HREG hreg;
  REGERR err = NR_RegOpen(NULL, &hreg);
  if (err == REGERR_OK) {
    RKEY key;
    err = NR_RegAddKey(hreg, ROOTKEY_COMMON, 
                       "SOFTWARE\\Netscape\\CID", &key);
    if (err == REGERR_OK) {
      char *cidString = aCID.ToString();
      NR_RegSetEntryString(hreg, key, cidString, (char *) aLibrary);
      delete [] cidString;
    }
    NR_RegClose(hreg);
  }
  return NS_OK;
}

static nsresult platformUnregister(const nsCID &aCID, const char *aLibrary) 
{  
  HREG hreg;
  REGERR err = NR_RegOpen(NULL, &hreg);
  if (err == REGERR_OK) {
    RKEY key;
    err = NR_RegAddKey(hreg, ROOTKEY_COMMON, 
                       "SOFTWARE\\Netscape\\CID", &key);
    if (err == REGERR_OK) {
      char *cidString = aCID.ToString();
      NR_RegDeleteEntry(hreg, key, cidString);
      delete [] cidString;
    }
    NR_RegClose(hreg);
  }
  return NS_OK;
}

static FactoryEntry *platformFind(const nsCID &aCID)
{
  FactoryEntry *res = NULL;
  HREG hreg;
  REGERR err = NR_RegOpen(NULL, &hreg);
  if (err == REGERR_OK) {
    RKEY key;
    err = NR_RegGetKey(hreg, ROOTKEY_COMMON, 
                       "SOFTWARE\\Netscape\\CID", &key);
    if (err == REGERR_OK) {
      char *cidString = aCID.ToString();
      char library[_MAX_PATH];
      uint32 len = _MAX_PATH;
      err = NR_RegGetEntryString(hreg, key, cidString, library, len);
                                 
      delete [] cidString;
      if (err == REGERR_OK) {
        res = new FactoryEntry(aCID, NULL, library);
        NR_RegClose(hreg);
      }
    }
  }
  return res;
}

#else // USE_NSREG

#ifdef XP_PC
#define USE_REGISTRY

static nsresult platformRegister(const nsCID &aCID, const char *aLibrary)
{
  HKEY key;
  LONG res = RegCreateKey(HKEY_CURRENT_USER, "SOFTWARE\\Netscape\\CID", &key);
  if (res == ERROR_SUCCESS) {
    char *cidString = aCID.ToString();
    RegSetValue(key, cidString, REG_SZ, aLibrary, PL_strlen(aLibrary));
    delete [] cidString;
    RegCloseKey(key);
  }
  return NS_OK;
}

static nsresult platformUnregister(const nsCID &aCID, const char *aLibrary)
{
  HKEY key;
  nsresult res = NS_OK;
  LONG err = RegCreateKey(HKEY_CURRENT_USER, "SOFTWARE\\Netscape\\CID", &key);
  if (err == ERROR_SUCCESS) {
    char *cidString = aCID.ToString();
    err = RegDeleteKey(key, cidString);
    if (err != ERROR_SUCCESS) {
      res = NS_ERROR_FACTORY_NOT_UNREGISTERED;
    }
    delete [] cidString;
    RegCloseKey(key);
  }
  return res;
}

static FactoryEntry *platformFind(const nsCID &aCID)
{
  HKEY key;
  LONG res = RegOpenKey(HKEY_CURRENT_USER, "SOFTWARE\\Netscape\\CID", &key);

  if (res == ERROR_SUCCESS) {
    char *cidString = aCID.ToString();
    char library[_MAX_PATH];
    LONG len = _MAX_PATH;
    res = RegQueryValue(key, cidString, library, &len);
    delete [] cidString;
    if (res == ERROR_SUCCESS) {
      FactoryEntry *entry = new FactoryEntry(aCID, NULL, library);
      return entry;
    }
  }
  return NULL;
}

#endif // XP_PC

#endif // USE_NSREG

static nsresult loadFactory(FactoryEntry *aEntry,
                            nsIFactory **aFactory)
{
  if (aFactory == NULL) {
    return NS_ERROR_NULL_POINTER;
  }
  *aFactory = NULL;

  if (aEntry->instance == NULL) {
    aEntry->instance = PR_LoadLibrary(aEntry->library);
  }
  if (aEntry->instance != NULL) {
    nsFactoryProc proc = (nsFactoryProc) PR_FindSymbol(aEntry->instance,
                                                       "NSGetFactory");
    if (proc != NULL) {
      nsresult res = proc(aEntry->cid, aFactory);
      return res;
    }
  }

  return NS_ERROR_FACTORY_NOT_LOADED;
}

nsresult NSRepository::FindFactory(const nsCID &aClass,
                                   nsIFactory **aFactory) 
{
  checkInitialized();

  if (aFactory == NULL) {
    return NS_ERROR_NULL_POINTER;
  }

  PR_EnterMonitor(monitor);

  IDKey key(aClass);
  FactoryEntry *entry = (FactoryEntry*) factories->Get(&key);

  nsresult res = NS_ERROR_FACTORY_NOT_REGISTERED;

#ifdef USE_REGISTRY
  if (entry == NULL) {
    entry = platformFind(aClass);
    // If we got one, cache it in our hashtable
    if (entry != NULL) {
      factories->Put(&key, entry);
    }
  }
#endif

  PR_ExitMonitor(monitor);

  if (entry != NULL) {
    if ((entry)->factory == NULL) {
      res = loadFactory(entry, aFactory);
    } else {
      *aFactory = entry->factory;
      (*aFactory)->AddRef();
      res = NS_OK;
    }
  }

  return res;
}

nsresult NSRepository::checkInitialized() 
{
  nsresult res = NS_OK;
  if (factories == NULL) {
    res = Initialize();
  }
  return res;
}

nsresult NSRepository::Initialize() 
{
  if (factories == NULL) {
    factories = new nsHashtable();
  }
  if (monitor == NULL) {
    monitor = PR_NewMonitor();
  }

  return NS_OK;
}

nsresult NSRepository::CreateInstance(const nsCID &aClass, 
                                      nsISupports *aDelegate,
                                      const nsIID &aIID,
                                      void **aResult)
{
  if (aResult == NULL) {
    return NS_ERROR_NULL_POINTER;
  }
  *aResult = NULL;

  nsIFactory *factory = NULL;
  nsresult res = FindFactory(aClass, &factory);
  if (NS_SUCCEEDED(res)) {
    res = factory->CreateInstance(aDelegate, aIID, aResult);
    factory->Release();

    return res;
  }
  
  return NS_ERROR_FACTORY_NOT_REGISTERED;
}

nsresult NSRepository::CreateInstance2(const nsCID &aClass, 
                                       nsISupports *aDelegate,
                                       const nsIID &aIID,
                                       void *aSignature,
                                       void **aResult)
{
  if (aResult == NULL) {
    return NS_ERROR_NULL_POINTER;
  }
  *aResult = NULL;

  nsIFactory *factory = NULL;

  nsresult res = FindFactory(aClass, &factory);

  if (NS_SUCCEEDED(res)) {
    nsIFactory2 *factory2 = NULL;
    res = NS_ERROR_FACTORY_NO_SIGNATURE_SUPPORT;
    
    factory->QueryInterface(kFactory2IID, (void **) &factory2);

    if (factory2 != NULL) {
      res = factory2->CreateInstance2(aDelegate, aIID, aSignature, aResult);
      factory2->Release();
    }

    factory->Release();
    return res;
  }
  
  return NS_ERROR_FACTORY_NOT_REGISTERED;
}

nsresult NSRepository::RegisterFactory(const nsCID &aClass,
                                       nsIFactory *aFactory, 
                                       PRBool aReplace)
{
  checkInitialized();

  nsIFactory *old = NULL;
  FindFactory(aClass, &old);
  
  if (old != NULL) {
    old->Release();
    if (!aReplace) {
      return NS_ERROR_FACTORY_EXISTS;
    }
  }

  PR_EnterMonitor(monitor);

  IDKey key(aClass);
  factories->Put(&key, new FactoryEntry(aClass, aFactory, NULL));

  PR_ExitMonitor(monitor);

  return NS_OK;
}

nsresult NSRepository::RegisterFactory(const nsCID &aClass,
                                       const char *aLibrary,
                                       PRBool aReplace,
                                       PRBool aPersist)
{
  checkInitialized();

  nsIFactory *old = NULL;
  FindFactory(aClass, &old);
  
  if (old != NULL) {
    old->Release();
    if (!aReplace) {
      return NS_ERROR_FACTORY_EXISTS;
    }
  }

  PR_EnterMonitor(monitor);

#ifdef USE_REGISTRY
  if (aPersist == PR_TRUE) {
    platformRegister(aClass, aLibrary);
  } 
  else
#endif
  {
    IDKey key(aClass);
    factories->Put(&key, new FactoryEntry(aClass, NULL, aLibrary));
  }

  PR_ExitMonitor(monitor);

  return NS_OK;
}

nsresult NSRepository::UnregisterFactory(const nsCID &aClass,
                                         nsIFactory *aFactory)
{
  checkInitialized();

  nsIFactory *old = NULL;
  FindFactory(aClass, &old);

  nsresult res = NS_ERROR_FACTORY_NOT_REGISTERED;
  if (old != NULL) {
    if (old == aFactory) {
      PR_EnterMonitor(monitor);

      IDKey key(aClass);
      FactoryEntry *entry = (FactoryEntry *) factories->Remove(&key);
      delete entry;

      PR_ExitMonitor(monitor);
  
      res = NS_OK;
    }
    old->Release();
  }

  return res;
}

nsresult NSRepository::UnregisterFactory(const nsCID &aClass,
                                         const char *aLibrary)
{
  checkInitialized();

  IDKey key(aClass);
  FactoryEntry *old = (FactoryEntry *) factories->Get(&key);

  nsresult res = NS_ERROR_FACTORY_NOT_REGISTERED;

  PR_EnterMonitor(monitor);

  if (old != NULL) {
#ifdef XP_UNIX
    if (old->library != NULL && PL_strcasecmp(old->library, aLibrary))
#else
    if (old->library != NULL && PL_strcmp(old->library, aLibrary))
#endif
    {
      FactoryEntry *entry = (FactoryEntry *) factories->Remove(&key);
      delete entry;
      res = NS_OK;
    }
  }
#ifdef USE_REGISTRY
  res = platformUnregister(aClass, aLibrary);
#endif

  PR_ExitMonitor(monitor);

  return res;
}

static PRBool freeLibraryEnum(nsHashKey *aKey, void *aData) 
{
  FactoryEntry *entry = (FactoryEntry *) aData;

  if (entry->instance != NULL) {
    nsCanUnloadProc proc = (nsCanUnloadProc) PR_FindSymbol(entry->instance,
                                                           "NSCanUnload");
    if (proc != NULL) {
      PRBool res = proc();
      if (res) {
        PR_UnloadLibrary(entry->instance);
        entry->instance = NULL;
      }
    }    
  }

  return PR_TRUE;
}

nsresult NSRepository::FreeLibraries() 
{
  PR_EnterMonitor(monitor);

  factories->Enumerate(freeLibraryEnum);

  PR_ExitMonitor(monitor);

  return NS_OK;
}

