/* ***** BEGIN LICENSE BLOCK *****
 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (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/MPL/
 *
 * 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 the Python XPCOM language bindings.
 *
 * The Initial Developer of the Original Code is
 * ActiveState Tool Corp.
 * Portions created by the Initial Developer are Copyright (C) 2000
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 *   Mark Hammond <mhammond@skippinet.com.au> (original author)
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either the GNU General Public License Version 2 or later (the "GPL"), or
 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the MPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the MPL, the GPL or the LGPL.
 *
 * ***** END LICENSE BLOCK ***** */

//
// This code is part of the XPCOM extensions for Python.
//
// Written May 2000 by Mark Hammond.
//
// Based heavily on the Python COM support, which is
// (c) Mark Hammond and Greg Stein.
//
// (c) 2000, ActiveState corp.

#include "PyXPCOM_std.h"
#include "nsDirectoryServiceDefs.h"
#include "nsDirectoryServiceUtils.h"
#include "nsILocalFile.h"
#include "nsITimelineService.h"

#include "nspr.h" // PR_fprintf

#ifdef XP_WIN
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif
#include "windows.h"
#endif

#ifdef XP_UNIX
#include <dlfcn.h>
#include <sys/stat.h>
#endif

static PRLock *g_lockMain = nsnull;

PYXPCOM_EXPORT PyObject *PyXPCOM_Error = NULL;
PYXPCOM_EXPORT PRBool PyXPCOM_ModuleInitialized = PR_FALSE;

PyXPCOM_INTERFACE_DEFINE(Py_nsIComponentManager, nsIComponentManager, PyMethods_IComponentManager)
PyXPCOM_INTERFACE_DEFINE(Py_nsIInterfaceInfoManager, nsIInterfaceInfoManager, PyMethods_IInterfaceInfoManager)
PyXPCOM_INTERFACE_DEFINE(Py_nsIEnumerator, nsIEnumerator, PyMethods_IEnumerator)
PyXPCOM_INTERFACE_DEFINE(Py_nsISimpleEnumerator, nsISimpleEnumerator, PyMethods_ISimpleEnumerator)
PyXPCOM_INTERFACE_DEFINE(Py_nsIInterfaceInfo, nsIInterfaceInfo, PyMethods_IInterfaceInfo)
PyXPCOM_INTERFACE_DEFINE(Py_nsIInputStream, nsIInputStream, PyMethods_IInputStream)
PyXPCOM_INTERFACE_DEFINE(Py_nsIClassInfo, nsIClassInfo, PyMethods_IClassInfo)
PyXPCOM_INTERFACE_DEFINE(Py_nsIVariant, nsIVariant, PyMethods_IVariant)

////////////////////////////////////////////////////////////
// Lock/exclusion global functions.
//
PYXPCOM_EXPORT void
PyXPCOM_AcquireGlobalLock(void)
{
	NS_PRECONDITION(g_lockMain != nsnull, "Cant acquire a NULL lock!");
	PR_Lock(g_lockMain);
}

PYXPCOM_EXPORT void
PyXPCOM_ReleaseGlobalLock(void)
{
	NS_PRECONDITION(g_lockMain != nsnull, "Cant release a NULL lock!");
	PR_Unlock(g_lockMain);
}

// Ensure that any paths guaranteed by this package exist on sys.path
// Only called once as we are first loaded into the process.
void AddStandardPaths()
{
	// Put {bin}\Python on the path if it exists.
	nsresult rv;
	nsCOMPtr<nsIFile> aFile;
	// XXX - this needs more thought for XULRunner - we want to stick the global
	// 'python' dir on sys.path (for xpcom etc), but also want to support a way
	// of adding a local application directory (in the case of xulrunner) too.
	// NS_XPCOM_CURRENT_PROCESS_DIR is the XULRunner app dir (ie, where application.ini lives)
	// NS_GRE_DIR is the 'bin' dir for XULRunner itself.
	rv = NS_GetSpecialDirectory(NS_GRE_DIR, getter_AddRefs(aFile));
	if (NS_FAILED(rv)) {
		PyXPCOM_LogError("The Python XPCOM loader could not locate the 'bin' directory");
		return;
	}
	aFile->Append(NS_LITERAL_STRING("python"));
	nsAutoString pathBuf;
	aFile->GetPath(pathBuf);
	PyObject *obPath = PySys_GetObject("path");
	if (!obPath) {
		PyXPCOM_LogError("The Python XPCOM loader could not get the Python sys.path variable");
		return;
	}
	// XXX - this should use the file-system encoding...
	NS_LossyConvertUTF16toASCII pathCBuf(pathBuf);
#ifdef NS_DEBUG
	PR_fprintf(PR_STDERR,"The Python XPCOM loader is adding '%s' to sys.path\n",
	           pathCBuf.get());
#endif
	PyObject *newStr = PyString_FromString(pathCBuf.get());
	PyList_Insert(obPath, 0, newStr);
	Py_XDECREF(newStr);
	// And now try and get Python to process this directory as a "site dir" 
	// - ie, look for .pth files, etc
	nsCAutoString cmdBuf(NS_LITERAL_CSTRING("import site;site.addsitedir(r'"));
	cmdBuf.Append(pathCBuf);
	cmdBuf.Append(NS_LITERAL_CSTRING("')\n"));
	if (0 != PyRun_SimpleString((char *)cmdBuf.get())) {
		PyXPCOM_LogError("The directory '%s' could not be added as a site directory", pathCBuf.get());
		PyErr_Clear();
	}
	// and somewhat like Python itself (site, citecustomize), we attempt 
	// to import "sitepyxpcom" ignoring ImportError
	PyObject *mod = PyImport_ImportModule("sitepyxpcom");
	if (NULL==mod) {
		if (!PyErr_ExceptionMatches(PyExc_ImportError))
			PyXPCOM_LogError("Failed to import 'sitepyxpcom'");
		PyErr_Clear();
	} else
		Py_DECREF(mod);
}

