/* ***** 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 <prthread.h>
#include "nsIThread.h"
#include "nsILocalFile.h"

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

static PRInt32 g_cLockCount = 0;
static PRLock *g_lockMain = nsnull;

PYXPCOM_EXPORT PyObject *PyXPCOM_Error = NULL;

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)
// deprecated, but retained for backward compatibility:
PyXPCOM_INTERFACE_DEFINE(Py_nsIComponentManagerObsolete, nsIComponentManagerObsolete, PyMethods_IComponentManagerObsolete)

#ifndef PYXPCOM_USE_PYGILSTATE

////////////////////////////////////////////////////////////
// Thread-state helpers/global functions.
// Only used if there is no Python PyGILState_* API
//
static PyThreadState *ptsGlobal = nsnull;
PyInterpreterState *PyXPCOM_InterpreterState = nsnull;
PRUintn tlsIndex = 0;


// This function must be called at some time when the interpreter lock and state is valid.
// Called by init{module} functions and also COM factory entry point.
void PyXPCOM_InterpreterState_Ensure()
{
	if (PyXPCOM_InterpreterState==NULL) {
		PyThreadState *threadStateSave = PyThreadState_Swap(NULL);
		if (threadStateSave==NULL)
			Py_FatalError("Can not setup interpreter state, as current state is invalid");

		PyXPCOM_InterpreterState = threadStateSave->interp;
		PyThreadState_Swap(threadStateSave);
	}
}

void PyXPCOM_InterpreterState_Free()
{
	PyXPCOM_ThreadState_Free();
	PyXPCOM_InterpreterState = NULL; // Eek - should I be freeing something?
}

// This structure is stored in the TLS slot.  At this stage only a Python thread state
// is kept, but this may change in the future...
struct ThreadData{
	PyThreadState *ts;
};

// Ensure that we have a Python thread state available to use.
// If this is called for the first time on a thread, it will allocate
// the thread state.  This does NOT change the state of the Python lock.
// Returns TRUE if a new thread state was created, or FALSE if a
// thread state already existed.
PRBool PyXPCOM_ThreadState_Ensure()
{
	ThreadData *pData = (ThreadData *)PR_GetThreadPrivate(tlsIndex);
	if (pData==NULL) { /* First request on this thread */
		/* Check we have an interpreter state */
		if (PyXPCOM_InterpreterState==NULL) {
				Py_FatalError("Can not setup thread state, as have no interpreter state");
		}
		pData = (ThreadData *)nsMemory::Alloc(sizeof(ThreadData));
		if (!pData)
			Py_FatalError("Out of memory allocating thread state.");
		memset(pData, 0, sizeof(*pData));
		if (NS_FAILED( PR_SetThreadPrivate( tlsIndex, pData ) ) ) {
			NS_ABORT_IF_FALSE(0, "Could not create thread data for this thread!");
			Py_FatalError("Could not thread private thread data!");
		}
		pData->ts = PyThreadState_New(PyXPCOM_InterpreterState);
		return PR_TRUE; // Did create a thread state state
	}
	return PR_FALSE; // Thread state was previously created
}

// Asuming we have a valid thread state, acquire the Python lock.
void PyXPCOM_InterpreterLock_Acquire()
{
	ThreadData *pData = (ThreadData *)PR_GetThreadPrivate(tlsIndex);
	NS_ABORT_IF_FALSE(pData, "Have no thread data for this thread!");
	PyThreadState *thisThreadState = pData->ts;
	PyEval_AcquireThread(thisThreadState);
}

// Asuming we have a valid thread state, release the Python lock.
void PyXPCOM_InterpreterLock_Release()
{
	ThreadData *pData = (ThreadData *)PR_GetThreadPrivate(tlsIndex);
	NS_ABORT_IF_FALSE(pData, "Have no thread data for this thread!");
	PyThreadState *thisThreadState = pData->ts;
	PyEval_ReleaseThread(thisThreadState);
}

// Free the thread state for the current thread
// (Presumably previously create with a call to
// PyXPCOM_ThreadState_Ensure)
void PyXPCOM_ThreadState_Free()
{
	ThreadData *pData = (ThreadData *)PR_GetThreadPrivate(tlsIndex);
	if (!pData) return;
	PyThreadState *thisThreadState = pData->ts;
	PyThreadState_Delete(thisThreadState);
	PR_SetThreadPrivate(tlsIndex, NULL);
	nsMemory::Free(pData);
}

void PyXPCOM_ThreadState_Clear()
{
	ThreadData *pData = (ThreadData *)PR_GetThreadPrivate(tlsIndex);
	PyThreadState *thisThreadState = pData->ts;
	PyThreadState_Clear(thisThreadState);
}
#endif // PYXPCOM_USE_PYGILSTATE

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

