/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/* ***** 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 mozilla.org code.
 *
 * The Initial Developer of the Original Code is
 * Netscape Communications Corporation.
 * Portions created by the Initial Developer are Copyright (C) 1998
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either of 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 ***** */

#include "nsThread.h"
#include "prmem.h"
#include "prlog.h"
#include "nsAutoLock.h"

PRUintn nsThread::kIThreadSelfIndex = 0;
static nsIThread *gMainThread = 0;

#if defined(PR_LOGGING)
//
// Log module for nsIThread logging...
//
// To enable logging (see prlog.h for full details):
//
//    set NSPR_LOG_MODULES=nsIThread:5
//    set NSPR_LOG_FILE=nspr.log
//
// this enables PR_LOG_DEBUG level information and places all output in
// the file nspr.log
//
// gSocketLog is defined in nsSocketTransport.cpp
//
PRLogModuleInfo* nsIThreadLog = nsnull;

#endif /* PR_LOGGING */

////////////////////////////////////////////////////////////////////////////////

nsThread::nsThread()
    : mThread(nsnull), mDead(PR_FALSE), mStartLock(nsnull)
{
#if defined(PR_LOGGING)
    //
    // Initialize the global PRLogModule for nsIThread logging 
    // if necessary...
    //
    if (nsIThreadLog == nsnull) {
        nsIThreadLog = PR_NewLogModule("nsIThread");
    }
#endif /* PR_LOGGING */

    // enforce matching of constants to enums in prthread.h
    NS_ASSERTION(int(nsIThread::PRIORITY_LOW)     == int(PR_PRIORITY_LOW) &&
                 int(nsIThread::PRIORITY_NORMAL)  == int(PRIORITY_NORMAL) &&
                 int(nsIThread::PRIORITY_HIGH)    == int(PRIORITY_HIGH) &&
                 int(nsIThread::PRIORITY_URGENT)  == int(PRIORITY_URGENT) &&
                 int(nsIThread::SCOPE_LOCAL)      == int(PR_LOCAL_THREAD) &&
                 int(nsIThread::SCOPE_GLOBAL)     == int(PR_GLOBAL_THREAD) &&
                 int(nsIThread::STATE_JOINABLE)   == int(PR_JOINABLE_THREAD) &&
                 int(nsIThread::STATE_UNJOINABLE) == int(PR_UNJOINABLE_THREAD),
                 "Bad constant in nsIThread!");
}

nsThread::~nsThread()
{
    if (mStartLock)
        PR_DestroyLock(mStartLock);

    PR_LOG(nsIThreadLog, PR_LOG_DEBUG,
           ("nsIThread %p destroyed\n", this));

    // This code used to free the nsIThreadLog loginfo stuff
    // Don't do that; loginfo structures are owned by nspr
    // and would be freed if we ever called PR_Cleanup()
    // see bug 142072
}

void
nsThread::Main(void* arg)
{
    nsThread* self = (nsThread*)arg;

    self->WaitUntilReadyToStartMain();

    nsresult rv = NS_OK;
    rv = self->RegisterThreadSelf();
    NS_ASSERTION(rv == NS_OK, "failed to set thread self");

    PR_LOG(nsIThreadLog, PR_LOG_DEBUG,
           ("nsIThread %p start run %p\n", self, self->mRunnable.get()));
    rv = self->mRunnable->Run();
    NS_ASSERTION(NS_SUCCEEDED(rv), "runnable failed");

#ifdef DEBUG
    // Because a thread can die after gMainThread dies and takes nsIThreadLog with it,
    // we need to check for it being null so that we don't crash on shutdown.
    if (nsIThreadLog) {
        PRThreadState state;
        rv = self->GetState(&state);
        PR_LOG(nsIThreadLog, PR_LOG_DEBUG,
               ("nsIThread %p end run %p\n", self, self->mRunnable.get()));
    }
#endif

    // explicitly drop the runnable now in case there are circular references
    // between it and the thread object
    self->mRunnable = nsnull;
}

void
nsThread::Exit(void* arg)
{
    nsThread* self = (nsThread*)arg;

    if (self->mDead) {
        NS_ERROR("attempt to Exit() thread twice");
        return;
    }

    self->mDead = PR_TRUE;

#if defined(PR_LOGGING)
    if (nsIThreadLog) {
        PR_LOG(nsIThreadLog, PR_LOG_DEBUG,
               ("nsIThread %p exited\n", self));
    }
#endif
    NS_RELEASE(self);
}

NS_METHOD
nsThread::Create(nsISupports *aOuter, REFNSIID aIID, void **aResult)
{
    nsThread* thread = new nsThread();
    if (!thread) return NS_ERROR_OUT_OF_MEMORY;
    nsresult rv = thread->QueryInterface(aIID, aResult);
    if (NS_FAILED(rv)) delete thread;
    return rv;
}

NS_IMPL_THREADSAFE_ISUPPORTS1(nsThread, nsIThread)