static PRBool bIsInitialized = PR_FALSE;
// Our 'entry point' into initialization - just call this any time you
// like, and the world will be setup!
PYXPCOM_EXPORT void
PyXPCOM_EnsurePythonEnvironment(void)
{
	// Must be thread-safe - but only while set to FALSE - so check for
	// set before getting the lock - then check again after!
	if (bIsInitialized)
		return;
	CEnterLeaveXPCOMFramework _celf;
	if (bIsInitialized)
		return; // another thread beat us to the init.

#if defined(XP_UNIX) && !defined(XP_MACOSX)
	/* *sob* - seems necessary to open the .so as RTLD_GLOBAL.  Without
	this we see:
	    Traceback (most recent call last):
	      File "<string>", line 1, in ?
	      File "/usr/lib/python2.4/logging/__init__.py", line 29, in ?
	        import sys, os, types, time, string, cStringIO, traceback
	    ImportError: /usr/lib/python2.4/lib-dynload/time.so: undefined
	                                                symbol: PyExc_IOError

	On osx, ShaneC writes that is it unnecessary (and fails anyway since 
	PYTHON_SO is wrong.)  More clues about this welcome!
	*/

	dlopen(PYTHON_SO,RTLD_NOW | RTLD_GLOBAL);
#endif

	PRBool bDidInitPython = !Py_IsInitialized(); // well, I will next line, anyway :-)
	if (bDidInitPython) {
		NS_TIMELINE_START_TIMER("PyXPCOM: Python initializing");
		Py_Initialize(); // NOTE: We never finalize Python!!
#ifndef NS_DEBUG
		Py_OptimizeFlag = 1;
#endif // NS_DEBUG
		// Must force Python to start using thread locks, as
		// this is certainly a threaded environment we are playing in
		PyEval_InitThreads();

		NS_TIMELINE_STOP_TIMER("PyXPCOM: Python initializing");
		NS_TIMELINE_MARK_TIMER("PyXPCOM: Python initializing");
	}
	// Get the Python interpreter state
	NS_TIMELINE_START_TIMER("PyXPCOM: Python threadstate setup");
	PyGILState_STATE state = PyGILState_Ensure();
#ifdef MOZ_TIMELINE
	// If the timeline service is installed, see if we can install our hooks.
	if (NULL==PyImport_ImportModule("timeline_hook")) {
		if (!PyErr_ExceptionMatches(PyExc_ImportError))
			PyXPCOM_LogError("Failed to import 'timeline_hook'");
		PyErr_Clear(); // but don't care if we can't.
	}
#endif

	// Make sure we have _something_ as sys.argv.
	if (PySys_GetObject("argv")==NULL) {
		PyObject *path = PyList_New(0);
		PyObject *str = PyString_FromString("");
		PyList_Append(path, str);
		PySys_SetObject("argv", path);
		Py_XDECREF(path);
		Py_XDECREF(str);
	}

	// Add the standard extra paths we assume
	AddStandardPaths();

	// The exception object pyxpcom uses.
	if (PyXPCOM_Error == NULL) {
		PRBool rc = PR_FALSE;
		PyObject *mod = NULL;

		mod = PyImport_ImportModule("xpcom");
		if (mod!=NULL) {
			PyXPCOM_Error = PyObject_GetAttrString(mod, "Exception");
			Py_DECREF(mod);
		}
		rc = (PyXPCOM_Error != NULL);
	}

	// Register our custom interfaces.
	Py_nsISupports::InitType();
	Py_nsIComponentManager::InitType();
	Py_nsIInterfaceInfoManager::InitType();
	Py_nsIEnumerator::InitType();
	Py_nsISimpleEnumerator::InitType();
	Py_nsIInterfaceInfo::InitType();
	Py_nsIInputStream::InitType();
	Py_nsIClassInfo::InitType();
	Py_nsIVariant::InitType();

	bIsInitialized = PR_TRUE;
	// import the xpcom module itself to setup the loggers etc.
	// We must do this after setting bIsInitialized, as it too tries to
	// initialize!
	PyImport_ImportModule("xpcom");

	// If we initialized Python, then we will also have acquired the thread
	// lock.  In that case, we want to leave it unlocked, so other threads
	// are free to run, even if they aren't running Python code.
	PyGILState_Release(bDidInitPython ? PyGILState_UNLOCKED : state);

	NS_TIMELINE_STOP_TIMER("PyXPCOM: Python threadstate setup");
	NS_TIMELINE_MARK_TIMER("PyXPCOM: Python threadstate setup");
}

void pyxpcom_construct(void)
{
	// Create the lock we will use to ensure startup thread
	// safetly, but don't actually initialize Python yet.
	g_lockMain = PR_NewLock();
	return; // PR_TRUE;
}

void pyxpcom_destruct(void)
{
	PR_DestroyLock(g_lockMain);
}

// Yet another attempt at cross-platform library initialization and finalization.
struct DllInitializer {
	DllInitializer() {
		pyxpcom_construct();
	}
	~DllInitializer() {
		pyxpcom_destruct();
	}
} dll_initializer;