//
//  Copyright (C) 2009-2012 Artyom Beilis (Tonkikh)
//
//  Distributed under the Boost Software License, Version 1.0. (See
//  accompanying file LICENSE_1_0.txt or copy at
//  http://www.boost.org/LICENSE_1_0.txt)
//
#define BOOSTER_SOURCE
#include <pthread.h>
#include <booster/thread.h>
#include <booster/system_error.h>
#include <errno.h>
#include <string.h>
#include <memory>
#include <vector>
#include <stack>

#ifdef BOOSTER_POSIX
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#endif

#ifdef BOOSTER_WIN32
#  ifndef NOMINMAX
#    define NOMINMAX
#  endif
#  include <windows.h>
#else
#  include <unistd.h>
#endif

namespace booster {

	typedef function<void()> thread_function_type;

	struct thread::data {
		pthread_t p;
		bool released;
		data() : released(false) {}
	};

	extern "C" void *booster_thread_func(void *p)
	{
		std::auto_ptr<thread_function_type> caller(reinterpret_cast<thread_function_type *>(p));
		try {
			thread_function_type &func = *caller;
			func();
		}
		catch(std::exception const &/*e*/) {
			/// TODO
		}
		catch(...) {
			/// TODO
		}
		return 0;
	}

	thread::thread(function<void()> const &cb) :
		d(new thread::data)
	{
		thread_function_type *ptr = new thread_function_type(cb);
		if( ::pthread_create(&d->p,0,booster_thread_func,reinterpret_cast<void *>(ptr)) == 0 ) {
			// passed pointer - ownership lost
			ptr = 0;
			return;
		}
		else {
			// failed to create - delete the object
			int err = errno;
			delete ptr;
			ptr = 0;
			throw system::system_error(	err,
							system::system_category,
							"booster::thread: failed to create a thread");
		}
	}
	thread::~thread()
	{
		detach();
	}
	void thread::join()
	{
		if(!d->released) {
			pthread_join(d->p,0);
			d->released = true;
		}
	}
	void thread::detach()
	{
		if(!d->released) {
			pthread_detach(d->p);
			d->released = true;
		}
	}

	unsigned thread::hardware_concurrency()
	{
		#ifdef BOOSTER_WIN32
			SYSTEM_INFO info=SYSTEM_INFO();
			GetSystemInfo(&info);
			return info.dwNumberOfProcessors;
		#else
			#ifdef _SC_NPROCESSORS_ONLN
				long procs = sysconf(_SC_NPROCESSORS_ONLN);
				if(procs < 0)
					return 0;
				return procs;
			#else
				return 0;
			#endif
		#endif
	}

	struct mutex::data { pthread_mutex_t m; };

	mutex::mutex() : d(new data)
	{
		pthread_mutex_init(&d->m,0);
	}
	mutex::~mutex()
	{
		pthread_mutex_destroy(&d->m);
	}
	void mutex::lock() { pthread_mutex_lock(&d->m); }
	void mutex::unlock() { pthread_mutex_unlock(&d->m); }

	struct recursive_mutex::data { pthread_mutex_t m; };

	recursive_mutex::recursive_mutex() : d(new data)
	{
		pthread_mutexattr_t a;
		pthread_mutexattr_init(&a);
		pthread_mutexattr_settype(&a,PTHREAD_MUTEX_RECURSIVE);
		pthread_mutex_init(&d->m,&a);
	}
	recursive_mutex::~recursive_mutex()
	{
		pthread_mutex_destroy(&d->m);
	}
	void recursive_mutex::lock() { pthread_mutex_lock(&d->m); }
	void recursive_mutex::unlock() { pthread_mutex_unlock(&d->m); }

	struct shared_mutex::data { pthread_rwlock_t m; };
	shared_mutex::shared_mutex() : d(new data)
	{
		pthread_rwlock_init(&d->m,0);
	}
	shared_mutex::~shared_mutex()
	{
		pthread_rwlock_destroy(&d->m);
	}
	void shared_mutex::shared_lock() { pthread_rwlock_rdlock(&d->m); }
	void shared_mutex::unique_lock() { pthread_rwlock_wrlock(&d->m); }
	void shared_mutex::unlock() { pthread_rwlock_unlock(&d->m); }

	#if !defined(__APPLE__) && !defined(__NetBSD__)
	//
	// This is normal implementation
	//
	// Same as shared mutex under "Most platforms"
	//
	struct recursive_shared_mutex::data { pthread_rwlock_t m; };
	recursive_shared_mutex::recursive_shared_mutex() : d(new data)
	{
		pthread_rwlock_init(&d->m,0);
	}
	recursive_shared_mutex::~recursive_shared_mutex()
	{
		pthread_rwlock_destroy(&d->m);
	}
	void recursive_shared_mutex::shared_lock() { pthread_rwlock_rdlock(&d->m); }
	void recursive_shared_mutex::unique_lock() { pthread_rwlock_wrlock(&d->m); }
	void recursive_shared_mutex::unlock() { pthread_rwlock_unlock(&d->m); }