NS_IMETHODIMP
nsThread::Join()
{
    // don't check for mDead here because nspr calls Exit (cleaning up
    // thread-local storage) before they let us join with the thread

    PR_LOG(nsIThreadLog, PR_LOG_DEBUG,
           ("nsIThread %p start join\n", this));
    if (!mThread)
        return NS_ERROR_NOT_INITIALIZED;
    PRStatus status = PR_JoinThread(mThread);
    // XXX can't use NS_RELEASE here because the macro wants to set
    // this to null (bad c++)
    PR_LOG(nsIThreadLog, PR_LOG_DEBUG,
           ("nsIThread %p end join\n", this));
    if (status != PR_SUCCESS)
        return NS_ERROR_FAILURE;

    NS_RELEASE_THIS();   // most likely the final release of this thread 
    return NS_OK;
}

NS_IMETHODIMP
nsThread::GetPriority(PRThreadPriority *result)
{
    if (mDead)
        return NS_ERROR_FAILURE;
    if (!mThread)
        return NS_ERROR_NOT_INITIALIZED;
    *result = PR_GetThreadPriority(mThread);
    return NS_OK;
}

NS_IMETHODIMP
nsThread::SetPriority(PRThreadPriority value)
{
    if (mDead)
        return NS_ERROR_FAILURE;
    if (!mThread)
        return NS_ERROR_NOT_INITIALIZED;
    PR_SetThreadPriority(mThread, value);
    return NS_OK;
}

NS_IMETHODIMP
nsThread::Interrupt()
{
    if (mDead)
        return NS_ERROR_FAILURE;
    if (!mThread)
        return NS_ERROR_NOT_INITIALIZED;
    PRStatus status = PR_Interrupt(mThread);
    return status == PR_SUCCESS ? NS_OK : NS_ERROR_FAILURE;
}

NS_IMETHODIMP
nsThread::GetScope(PRThreadScope *result)
{
    if (mDead)
        return NS_ERROR_FAILURE;
    if (!mThread)
        return NS_ERROR_NOT_INITIALIZED;
    *result = PR_GetThreadScope(mThread);
    return NS_OK;
}

NS_IMETHODIMP
nsThread::GetState(PRThreadState *result)
{
    if (mDead)
        return NS_ERROR_FAILURE;
    if (!mThread)
        return NS_ERROR_NOT_INITIALIZED;
    *result = PR_GetThreadState(mThread);
    return NS_OK;
}

NS_IMETHODIMP
nsThread::GetPRThread(PRThread* *result)
{
    if (mDead) {
        *result = nsnull;
        return NS_ERROR_FAILURE;
    }    
    *result = mThread;
    return NS_OK;
}

NS_IMETHODIMP
nsThread::Init(nsIRunnable* runnable,
               PRUint32 stackSize,
               PRThreadPriority priority,
               PRThreadScope scope,
               PRThreadState state)
{
    NS_ENSURE_ARG_POINTER(runnable);
    if (mRunnable)
        return NS_ERROR_ALREADY_INITIALIZED;

    mRunnable = runnable;

    if (mStartLock)
        return NS_ERROR_ALREADY_INITIALIZED;

    mStartLock = PR_NewLock();
    if (mStartLock == nsnull) {
        mRunnable = nsnull;
        return NS_ERROR_OUT_OF_MEMORY;
    }

    NS_ADDREF_THIS();       // released in nsThread::Exit
    if (state == PR_JOINABLE_THREAD)
        NS_ADDREF_THIS();   // released in nsThread::Join

    PR_Lock(mStartLock);
    mDead = PR_FALSE;
    mThread = PR_CreateThread(PR_USER_THREAD, Main, this,
                              priority, scope, state, stackSize);
    /* As soon as we PR_Unlock(mStartLock), if mThread was successfully 
     * created, it could run and exit very quickly. In which case, it 
     * would null mThread and therefore if we check if (mThread) we could 
     * confuse a successfully created, yet already exited thread with
     * OOM - failure to create the thread. So instead we store a local thr 
     * which we check to see if we really failed to create the thread.
     */
    PRThread *thr = mThread;
    PR_Unlock(mStartLock);

    if (thr == nsnull) {
        mDead = PR_TRUE;         // otherwise cleared in nsThread::Exit
        mRunnable = nsnull;      // otherwise cleared in nsThread::Main(when done)
        PR_DestroyLock(mStartLock);
        mStartLock = nsnull;
        NS_RELEASE_THIS();       // otherwise released in nsThread::Exit
        if (state == PR_JOINABLE_THREAD)
            NS_RELEASE_THIS();   // otherwise released in nsThread::Join
        return NS_ERROR_OUT_OF_MEMORY;
    }

    PR_LOG(nsIThreadLog, PR_LOG_DEBUG,
           ("nsIThread %p created\n", this));

    return NS_OK;
}

/* readonly attribute nsIThread currentThread; */
NS_IMETHODIMP 
nsThread::GetCurrentThread(nsIThread * *aCurrentThread)
{
    return GetIThread(PR_GetCurrentThread(), aCurrentThread);
}

