#ifndef THREADABLE_H
#define THREADABLE_H 1

/*
 * Generic class for something readable by a threaded assistant
 */

#include <pthread.h>

template <class T> class Threader {

  private:
    bool threaded_;		// is a separate reader thread running?
    pthread_t th_;
    pthread_mutex_t mut_;	// lock on shared data
    char *name_;

  public:

    Threader() : threaded_(false), name_(NULL)
    {
	pthread_mutex_init( &mut_, NULL );
    }

    class Thunk {
	void *(T::*func_)(void);
	T *inst_;

	// private constructor -- thanks to Matt Hall
	Thunk( T *inst, void *(T::*func)() ) : inst_(inst), func_(func)
	{ }

      public:
	static Thunk *make( T *inst, void *(T::*func)(void) ) {
	    return new Thunk( inst, func );
	}

	static void *call( Thunk *deleteme ) {
	    Thunk t = *deleteme;
	    delete deleteme;
	    return ((t.inst_)->*(t.func_))();
	}
    };

  public:

    void start( T *inst, void *(T::*func)(void) ) {

	threaded_ = true;

	Thunk *g = Thunk::make( inst, func );
	pthread_create( &th_, NULL, (void *(*)(void *))&g->call, (void *)g );
    }

    pthread_mutex_t &mutex() { return mut_; }

    bool threading() const { return threaded_; }

    void lock() {
	if(threaded_) {
	    if(pthread_mutex_lock( &mut_ ) != 0) {
		fprintf(stderr, "%s->lock(): %s\n",
			name_ ? name_ : "Threader", strerror(errno));
	    }
	}
    }

    void unlock() {
	if(threaded_) {
	    if(pthread_mutex_unlock( &mut_ ) != 0) {
		fprintf(stderr, "%s->unlock(): %s\n",
			name_ ? name_ : "Threader", strerror(errno));
	    }
	}
    }
    void testcancel() {
	if(threaded_) pthread_testcancel();
    }

    void cancel() {
	if(threaded_) {
	    if(pthread_cancel( th_ ) != 0) {
		fprintf(stderr, "%s->cancel(0x%lx): %s\n",
			name_ ? name_ : "Threadable",
			(long int)th_, strerror(errno));
	    }
	}
    }

};

// Maybe convenient for above?
//  { scoped_lock lock(threader.mutex());
//    mess with threader's contents
//  }

class scoped_lock {
    pthread_mutex_t *m_;

  public:
    scoped_lock( pthread_mutex_t &mut ) {
	m_ = &mut;
	pthread_mutex_lock( m_ );
    }

    ~scoped_lock() {
	pthread_mutex_unlock( m_ );
    }
};

#endif /*THREADABLE_H*/