	#else // Darwin has broken recursive RW Lock
	
	struct recursive_shared_mutex::data {
		thread_specific_ptr<int> k;
		pthread_rwlock_t m;
	};

	namespace {
		int &specific_key(thread_specific_ptr<int> &k)
		{
			int *counter = k.get();
			if(!counter) {
				counter = new int(0);
				k.reset(counter);
			}
			return *counter;
		}
	}

	recursive_shared_mutex::recursive_shared_mutex() : d(new data)
	{
		pthread_rwlock_init(&d->m,0);
	}
	recursive_shared_mutex::~recursive_shared_mutex()
	{
		pthread_rwlock_destroy(&d->m);
	}
	void recursive_shared_mutex::shared_lock()
	{
		int &counter = specific_key(d->k);
		if(counter++ == 0)
			pthread_rwlock_rdlock(&d->m);
	}
	void recursive_shared_mutex::unique_lock() {
		pthread_rwlock_wrlock(&d->m); 
	}
	void recursive_shared_mutex::unlock() { 
		int &counter = specific_key(d->k);
		if(counter > 1) {
			counter --;
			return;
		}
		counter = 0;
		pthread_rwlock_unlock(&d->m);
	}

	#endif

	struct condition_variable::data { pthread_cond_t c; };

	condition_variable::condition_variable() : d(new data)
	{
		pthread_cond_init(&d->c,0);
	}
	condition_variable::~condition_variable()
	{
		pthread_cond_destroy(&d->c);
	}
	void condition_variable::notify_one()
	{
		pthread_cond_signal(&d->c);
	}
	void condition_variable::notify_all()
	{
		pthread_cond_broadcast(&d->c);
	}
	void condition_variable::wait(unique_lock<mutex> &m)
	{
		pthread_cond_wait(&d->c,&(m.mutex()->d->m));
	}


#if !defined(__NetBSD__) 
	//
	// Standard implementation pthread_key_t per object
	//
	namespace details {
		extern "C" {
			static void booster_pthread_key_destroyer(void *p)
			{
				if(!p)
					return;
				delete static_cast<tls_object*>(p);
			}
		}
		
		class pthread_key : public key {
		public:
			pthread_key(void (*d)(void *)) : key(d)
			{
				if(pthread_key_create(&key_,booster_pthread_key_destroyer) != 0) {
					throw system::system_error(errno,system::system_category,
								   "Failed to create thread specific key");
				}
			}
			virtual ~pthread_key()
			{
				pthread_key_delete(key_);
			}
			tls_object *get_object()
			{
				void *p=pthread_getspecific(key_);
				if(p)
					return static_cast<tls_object*>(p);
				tls_object *res = new tls_object(intrusive_ptr<key>(this));
				pthread_setspecific(key_,static_cast<void*>(res));
				return res;
			}
		private:
			pthread_key_t key_;
		};

		intrusive_ptr<key> make_key(void (*dtor)(void *))
		{
			return new pthread_key(dtor);
		}
	} // details
#else // workaround
	//
	// Workaround of failure to allocate and release keys or if there only few pthread_keys avalible
	// 
	// NetBSD
	//
	namespace details {
		typedef std::vector<tls_object *> tls_vector;
		extern "C" {
			static void booster_init_mgr_instance();
			static void booster_detail_keys_deleter(void *ptr)
			{
				if(!ptr)
					return;
				tls_vector *v=static_cast<tls_vector *>(ptr);
				if(!v)
					return;
				for(size_t i=0;i<v->size();i++)
					if((*v)[i])
						delete (*v)[i];
				delete v;
			}
		}

		class keys_manager {
		public:
			typedef booster::unique_lock<booster::mutex> guard_type;
			static keys_manager &instance()
			{
				static pthread_once_t once = PTHREAD_ONCE_INIT;
				pthread_once(&once,booster_init_mgr_instance);
				return *instance_ptr;
			}
			keys_manager() : key_max_(0)
			{
				if(pthread_key_create(&key_,booster_detail_keys_deleter)!=0) {
					throw system::system_error(errno,system::system_category,
						   "Failed to create thread specific key");
				}
			}
			int allocate_key()
			{
				int key;
				guard_type g(lock_);
				if(reuse_pool_.empty()) {
					key = key_max_++;
				}
				else {
					key = reuse_pool_.top();
					reuse_pool_.pop();
				}
				return key;
			}
			void release_key(int key)
			{
				try {
					guard_type g(lock_);
					reuse_pool_.push(key);
				}
				catch(...) {}
			}
			
			tls_vector &get_tls_vector()
			{
				void *p=pthread_getspecific(key_);
				if(!p) {
					tls_vector *v = new tls_vector();
					pthread_setspecific(key_,v);
					return *v;
				}
				return *static_cast<tls_vector *>(p);

			}
			static keys_manager *instance_ptr;
		private:
			booster::mutex lock_;
			int key_max_;
			std::stack<int> reuse_pool_;
			pthread_key_t key_;
			
		};

