/* -*- Mode: C++; tab-width: 4; 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 ***** */


/*
  A stack-based lock object that makes using PRLock a bit more
  convenient. It acquires the monitor when constructed, and releases
  it when it goes out of scope.

  For example,

    class Foo {
    private:
        PRLock* mLock;

    public:
        Foo(void) {
            mLock = PR_NewLock();
        }

        ~Foo(void) {
            PR_DestroyLock(mLock);
        }

        void ThreadSafeMethod(void) {
            // we're don't hold the lock yet...

            nsAutoLock lock(mLock);
            // ...but now we do.

            // we even can do wacky stuff like return from arbitrary places w/o
            // worrying about forgetting to release the lock
            if (some_weird_condition)
                return;

            // otherwise do some other stuff
        }

        void ThreadSafeBlockScope(void) {
            // we're not in the lock here...

            {
                nsAutoLock lock(mLock);
                // but we are now, at least until the block scope closes
            }

            // ...now we're not in the lock anymore
        }
    };

    A similar stack-based locking object is available for PRMonitor.  The 
    major difference is that the PRMonitor must be created and destroyed 
    via the static methods on nsAutoMonitor.

    For example:
    Foo::Foo() {
      mMon =  nsAutoMonitor::NewMonitor("FooMonitor");
    }
    nsresult Foo::MyMethod(...) {
       nsAutoMonitor mon(mMon);
       ...
       // go ahead and do deeply nested returns...
                    return NS_ERROR_FAILURE;
       ...
       // or call Wait or Notify...
       mon.Wait();
       ...
       // cleanup is automatic
    }
 */

#ifndef nsAutoLock_h__
#define nsAutoLock_h__

#include "nscore.h"
#include "prlock.h"
#include "prlog.h"
#include "mozilla/AutoRestore.h"

/**
 * nsAutoLockBase
 * This is the base class for the stack-based locking objects.
 * Clients of derived classes need not play with this superclass.
 **/
class NS_COM_GLUE NS_STACK_CLASS nsAutoLockBase {
    friend class nsAutoUnlockBase;

protected:
    nsAutoLockBase() {}
    enum nsAutoLockType {eAutoLock, eAutoMonitor, eAutoCMonitor};

#ifdef DEBUG
    nsAutoLockBase(void* addr, nsAutoLockType type);
    ~nsAutoLockBase();

    void            Show();
    void            Hide();

    void*           mAddr;
    nsAutoLockBase* mDown;
    nsAutoLockType  mType;
#else
    nsAutoLockBase(void* addr, nsAutoLockType type) {}
    ~nsAutoLockBase() {}

    void            Show() {}
    void            Hide() {}
#endif
};

/**
 * nsAutoUnlockBase
 * This is the base class for stack-based unlocking objects.
 * It unlocks locking objects based on nsAutoLockBase.
 **/
class NS_COM_GLUE NS_STACK_CLASS nsAutoUnlockBase {
protected:
    nsAutoUnlockBase() {}

#ifdef DEBUG
    nsAutoUnlockBase(void* addr);
    ~nsAutoUnlockBase();

    nsAutoLockBase* mLock;
#else
    nsAutoUnlockBase(void* addr) {}
    ~nsAutoUnlockBase() {}
#endif
};

/** 
 * nsAutoLock
 * Stack-based locking object for PRLock.
 **/
class NS_COM_GLUE NS_STACK_CLASS nsAutoLock : public nsAutoLockBase {
private:
    PRLock* mLock;
    PRBool mLocked;
    MOZILLA_DECL_USE_GUARD_OBJECT_NOTIFIER

    // Not meant to be implemented. This makes it a compiler error to
    // construct or assign an nsAutoLock object incorrectly.
    nsAutoLock(void) {}
    nsAutoLock(nsAutoLock& /*aLock*/) {}
    nsAutoLock& operator =(nsAutoLock& /*aLock*/) {
        return *this;
    }

    // Not meant to be implemented. This makes it a compiler error to
    // attempt to create an nsAutoLock object on the heap.
    static void* operator new(size_t /*size*/) CPP_THROW_NEW {
        return nsnull;
    }
    static void operator delete(void* /*memory*/) {}

public:

    /**
     * NewLock
     * Allocates a new PRLock for use with nsAutoLock. name is
     * not checked for uniqueness.
     * @param name A name which can reference this lock
     * @param lock A valid PRLock* that was created by nsAutoLock::NewLock()
     * @returns nsnull if failure
     *          A valid PRLock* if successful, which must be destroyed
     *          by nsAutoLock::DestroyLock()
     **/
    static PRLock* NewLock(const char* name);
    static void    DestroyLock(PRLock* lock);

    /**
     * Constructor
     * The constructor aquires the given lock.  The destructor
     * releases the lock.
     * 
     * @param aLock A valid PRLock* returned from the NSPR's 
     * PR_NewLock() function.
     **/
    nsAutoLock(PRLock* aLock MOZILLA_GUARD_OBJECT_NOTIFIER_PARAM)
        : nsAutoLockBase(aLock, eAutoLock),
          mLock(aLock),
          mLocked(PR_TRUE) {
        MOZILLA_GUARD_OBJECT_NOTIFIER_INIT;
        PR_ASSERT(mLock);

        // This will assert deep in the bowels of NSPR if you attempt
        // to re-enter the lock.
        PR_Lock(mLock);
    }
    
    ~nsAutoLock(void) {
        if (mLocked)
            PR_Unlock(mLock);
    }

    /** 
     * lock
     * Client may call this to reaquire the given lock. Take special
     * note that attempting to aquire a locked lock will hang or crash.
     **/  
    void lock() {
        Show();
        PR_ASSERT(!mLocked);
        PR_Lock(mLock);
        mLocked = PR_TRUE;
    }


    /** 
     * unlock
     * Client may call this to release the given lock. Take special
     * note unlocking an unlocked lock has undefined results.
     **/ 
     void unlock() {
        PR_ASSERT(mLocked);
        PR_Unlock(mLock);
        mLocked = PR_FALSE;
        Hide();
    }
};

class NS_STACK_CLASS nsAutoUnlock : private nsAutoUnlockBase
{
private:
    PRLock *mLock;
    MOZILLA_DECL_USE_GUARD_OBJECT_NOTIFIER
     
public:
    nsAutoUnlock(PRLock *lock MOZILLA_GUARD_OBJECT_NOTIFIER_PARAM) : 
        nsAutoUnlockBase(lock),
        mLock(lock)
    {
        MOZILLA_GUARD_OBJECT_NOTIFIER_INIT;
        PR_Unlock(mLock);
    }

    ~nsAutoUnlock() {
        PR_Lock(mLock);
    }
};

#include "prcmon.h"
#include "nsError.h"
#include "nsDebug.h"

class NS_COM_GLUE NS_STACK_CLASS nsAutoMonitor : public nsAutoLockBase {
public:

    /**
     * NewMonitor
     * Allocates a new PRMonitor for use with nsAutoMonitor.
     * @param name A (unique /be?) name which can reference this monitor
     * @returns nsnull if failure
     *          A valid PRMonitor* is successful while must be destroyed
     *          by nsAutoMonitor::DestroyMonitor()
     **/
    static PRMonitor* NewMonitor(const char* name);
    static void       DestroyMonitor(PRMonitor* mon);

    
    /**
     * Constructor
     * The constructor locks the given monitor.  During destruction
     * the monitor will be unlocked.
     * 
     * @param mon A valid PRMonitor* returned from 
     *        nsAutoMonitor::NewMonitor().
     **/
    nsAutoMonitor(PRMonitor* mon MOZILLA_GUARD_OBJECT_NOTIFIER_PARAM)
        : nsAutoLockBase((void*)mon, eAutoMonitor),
          mMonitor(mon), mLockCount(0)
    {
        MOZILLA_GUARD_OBJECT_NOTIFIER_INIT;
        NS_ASSERTION(mMonitor, "null monitor");
        if (mMonitor) {
            PR_EnterMonitor(mMonitor);
            mLockCount = 1;
        }
    }

    ~nsAutoMonitor() {
        NS_ASSERTION(mMonitor, "null monitor");
        if (mMonitor && mLockCount) {
#ifdef DEBUG
            PRStatus status = 
#endif
            PR_ExitMonitor(mMonitor);
            NS_ASSERTION(status == PR_SUCCESS, "PR_ExitMonitor failed");
        }
    }

    /** 
     * Enter
     * Client may call this to reenter the given monitor.
     * @see prmon.h 
     **/  
    void Enter();