/* void sleep (in PRUint32 msec); */
NS_IMETHODIMP 
nsThread::Sleep(PRUint32 msec)
{
    if (PR_GetCurrentThread() != mThread)
        return NS_ERROR_FAILURE;
    
    if (PR_Sleep(PR_MillisecondsToInterval(msec)) != PR_SUCCESS)
        return NS_ERROR_FAILURE;

    return NS_OK;
}

NS_COM nsresult
NS_NewThread(nsIThread* *result, 
             nsIRunnable* runnable,
             PRUint32 stackSize,
             PRThreadState state,
             PRThreadPriority priority,
             PRThreadScope scope)
{
    nsresult rv;
    nsThread* thread = new nsThread();
    if (thread == nsnull)
        return NS_ERROR_OUT_OF_MEMORY;
    NS_ADDREF(thread);

    rv = thread->Init(runnable, stackSize, priority, scope, state);
    if (NS_FAILED(rv)) {
        NS_RELEASE(thread);
        return rv;
    }

    *result = thread;
    return NS_OK;
}

NS_COM nsresult
NS_NewThread(nsIThread* *result, 
             PRUint32 stackSize,
             PRThreadState state,
             PRThreadPriority priority,
             PRThreadScope scope)
{
    nsThread* thread = new nsThread();
    if (thread == nsnull)
        return NS_ERROR_OUT_OF_MEMORY;
    NS_ADDREF(thread);
    *result = thread;
    return NS_OK;
}

////////////////////////////////////////////////////////////////////////////////

nsresult
nsThread::RegisterThreadSelf()
{
    PRStatus status;

    if (kIThreadSelfIndex == 0) {
        status = PR_NewThreadPrivateIndex(&kIThreadSelfIndex, Exit);
        if (status != PR_SUCCESS) return NS_ERROR_FAILURE;
    }

    status = PR_SetThreadPrivate(kIThreadSelfIndex, this);
    if (status != PR_SUCCESS) return NS_ERROR_FAILURE;

    return NS_OK;
}

void 
nsThread::WaitUntilReadyToStartMain()
{
    PR_Lock(mStartLock);
    PR_Unlock(mStartLock);
    PR_DestroyLock(mStartLock);
    mStartLock = nsnull;
}

NS_COM nsresult
nsIThread::GetCurrent(nsIThread* *result)
{
    return GetIThread(PR_GetCurrentThread(), result);
}

NS_COM nsresult
nsIThread::GetIThread(PRThread* prthread, nsIThread* *result)
{
    PRStatus status;
    nsThread* thread;

    if (nsThread::kIThreadSelfIndex == 0) {
        status = PR_NewThreadPrivateIndex(&nsThread::kIThreadSelfIndex, nsThread::Exit);
        if (status != PR_SUCCESS) return NS_ERROR_FAILURE;
    }

    thread = (nsThread*)PR_GetThreadPrivate(nsThread::kIThreadSelfIndex);
    if (thread == nsnull) {
        // if the current thread doesn't have an nsIThread associated
        // with it, make one
        thread = new nsThread();
        if (thread == nsnull)
            return NS_ERROR_OUT_OF_MEMORY;
        NS_ADDREF(thread);      // released by Exit
        thread->SetPRThread(prthread);
        nsresult rv = thread->RegisterThreadSelf();
        if (NS_FAILED(rv)) return rv;
    }
    NS_ADDREF(thread);
    *result = thread;
    return NS_OK;
}

NS_COM nsresult
nsIThread::SetMainThread()
{
    // strictly speaking, it could be set twice. but practically speaking,
    // it's almost certainly an error if it is
    if (gMainThread != 0) {
        NS_ERROR("Setting main thread twice?");
        return NS_ERROR_FAILURE;
    }
    return GetCurrent(&gMainThread);
}

NS_COM nsresult
nsIThread::GetMainThread(nsIThread **result)
{
    NS_ASSERTION(result, "bad result pointer");
    if (gMainThread == 0)
        return NS_ERROR_FAILURE;
    *result = gMainThread;
    NS_ADDREF(gMainThread);
    return NS_OK;
}

NS_COM PRBool
nsIThread::IsMainThread()
{
    if (gMainThread == 0)
        return PR_TRUE;
    
    PRThread *theMainThread;
    gMainThread->GetPRThread(&theMainThread);
    return theMainThread == PR_GetCurrentThread();
}

void 
nsThread::Shutdown()
{
    if (gMainThread) {
        // In most recent versions of NSPR the main thread's destructor
        // callback will get called.
        // In older versions of NSPR it will not get called,
        // (unless we call PR_Cleanup).
        // Because of that we:
        // - call the function ourselves
        // - set the data pointer to NULL to ensure the function will
        //   not get called again by NSPR
        // The PR_SetThreadPrivate call does both of these.
        // See also bugs 379550, 362768.
        PR_SetThreadPrivate(kIThreadSelfIndex, NULL);
        nsrefcnt cnt;
        NS_RELEASE2(gMainThread, cnt);
        NS_WARN_IF_FALSE(cnt == 0, "Main thread being held past XPCOM shutdown.");
        gMainThread = nsnull;
        
        kIThreadSelfIndex = 0;
    }
}

////////////////////////////////////////////////////////////////////////////////
