/**
 * @file
 * $Id$
 * $Revision$
 * $Author$
 * $Date$
 *
 * This file is part of The iWear Framework.
 *
 * The iWear Framework is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by the
 * Free Software Foundation as in version 2 of the License.

 * 
 * The iWear Framework is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
 * more details.
 * 
 * You should have received a copy of the GNU General Public License along with
 * The iWear Framework; if not, write to the Free Software Foundation, Inc., 59
 * Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

#ifndef __IWEAR_THREADLOCKER_H
#define __IWEAR_THREADLOCKER_H

#ifndef __IWEAR_IWMUTEX_H
#include <iwear/iwmutex.h>
#endif

#ifndef __IWEAR_EXCEPTIONS_H
#include <iwear/exceptions.h>
#endif

namespace iwear
{

/**
 * What is this class good for ? Couldn you just lock and unlock a mutex by
 * urself ? Shure you can, and often its usefull to do it, but in some cases
 * not. Imagine you have a function where you lock at the beginning and would
 * have to unlock at multiple places since you exit the function there. Usually
 * you would need to unlock there all by urself, but with this class, the
 * compile-time-magic does it for you. The ThreadLocker object locks at
 * creation time, and at destruction time it automatically unlocks, so when u
 * exit the function, the object goes out of scope and will be destructed then.
 * So, you should never use new for creation of this object and of course no
 * garbage collection stuff ;)
 */
class IWAPIT ThreadLocker
{
protected:
    Mutex* mutex;

    void lock( bool locked );

    void lock( float to, bool locked );

public:
    void* operator new(std::size_t) { throw std::bad_alloc(); }
    void* operator new(std::size_t, const std::nothrow_t&) throw() { return NULL; }
    /**
     * This constructor also locks the mutex which pointer is passed.
     * @param locked is to be set to true if the mutex already has been locked
     * internally and the unlock is only for making shure it isnt left locked
     */
    ThreadLocker( Mutex* _mutex, bool locked = false );

    ThreadLocker( Mutex* _mutex, float to, bool locked = false );
    /**
     * This constructor also locks the mutex which pointer is passed.
     * @param locked is to be set to true if the mutex already has been locked
     * internally and the unlock is only for making shure it isnt left locked
     */
    ThreadLocker( Mutex& _mutex, bool locked = false );

    ThreadLocker( Mutex& _mutex, float to, bool locked = false );
    /**
     * The destructor unlocks the mutex, so when created on stack and this goes
     * out of scope its automagically unlocked.
     */
    ~ThreadLocker();
};

inline ThreadLocker::ThreadLocker( Mutex& _mutex, float to, bool locked )
	: mutex(&_mutex)
{
    lock(to,locked);
}

inline ThreadLocker::ThreadLocker( Mutex* _mutex, float to, bool locked )
	: mutex(_mutex)
{
    lock(to,locked);
}

inline ThreadLocker::ThreadLocker( Mutex& _mutex, bool locked ) 
	: mutex(&_mutex)
{
    lock(locked);
}

inline ThreadLocker::ThreadLocker( Mutex* _mutex, bool locked ) 
	: mutex(_mutex)
{
    lock(locked);
}

inline void ThreadLocker::lock( float to, bool locked )
{
    if( !locked )
    {
	int err;
	if(mutex && (err = mutex->TimedLock(to)) )
	{
	    switch(err)
	    {
		case ETIMEDOUT:
		    THROW( timeout_error,("Could not lock mutex",to));
		    break;
		case EDEADLK:
		    mutex = NULL;
		    break;
		default:
		    THROW( thread_error,("Could not Lock Mutex",err));
	    }
	}
    }
}

inline void ThreadLocker::lock( bool locked )
{
    if( !locked )
    {
	int err;
	if(mutex && (err = mutex->Lock()) )
	{
	    if ( err == EDEADLK ) 
		mutex = NULL;
	    else
		THROW( thread_error,("Could not Lock Mutex",err));
	}
    }
}

inline ThreadLocker::~ThreadLocker( )
{
    if(mutex) mutex->Unlock();
}

}

#endif
/**
 */
