///////////////////////////////////////////////////////////////////////////////
//                                                                             
//  Copyright (C) 2008-2012  Artyom Beilis (Tonkikh) <artyomtnk@yahoo.com>     
//                                                                             
//  See accompanying file COPYING.TXT file for licensing details.
//
///////////////////////////////////////////////////////////////////////////////
#ifndef CPPCMS_POSIX_UTIL_H
#define CPPCMS_POSIX_UTIL_H
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/types.h>
#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <pthread.h>
#include <cppcms/cppcms_error.h>
#include <booster/noncopyable.h>

namespace cppcms {
namespace impl {

	inline void *mmap_anonymous(size_t size)
	{
		#if defined(MAP_ANONYMOUS)
		void *p=::mmap(0,size,PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_SHARED, -1, 0);
		int err=errno;
		#elif defined(MAP_ANON)
		void *p=::mmap(0,size,PROT_READ | PROT_WRITE, MAP_ANON | MAP_SHARED, -1, 0);
		int err=errno;
		#else
		int fd=::open("/dev/null",O_RDWR);
		if(fd < 0) 
			throw cppcms_error(errno,"Failed to open /dev/null");
		void *p=::mmap(0,size,PROT_READ | PROT_WRITE,MAP_SHARED, fd, 0);
		int err=errno;
		::close(fd);
		#endif
		if(p==MAP_FAILED)
			throw cppcms_error(err,"Failed to create shared memory");
		return p;
	}

	inline void create_mutex(pthread_mutex_t *m,bool pshared = false)
	{
		if(!pshared) {
			pthread_mutex_init(m,0);
		}
		else {
			#ifdef CPPCMS_HAS_THREAD_PSHARED	
			pthread_mutexattr_t attr;
			pthread_mutexattr_init(&attr);
			try {
				int res;
				res = pthread_mutexattr_setpshared(&attr,PTHREAD_PROCESS_SHARED);
				if(res==0)
					res = pthread_mutex_init(m,&attr);
				if(res < 0)
					throw cppcms_error(errno,"Failed to create process shared mutex");
				pthread_mutexattr_destroy(&attr);
			}
			catch(...) {
				pthread_mutexattr_destroy(&attr);
				throw;
			}
			#else
			throw cppcms_error("Process shared mutex is not supported");
			#endif
		}
	}

	inline void destroy_mutex(pthread_mutex_t *m)
	{
		pthread_mutex_destroy(m);
	}
	
	inline void create_rwlock(pthread_rwlock_t *m,bool pshared=false)
	{
		if(!pshared) {
			pthread_rwlock_init(m,0);
		}
		else {
			#ifdef CPPCMS_HAS_THREAD_PSHARED	
			pthread_rwlockattr_t attr;
			pthread_rwlockattr_init(&attr);
			try {
				int res;
				res = pthread_rwlockattr_setpshared(&attr,PTHREAD_PROCESS_SHARED);
				if(res==0)
					res = pthread_rwlock_init(m,&attr);
				if(res < 0)
					throw cppcms_error(errno,"Failed to create process shared mutex");
				pthread_rwlockattr_destroy(&attr);
			}
			catch(...) {
				pthread_rwlockattr_destroy(&attr);
				throw;
			}
			#else
			throw cppcms_error("Process shared mutex is not supported");
			#endif
		}
	}

	inline void destroy_rwlock(pthread_rwlock_t *m)
	{
		pthread_rwlock_destroy(m);
	}


	#ifdef CPPCMS_HAS_THREAD_PSHARED	
	inline bool test_pthread_mutex_pshared_impl()
	{
		void *memory=mmap_anonymous(sizeof(pthread_mutex_t));
		bool res=false;
		try {
			create_mutex(reinterpret_cast<pthread_mutex_t *>(memory),true);
			destroy_mutex(reinterpret_cast<pthread_mutex_t *>(memory));
			res=true;
		}
		catch(cppcms_error const &e) {
			res= false;
		}
		::munmap((char*)memory,sizeof(pthread_mutex_t));
		return res;
	}

	inline bool test_pthread_mutex_pshared()
	{
		static bool has = test_pthread_mutex_pshared_impl();
		return has;
	}
	#else
	inline bool test_pthread_mutex_pshared()
	{
		return false;
	}

	#endif