		keys_manager *keys_manager::instance_ptr;

		extern "C" {
			static void booster_init_mgr_instance()
			{
				static keys_manager mgr;
				keys_manager::instance_ptr = &mgr;
			}
		}

		class unlimited_key : public key {
		public:
			unlimited_key(void (*d)(void *)) : key(d)
			{
				id_ = keys_manager::instance().allocate_key();
			}
			virtual ~unlimited_key()
			{
				keys_manager::instance().release_key(id_);
			}
			tls_object *get_object()
			{
				std::vector<tls_object *> &v=keys_manager::instance().get_tls_vector();
				if(v.size() < size_t(id_) + 1) {
					v.resize(id_+1,0);
				}
				tls_object *p=v[id_];
				if(p)
					return p;
				tls_object *res = new tls_object(intrusive_ptr<key>(this));
				v[id_] = res;
				return res;
	
			}
		private:
			int id_;
		};
		intrusive_ptr<key> make_key(void (*dtor)(void *))
		{
			return new unlimited_key(dtor);
		}
	} // detail
#endif 

#ifdef BOOSTER_POSIX

	struct fork_shared_mutex::data {
		pthread_rwlock_t lock;
		FILE *lock_file;
	};


	fork_shared_mutex::fork_shared_mutex() : d(new data)
	{
		pthread_rwlock_init(&d->lock,0);
		d->lock_file = tmpfile();
		if(!d->lock_file) {
			int err=errno;
			pthread_rwlock_destroy(&d->lock);
			throw system::system_error(err,system::system_category,"fork_shared_mutex:failed to create temporary file");
		}
	}
	fork_shared_mutex::~fork_shared_mutex()
	{
		// only threads see the lock - it is safe
		fclose(d->lock_file);
		pthread_rwlock_destroy(&d->lock);
	}
	
	bool fork_shared_mutex::try_unique_lock()
	{
		if(pthread_rwlock_trywrlock(&d->lock)!=0)
			return false;
		struct flock lock;
		memset(&lock,0,sizeof(lock));
		lock.l_type=F_WRLCK;
		lock.l_whence=SEEK_SET;
		int res = 0;
		while((res = ::fcntl(fileno(d->lock_file),F_SETLK,&lock))!=0 && errno==EINTR)
			;
		if(res == 0)
			return true;
		int err = errno;
		pthread_rwlock_unlock(&d->lock);
		if(err == EACCES || err==EAGAIN)
			return false;
		throw system::system_error(err,system::system_category,"fork_shared_mutex: failed to lock");
	}
	void fork_shared_mutex::unique_lock()
	{
		pthread_rwlock_wrlock(&d->lock);
		struct flock lock;
		memset(&lock,0,sizeof(lock));
		lock.l_type=F_WRLCK;
		lock.l_whence=SEEK_SET;
		int res = 0;
		while((res = ::fcntl(fileno(d->lock_file),F_SETLKW,&lock))!=0 && errno==EINTR)
			;
		if(res == 0)
			return;
		int err = errno;
		pthread_rwlock_unlock(&d->lock);
		throw system::system_error(err,system::system_category,"fork_shared_mutex: failed to lock");
	}

	bool fork_shared_mutex::try_shared_lock()
	{
		if(pthread_rwlock_tryrdlock(&d->lock)!=0)
			return false;
		struct flock lock;
		memset(&lock,0,sizeof(lock));
		lock.l_type=F_RDLCK;
		lock.l_whence=SEEK_SET;
		int res = 0;
		while((res = ::fcntl(fileno(d->lock_file),F_SETLK,&lock))!=0 && errno==EINTR)
			;
		if(res == 0)
			return true;
		int err = errno;
		pthread_rwlock_unlock(&d->lock);
		if(err == EACCES || err==EAGAIN)
			return false;
		throw system::system_error(err,system::system_category,"fork_shared_mutex: failed to lock");
	}

	void fork_shared_mutex::shared_lock()
	{
		pthread_rwlock_rdlock(&d->lock);
		struct flock lock;
		memset(&lock,0,sizeof(lock));
		lock.l_type=F_RDLCK;
		lock.l_whence=SEEK_SET;
		int res = 0;
		while((res = ::fcntl(fileno(d->lock_file),F_SETLKW,&lock))!=0 && errno==EINTR)
			;
		if(res == 0)
			return;
		int err = errno;
		pthread_rwlock_unlock(&d->lock);
		throw system::system_error(err,system::system_category,"fork_shared_mutex: failed to lock");
	}

	void fork_shared_mutex::unlock()
	{
		struct flock lock;
		memset(&lock,0,sizeof(lock));
		lock.l_type=F_UNLCK;
		lock.l_whence=SEEK_SET;
		int res = 0;
		while((res = ::fcntl(fileno(d->lock_file),F_SETLKW,&lock))!=0 && errno==EINTR)
			;
		pthread_rwlock_unlock(&d->lock);
	}
#endif

} // booster