void PyXPCOM_DLLAddRef(void)
{
	// Must be thread-safe, although cant have the Python lock!
	CEnterLeaveXPCOMFramework _celf;
	PRInt32 cnt = PR_AtomicIncrement(&g_cLockCount);
	if (cnt==1) { // First call
		if (!Py_IsInitialized()) {
			Py_Initialize();
			// Make sure our Windows framework is all setup.
			PyXPCOM_Globals_Ensure();
			// Make sure we have _something_ as sys.argv.
			if (PySys_GetObject((char*)"argv")==NULL) {
				PyObject *path = PyList_New(0);
#if PY_MAJOR_VERSION <= 2
				PyObject *str = PyString_FromString("");
#else
				PyObject *str = PyUnicode_FromString("");
#endif
				PyList_Append(path, str);
				PySys_SetObject((char*)"argv", path);
				Py_XDECREF(path);
				Py_XDECREF(str);
			}

			// Must force Python to start using thread locks, as
			// we are free-threaded (maybe, I think, sometimes :-)
			PyEval_InitThreads();
#ifndef PYXPCOM_USE_PYGILSTATE
			// Release Python lock, as first thing we do is re-get it.
			ptsGlobal = PyEval_SaveThread();
#endif
			// NOTE: We never finalize Python!!
		}
	}
}
void PyXPCOM_DLLRelease(void)
{
	PR_AtomicDecrement(&g_cLockCount);
}

void pyxpcom_construct(void)
{
	g_lockMain = PR_NewLock();
#ifndef PYXPCOM_USE_PYGILSTATE
	PRStatus status;
	status = PR_NewThreadPrivateIndex( &tlsIndex, NULL );
	NS_WARN_IF_FALSE(status==0, "Could not allocate TLS storage");
	if (NS_FAILED(status)) {
		PR_DestroyLock(g_lockMain);
		return; // PR_FALSE;
	}
#endif // PYXPCOM_USE_PYGILSTATE
	return; // PR_TRUE;
}

void pyxpcom_destruct(void)
{
	PR_DestroyLock(g_lockMain);
#ifndef PYXPCOM_USE_PYGILSTATE
	// I can't locate a way to kill this -
	// should I pass a dtor to PR_NewThreadPrivateIndex??
	// TlsFree(tlsIndex);
#endif // PYXPCOM_USE_PYGILSTATE
}

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

////////////////////////////////////////////////////////////
// Other helpers/global functions.
//
PRBool PyXPCOM_Globals_Ensure()
{
	PRBool rc = PR_TRUE;

#ifndef PYXPCOM_USE_PYGILSTATE
	PyXPCOM_InterpreterState_Ensure();
#endif

	// The exception object - we load it from .py code!
	if (PyXPCOM_Error == NULL) {
		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);
	}
	if (!rc)
		return rc;

	static PRBool bHaveInitXPCOM = PR_FALSE;
	if (!bHaveInitXPCOM) {
		nsCOMPtr<nsIThread> thread_check;
		// xpcom appears to assert if already initialized
		// Is there an official way to determine this?
		if (NS_FAILED(nsIThread::GetMainThread(getter_AddRefs(thread_check)))) {
			// not already initialized.
#ifdef XP_WIN
			// On Windows, we need to locate the Mozilla bin
			// directory.  This by using locating a Moz DLL we depend
			// on, and assume it lives in that bin dir.  Different
			// moz build types (eg, xulrunner, suite) package
			// XPCOM itself differently - but all appear to require
			// nspr4.dll - so this is what we use.
			char landmark[MAX_PATH+1];
			HMODULE hmod = GetModuleHandle("nspr4.dll");
			if (hmod==NULL) {
				PyErr_SetString(PyExc_RuntimeError, "We dont appear to be linked against nspr4.dll.");
				return PR_FALSE;
			}
			GetModuleFileName(hmod, landmark, sizeof(landmark)/sizeof(landmark[0]));
			char *end = landmark + (strlen(landmark)-1);
			while (end > landmark && *end != '\\')
				end--;
			if (end > landmark) *end = '\0';

			nsCOMPtr<nsILocalFile> ns_bin_dir;
			NS_ConvertASCIItoUCS2 strLandmark(landmark);
			NS_NewLocalFile(strLandmark, PR_FALSE, getter_AddRefs(ns_bin_dir));
			nsresult rv = NS_InitXPCOM2(nsnull, ns_bin_dir, nsnull);
#else
			// Elsewhere, Mozilla can find it itself (we hope!)
			nsresult rv = NS_InitXPCOM2(nsnull, nsnull, nsnull);
#endif // XP_WIN
			if (NS_FAILED(rv)) {
				PyErr_SetString(PyExc_RuntimeError, "The XPCOM subsystem could not be initialized");
				return PR_FALSE;
			}
		}
		// Even if xpcom was already init, we want to flag it as init!
		bHaveInitXPCOM = PR_TRUE;
		// 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();
		// for backward compatibility:
		Py_nsIComponentManagerObsolete::InitType();

	}
	return rc;
}

