/******************************************************************************
 *                                                                            *
 *                       (c) 2000-2020 Jaroslav fojtik                        *
 *                  This code could be used under LGPL licence                *
 *                                                                            *
 ******************************************************************************/
/** @file
 *  Critical section object wrapper, supports RO and RW locks.
 *
 * $HeadURL: https://217.160.184.146:4433/svn/lib_logger/lib/csext.h $
 * $Revision: 24 $
 * $Date: 2020-02-08 12:04:39 +0100 (so, 08 2 2020) $
 * $Author: escjfo $ */
#ifndef __CRITICAL_SECTION_EXTENDED_H_
#define __CRITICAL_SECTION_EXTENDED_H_

/* Linux and Windows compatibility block. */
#ifdef _WIN32
  #include <Windows.h>
  #ifdef __BORLANDC__
    #define InterlockedIncrement(pX)  InterlockedIncrement((long*)pX)
    #define InterlockedDecrement(pX)  InterlockedDecrement((long*)pX)
  #endif
#else
  #include <pthread.h>
  #include <unistd.h>

  #define CRITICAL_SECTION     pthread_mutex_t
  #define LeaveCriticalSection(pCS) pthread_mutex_unlock(pCS)
  #define EnterCriticalSection(pCS) pthread_mutex_lock(pCS)
  #define InitializeCriticalSection(pCS)  pthread_mutex_init(pCS, NULL)
  #define DeleteCriticalSection(pCS) pthread_mutex_destroy(pCS)

  #define InterlockedIncrement(pX) __sync_fetch_and_add(pX,1)
  #define InterlockedDecrement(pX) __sync_fetch_and_sub(pX,1)

  inline void Sleep(long sleep_time) {usleep(1000*(sleep_time));}
#endif


/****************************************************************************************/


/** The class wraps critical section and ensures that it is correctly initialised and destructed. */
class CritSectionWrapper
{
public:
  CRITICAL_SECTION cs;

  CritSectionWrapper(void) {InitializeCriticalSection(&cs);}
  ~CritSectionWrapper() {DeleteCriticalSection(&cs);}

  void Lock(void);
  void Unlock(void);

private:
  CritSectionWrapper(const CritSectionWrapper&);	// Disable copy ctor.
};


inline void CritSectionWrapper::Lock(void)
{
  EnterCriticalSection(&cs);
}


inline void CritSectionWrapper::Unlock(void)
{
  LeaveCriticalSection(&cs);
}


/****************************************************************************************/


/** The critical section wrapper to allow automatically leave critical section during exception.
 * It does NOT create critical section NOR destroy it.  */
class CritSectionLocker
{
private:
    CRITICAL_SECTION  *poCritSection;     /**< Critical section OS dependent. */

public:

    /* Constructor extracts critical section inside itself. */
    explicit CritSectionLocker(CRITICAL_SECTION *poCS)
    {
      poCritSection = poCS;
      EnterCriticalSection(poCritSection);
    }

    explicit CritSectionLocker(CritSectionWrapper & WrapCS)
    {
      poCritSection = &WrapCS.cs;
      EnterCriticalSection(poCritSection);
    }

    /* Destructor causes to realease critical section. */
    ~CritSectionLocker()
    {
      LeaveCriticalSection(poCritSection);
    }

private:
  CritSectionLocker(const CritSectionLocker&);	// Disable copy ctor.
};


/****************************************************************************************/


/** Extended critical section.
 * Class provides read-only and read-write lock functions. */
class CriticalSectionExt
{
protected:
#ifdef _WIN32
    HANDLE no_access;
#else
    mutable pthread_cond_t no_access;
    mutable pthread_mutex_t mtx_no_access;
#endif
    mutable volatile long rocounter;
    mutable CRITICAL_SECTION rwlock;

    void Init(void);

public:
    CriticalSectionExt(void) {Init();}
    virtual ~CriticalSectionExt();

    void LockRO(void) const;
    void UnlockRO(void) const;
    void LockRW(void) const;
    void UnlockRW(void) const;

private:
    CriticalSectionExt(const CriticalSectionExt&);	// Disable copy ctor.
};


/** Exception-safe read-only locking for CriticalSectionExt. */
class CriticalSectionExtROLocker
{
  const CriticalSectionExt& cs;
public:
  explicit CriticalSectionExtROLocker(const CriticalSectionExt& c): cs(c) {cs.LockRO();}
  ~CriticalSectionExtROLocker() { cs.UnlockRO(); }
private:
  CriticalSectionExtROLocker(const CriticalSectionExtROLocker&);	// Disable copy ctor.
};


/** Exception-safe read-write locking for CriticalSectionExt */
class CriticalSectionExtRWLocker
{
  const CriticalSectionExt& cs;
public:
  explicit CriticalSectionExtRWLocker(const CriticalSectionExt& c): cs(c) {cs.LockRW();}
  ~CriticalSectionExtRWLocker() {cs.UnlockRW();}
private:
  CriticalSectionExtRWLocker(const CriticalSectionExtRWLocker&);	// Disable copy ctor.
};


/*******************************************************************************
 *
 * inline functions implementation
 *
 *******************************************************************************/

inline void CriticalSectionExt::Init(void)
{
#ifdef _WIN32
  no_access = CreateEvent(NULL, TRUE, TRUE, NULL);
#else
  //mtx_no_access = PTHREAD_MUTEX_INITIALIZER;
  pthread_mutex_init(&mtx_no_access, NULL);
#endif
  InitializeCriticalSection(&rwlock);
  rocounter = 0L;
}

inline CriticalSectionExt::~CriticalSectionExt()
{
  EnterCriticalSection(&rwlock);
  LeaveCriticalSection(&rwlock);

#ifdef _WIN32
  CloseHandle(no_access);
#else
  pthread_mutex_destroy(&mtx_no_access);
#endif
  DeleteCriticalSection(&rwlock);
}


inline void CriticalSectionExt::LockRO(void) const
{
  EnterCriticalSection(&rwlock);
#ifdef _WIN32
  if(InterlockedIncrement(&rocounter) != 0)
    ResetEvent(no_access);
#else
  InterlockedIncrement(&rocounter);
#endif
  LeaveCriticalSection(&rwlock);
}


inline void CriticalSectionExt::UnlockRO(void) const
{
  if(InterlockedDecrement(&rocounter) == 0)
#ifdef _WIN32
    SetEvent(no_access);
#else
    pthread_cond_signal(&no_access);
#endif
}


inline void CriticalSectionExt::LockRW(void) const
{
  EnterCriticalSection(&rwlock);
  while (rocounter != 0)	// The while cycle is for safety only as well as the timeout 10sec
  {
	// We cannot wait inside critical section because some threads may need to
	// Call LockRO to completelly release section
    LeaveCriticalSection(&rwlock);
#ifdef _WIN32
    WaitForSingleObject(no_access, 10000);
#else
    struct timespec ts;
    clock_gettime(CLOCK_REALTIME, &ts);
    ts.tv_sec += 10;            // ten seconds
    pthread_cond_timedwait(&no_access, &mtx_no_access, &ts);
#endif
    EnterCriticalSection(&rwlock);
  }
}


inline void CriticalSectionExt::UnlockRW(void) const
{
  LeaveCriticalSection(&rwlock);
}


#endif // __CRITICAL_SECTION_EXTENDED_H_