    /** 
     * Exit
     * Client may call this to exit the given monitor.
     * @see prmon.h 
     **/      
    void Exit();

    /** 
     * Wait
     * @see prmon.h 
     **/      
    nsresult Wait(PRIntervalTime interval = PR_INTERVAL_NO_TIMEOUT) {
        return PR_Wait(mMonitor, interval) == PR_SUCCESS
            ? NS_OK : NS_ERROR_FAILURE;
    }

    /** 
     * Notify
     * @see prmon.h 
     **/      
    nsresult Notify() {
        return PR_Notify(mMonitor) == PR_SUCCESS
            ? NS_OK : NS_ERROR_FAILURE;
    }

    /** 
     * NotifyAll
     * @see prmon.h 
     **/      
    nsresult NotifyAll() {
        return PR_NotifyAll(mMonitor) == PR_SUCCESS
            ? NS_OK : NS_ERROR_FAILURE;
    }

private:
    PRMonitor*  mMonitor;
    PRInt32     mLockCount;
    MOZILLA_DECL_USE_GUARD_OBJECT_NOTIFIER

    // Not meant to be implemented. This makes it a compiler error to
    // construct or assign an nsAutoLock object incorrectly.
    nsAutoMonitor(void) {}
    nsAutoMonitor(nsAutoMonitor& /*aMon*/) {}
    nsAutoMonitor& operator =(nsAutoMonitor& /*aMon*/) {
        return *this;
    }

    // Not meant to be implemented. This makes it a compiler error to
    // attempt to create an nsAutoLock object on the heap.
    static void* operator new(size_t /*size*/) CPP_THROW_NEW {
        return nsnull;
    }
    static void operator delete(void* /*memory*/) {}
};

////////////////////////////////////////////////////////////////////////////////
// Once again, this time with a cache...
// (Using this avoids the need to allocate a PRMonitor, which may be useful when
// a large number of objects of the same class need associated monitors.)

#include "prcmon.h"
#include "nsError.h"

class NS_COM_GLUE NS_STACK_CLASS nsAutoCMonitor : public nsAutoLockBase {
public:
    nsAutoCMonitor(void* lockObject MOZILLA_GUARD_OBJECT_NOTIFIER_PARAM)
        : nsAutoLockBase(lockObject, eAutoCMonitor),
          mLockObject(lockObject), mLockCount(0)
    {
        MOZILLA_GUARD_OBJECT_NOTIFIER_INIT;
        NS_ASSERTION(lockObject, "null lock object");
        PR_CEnterMonitor(mLockObject);
        mLockCount = 1;
    }

    ~nsAutoCMonitor() {
        if (mLockCount) {
#ifdef DEBUG
            PRStatus status =
#endif
            PR_CExitMonitor(mLockObject);
            NS_ASSERTION(status == PR_SUCCESS, "PR_CExitMonitor failed");
        }
    }

    void Enter();
    void Exit();

    nsresult Wait(PRIntervalTime interval = PR_INTERVAL_NO_TIMEOUT) {
        return PR_CWait(mLockObject, interval) == PR_SUCCESS
            ? NS_OK : NS_ERROR_FAILURE;
    }

    nsresult Notify() {
        return PR_CNotify(mLockObject) == PR_SUCCESS
            ? NS_OK : NS_ERROR_FAILURE;
    }

    nsresult NotifyAll() {
        return PR_CNotifyAll(mLockObject) == PR_SUCCESS
            ? NS_OK : NS_ERROR_FAILURE;
    }

private:
    void*   mLockObject;
    PRInt32 mLockCount;
    MOZILLA_DECL_USE_GUARD_OBJECT_NOTIFIER

    // Not meant to be implemented. This makes it a compiler error to
    // construct or assign an nsAutoLock object incorrectly.
    nsAutoCMonitor(void) {}
    nsAutoCMonitor(nsAutoCMonitor& /*aMon*/) {}
    nsAutoCMonitor& operator =(nsAutoCMonitor& /*aMon*/) {
        return *this;
    }

    // Not meant to be implemented. This makes it a compiler error to
    // attempt to create an nsAutoLock object on the heap.
    static void* operator new(size_t /*size*/) CPP_THROW_NEW {
        return nsnull;
    }
    static void operator delete(void* /*memory*/) {}
};

#endif // nsAutoLock_h__