	class mutex : public booster::noncopyable {
	public:
		class guard;
		mutex() :
			plock_(0),
			flock_(0)
		{
			bool use_pthread=test_pthread_mutex_pshared();
			if(use_pthread) {
				plock_ =reinterpret_cast<pthread_mutex_t *>(mmap_anonymous(sizeof(pthread_mutex_t)));
				create_mutex(plock_,true);
			}
			else {
				plock_=&normal_;
				create_mutex(plock_,false);
				flock_=tmpfile();
				if(!flock_) {
					int err=errno;
					destroy_mutex(plock_);
					throw cppcms_error(err,"Failed to create temporary file");
				}
			}
		}

		void lock()
		{
			pthread_mutex_lock(plock_);
			if(flock_) {
				struct flock lock;
				memset(&lock,0,sizeof(lock));
				lock.l_type=F_WRLCK;
				lock.l_whence=SEEK_SET;
				while(::fcntl(fileno(flock_),F_SETLKW,&lock)!=0 && errno==EINTR)
					;
			}
		}

		void unlock()
		{
			if(flock_) {
				struct flock lock;
				memset(&lock,0,sizeof(lock));
				lock.l_type=F_UNLCK;
				lock.l_whence=SEEK_SET;
				while(::fcntl(fileno(flock_),F_SETLKW,&lock)!=0 && errno==EINTR)
					;
			}
			pthread_mutex_unlock(plock_);
		}

		~mutex()
		{
			if(flock_) ::fclose(flock_);
			destroy_mutex(plock_);
			if(plock_ != &normal_)
				::munmap((char*)plock_,sizeof(pthread_mutex_t));
		}
	private:
		pthread_mutex_t *plock_;
		FILE *flock_;
		pthread_mutex_t normal_;
	};

	class mutex::guard : public booster::noncopyable {
	public:
		guard(mutex &m) : m_(m) 
		{
			m_.lock();
		}
		~guard()
		{
			m_.unlock();
		}
	private:
		mutex &m_;
	};
	
	class shared_mutex : public booster::noncopyable {
	public:
		class shared_guard;
		class unique_guard;
		shared_mutex() :
			plock_(0),
			flock_(0)
		{
			bool use_pthread=test_pthread_mutex_pshared();
			if(use_pthread) {
				plock_ =reinterpret_cast<pthread_rwlock_t *>(mmap_anonymous(sizeof(pthread_rwlock_t)));
				create_rwlock(plock_,true);
			}
			else {
				plock_=&normal_;
				create_rwlock(plock_,false);
				flock_=tmpfile();
				if(!flock_) {
					int err=errno;
					destroy_rwlock(plock_);
					throw cppcms_error(err,"Failed to create temporary file");
				}
			}
		}

		void wrlock()
		{
			pthread_rwlock_wrlock(plock_);
			if(flock_) {
				struct flock lock;
				memset(&lock,0,sizeof(lock));
				lock.l_type=F_WRLCK;
				lock.l_whence=SEEK_SET;
				while(::fcntl(fileno(flock_),F_SETLKW,&lock)!=0 && errno==EINTR)
					;
			}
		}
		
		void rdlock()
		{
			pthread_rwlock_rdlock(plock_);
			if(flock_) {
				struct flock lock;
				memset(&lock,0,sizeof(lock));
				lock.l_type=F_RDLCK;
				lock.l_whence=SEEK_SET;
				while(::fcntl(fileno(flock_),F_SETLKW,&lock)!=0 && errno==EINTR)
					;
			}
		}

		void unlock()
		{
			if(flock_) {
				struct flock lock;
				memset(&lock,0,sizeof(lock));
				lock.l_type=F_UNLCK;
				lock.l_whence=SEEK_SET;
				while(::fcntl(fileno(flock_),F_SETLKW,&lock)!=0 && errno==EINTR)
					;
			}
			pthread_rwlock_unlock(plock_);
		}

		~shared_mutex()
		{
			if(flock_) ::fclose(flock_);
			destroy_rwlock(plock_);
			if(plock_ != &normal_)
				::munmap((char*)plock_,sizeof(pthread_rwlock_t));
		}
	private:
		pthread_rwlock_t *plock_;
		FILE *flock_;
		pthread_rwlock_t normal_;
	};
	
	class shared_mutex::shared_guard : public booster::noncopyable {
	public:
		shared_guard(shared_mutex &m) : m_(m) 
		{
			m_.rdlock();
		}
		~shared_guard()
		{
			m_.unlock();
		}
	private:
		shared_mutex &m_;
	};
	
	class shared_mutex::unique_guard : public booster::noncopyable {
	public:
		unique_guard(shared_mutex &m) : m_(m) 
		{
			m_.wrlock();
		}
		~unique_guard()
		{
			m_.unlock();
		}
	private:
		shared_mutex &m_;
	};

} // impl
} // cppcms


#endif

