/**
 * @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_MUTEX_H
#define __IWEAR_MUTEX_H

#ifndef __IWEAR_IWEAR_H
#include <iwear/iwear.h>
#endif

#ifndef __USE_GNU
#define __USE_GNU
#endif

#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif

extern "C"
{
#include <pthread.h>
#include <math.h>
#ifndef HAVE_CLOCK_GETTIME
#include <iwear/clock_gettime.h>
#endif
#ifndef HAVE_PTHREAD_MUTEX_TIMEDLOCK
#include <iwear/pthread_mutex_timedlock.h>
#endif
}

#include <iostream>

namespace iwear
{

/**
 * This specifies of what kind the mutex is.
 * Default is timed !!!
 */
enum iw_mutex_type
{
    mutex_normal = PTHREAD_MUTEX_NORMAL,
    mutex_recursive = PTHREAD_MUTEX_RECURSIVE,	///< Recursive mutex, can be locked multiple times
    mutex_errorcheck = PTHREAD_MUTEX_ERRORCHECK,	///< Mutex that checks for errors
    mutex_nolock,					///< This means no locking at all !!!!
    num_iw_mutex_type,
};

/* 
 * on netbsd
#define PTHREAD_MUTEX_NORMAL            0
#define PTHREAD_MUTEX_ERRORCHECK        1
#define PTHREAD_MUTEX_RECURSIVE         2
#define PTHREAD_MUTEX_DEFAULT           PTHREAD_MUTEX_NORMAL
#define PTHREAD_MUTEX_INITIALIZER       _PTHREAD_MUTEX_INITIALIZER
*/
class Conditional;
/**
 * From Manpage :
 * pthread_mutex_destroy  destroys  a mutex object, freeing the resources it
 * might hold. The mutex must be unlocked on entrance. In  the  LinuxThreads
 * implementation,  no  resources  are  associated  with  mutex  objects,  thus
 * pthread_mutex_destroy actually does nothing except checking that the mutex
 * is unlocked.
 * So : Keep in mind that on another system resources might be associated with
 * the mutex and are not freed if the destroy fails !! --> Memory leak
 */
class IWAPIT Mutex/*{{{*/
{
friend class Conditional;
protected:
    pthread_mutex_t mutex;
    
    iw_mutex_type MType;

    pthread_mutexattr_t pattr;

public:
    Mutex( );
//    Mutex( iw_mutex_type mtype = mutex_recursive );
    ~Mutex();
//    int Locked( void );

    /**
     * You *must* always check for the return of the lock, since it might be
     * one kind that can come back with an error code and then the resource is
     * not locked !!
     */
    int Lock( void );

    /**
     * Try to lock a mutex.
     * @return
     * - 0 if the mutex could be locked properly.
     * - EBUSY in case the mutex is already locked. In this case you should do
     *   something else
     */
    int TryLock( void );

    /**
     * Although I dont have any advice what to do in case this function returns
     * an error you should check for it.
     */
    int Unlock( void );

    int TimedLock( float to );
};/*}}}*/


inline Mutex::Mutex( /*iw_mutex_type mtype*/ )/*{{{*/
   /* : MType(mtype)*/
{
    MType = mutex_recursive;


//    cout <<  "CREATING MUTEX OF TYPE " << MType << endl;
    if ( MType != mutex_nolock )
    {
//        printf("On line %d\n", __LINE__);
	pthread_mutexattr_t pattr;
	union {
	int (*fp)( pthread_mutexattr_t* );
	void* dp;
	} pmi;

	pmi.fp = pthread_mutexattr_init;
//        printf("On line %d\n", __LINE__);
//	printf("Function is at 0x%p\n",pmi.dp);
	if( pmi.dp == 0 )
	{
	    printf("Linker failure, pthread symbols not relocated, aborting\n");
	    abort();
	}
	pthread_mutexattr_init(&pattr);
//        printf("On line %d\n", __LINE__);
//	pthread_mutexattr_settype(&pattr,mtype);
	pthread_mutexattr_settype(&pattr,mutex_recursive);
	pthread_mutex_init(&mutex,&pattr);
	/*
	cout << "Mutex Create" << endl;
	cout << "LINE" << __LINE__ << endl;
	Lock();
	cout << "LINE" << __LINE__ << endl;
	Lock();
	cout << "LINE" << __LINE__ << endl;
	Unlock();
	cout << "LINE" << __LINE__ << endl;
	Unlock();
	cout << "LINE" << __LINE__ << endl;
	cout << "CREATED MUTEX " << (void*)&Mutex << endl;
	*/
    }
    else
    {
//	pthread_mutex_init(&Mutex,NULL);
    }
}/*}}}*/

inline Mutex::~Mutex( )/*{{{*/
{
    if ( MType != mutex_nolock )
    {
	pthread_mutex_lock(&mutex);
	pthread_mutex_unlock(&mutex);
	pthread_mutex_destroy(&mutex);
    }
}/*}}}*/

inline int Mutex::TimedLock( float s )/*{{{*/
{
    if( MType != mutex_nolock )
    {
	struct timespec timeout;

	clock_gettime( CLOCK_REALTIME, &timeout );
	timeout.tv_sec += static_cast<long int>(floor(s));
	timeout.tv_nsec += static_cast<long int>((s-floor(s)) * 1000000000.0);
	while( timeout.tv_nsec > 1000000000 )
	{
	    timeout.tv_nsec -= 1000000000;
	    timeout.tv_sec += 1;
	}
	return pthread_mutex_timedlock(&mutex,&timeout);
    }
    else
    {
	return 0;
    }
}/*}}}*/

inline int Mutex::Lock( void )/*{{{*/
{
//    cout << "Mutex " << (void*)this << " should be locked by thread " << pthread_self() << endl;
    if ( MType != mutex_nolock )
    {
//	cout << "Thread " << pthread_self() << " locks " << (void*)&Mutex << endl;
	int ret = pthread_mutex_lock(&mutex);
//	cout << "Mutex " << (void*)this << " is now locked by thread " << pthread_self() << "( ret =" <<ret << " )" <<  endl;
	return ret;
    }
    else
    {
	return 0;
    }
}/*}}}*/

inline int Mutex::Unlock( void )/*{{{*/
{
//    cout << "Mutex " << (void*)this << " should be unlocked by thread " << pthread_self() << endl;
    if ( MType != mutex_nolock )
    {
//	cout << "Thread " << pthread_self() << " unlocks " << (void*)&Mutex << endl;
	int ret = pthread_mutex_unlock(&mutex);
//	cout << "Mutex " << (void*)this << " is now unlocked by thread " << pthread_self() << "( ret =" <<ret << " )" <<  endl;
	return ret;
    }
    else
    {
	return 0;
    }
}/*}}}*/

inline int Mutex::TryLock( void )/*{{{*/
{
    if ( MType != mutex_nolock )
    {
	return pthread_mutex_trylock(&mutex);
    }
    else
    {
	return 0;
    }
}/*}}}*/

}

#endif
