/*
 * thread.h: A simple thread base class
 *
 * See the main source file 'vdr.c' for copyright information and
 * how to reach the author.
 *
 * $Id: thread.h 4.2 2016/12/08 10:18:32 kls Exp $
 */

#ifndef __THREAD_H
#define __THREAD_H

#include <pthread.h>
#include <stdio.h>
#include <sys/types.h>

typedef pid_t tThreadId;

class cCondWait {
private:
  pthread_mutex_t mutex;
  pthread_cond_t cond;
  bool signaled;
public:
  cCondWait(void);
  ~cCondWait();
  static void SleepMs(int TimeoutMs);
       ///< Creates a cCondWait object and uses it to sleep for TimeoutMs
       ///< milliseconds, immediately giving up the calling thread's time
       ///< slice and thus avoiding a "busy wait".
       ///< In order to avoid a possible busy wait, TimeoutMs will be automatically
       ///< limited to values >2.
  bool Wait(int TimeoutMs = 0);
       ///< Waits at most TimeoutMs milliseconds for a call to Signal(), or
       ///< forever if TimeoutMs is 0.
       ///< Returns true if Signal() has been called, false it the given
       ///< timeout has expired.
  void Signal(void);
       ///< Signals a caller of Wait() that the condition it is waiting for is met.
  };

class cMutex;

class cCondVar {
private:
  pthread_cond_t cond;
public:
  cCondVar(void);
  ~cCondVar();
  void Wait(cMutex &Mutex);
  bool TimedWait(cMutex &Mutex, int TimeoutMs);
  void Broadcast(void);
  };

class cRwLock {
private:
  pthread_rwlock_t rwlock;
  int locked;
  tThreadId writeLockThreadId;
public:
  cRwLock(bool PreferWriter = false);
  ~cRwLock();
  bool Lock(bool Write, int TimeoutMs = 0);
  void Unlock(void);
  };

class cMutex {
  friend class cCondVar;
private:
  pthread_mutex_t mutex;
  int locked;
public:
  cMutex(void);
  ~cMutex();
  void Lock(void);
  void Unlock(void);
  };

class cThread {
  friend class cThreadLock;
private:
  bool active;
  bool running;
  pthread_t childTid;
  tThreadId childThreadId;
  cMutex mutex;
  char *description;
  bool lowPriority;
  static tThreadId mainThreadId;
  static void *StartThread(cThread *Thread);
protected:
  void SetPriority(int Priority);
  void SetIOPriority(int Priority);
  void Lock(void) { mutex.Lock(); }
  void Unlock(void) { mutex.Unlock(); }
  virtual void Action(void) = 0;
       ///< A derived cThread class must implement the code it wants to
       ///< execute as a separate thread in this function. If this is
       ///< a loop, it must check Running() repeatedly to see whether
       ///< it's time to stop.
  bool Running(void) { return running; }
       ///< Returns false if a derived cThread object shall leave its Action()
       ///< function.
  void Cancel(int WaitSeconds = 0);
       ///< Cancels the thread by first setting 'running' to false, so that
       ///< the Action() loop can finish in an orderly fashion and then waiting
       ///< up to WaitSeconds seconds for the thread to actually end. If the
       ///< thread doesn't end by itself, it is killed.
       ///< If WaitSeconds is -1, only 'running' is set to false and Cancel()
       ///< returns immediately, without killing the thread.
public:
  cThread(const char *Description = NULL, bool LowPriority = false);
       ///< Creates a new thread.
       ///< If Description is present, a log file entry will be made when
       ///< the thread starts and stops (see SetDescription()).
       ///< The Start() function must be called to actually start the thread.
       ///< LowPriority can be set to true to make this thread run at a lower
       ///< priority.
  virtual ~cThread();
  void SetDescription(const char *Description, ...) __attribute__ ((format (printf, 2, 3)));
       ///< Sets the description of this thread, which will be used when logging
       ///< starting or stopping of the thread. Make sure any important information
       ///< is within the first 15 characters of Description, because only these
       ///< may be displayed in thread listings (like 'htop', for instance).
  bool Start(void);
       ///< Actually starts the thread.
       ///< If the thread is already running, nothing happens.
  bool Active(void);
       ///< Checks whether the thread is still alive.
  static tThreadId ThreadId(void);
  static tThreadId IsMainThread(void) { return ThreadId() == mainThreadId; }
  static void SetMainThreadId(void);
  };

// cMutexLock can be used to easily set a lock on mutex and make absolutely
// sure that it will be unlocked when the block will be left. Several locks can
// be stacked, so a function that makes many calls to another function which uses
// cMutexLock may itself use a cMutexLock to make one longer lock instead of many
// short ones.

class cMutexLock {
private:
  cMutex *mutex;
  bool locked;
public:
  cMutexLock(cMutex *Mutex = NULL);
  ~cMutexLock();
  bool Lock(cMutex *Mutex);
  };

// cThreadLock can be used to easily set a lock in a thread and make absolutely
// sure that it will be unlocked when the block will be left. Several locks can
// be stacked, so a function that makes many calls to another function which uses
// cThreadLock may itself use a cThreadLock to make one longer lock instead of many
// short ones.

class cThreadLock {
private:
  cThread *thread;
  bool locked;
public:
  cThreadLock(cThread *Thread = NULL);
  ~cThreadLock();
  bool Lock(cThread *Thread);
  };

#define LOCK_THREAD cThreadLock ThreadLock(this)

class cStateKey;

class cStateLock {
  friend class cStateKey;
private:
  const char *name;
  tThreadId threadId;
  cRwLock rwLock;
  int state;
  bool explicitModify;
  void Unlock(cStateKey &StateKey, bool IncState = true);
       ///< Releases a lock that has been obtained by a previous call to Lock()
       ///< with the given StateKey. If this was a write-lock, and IncState is true,
       ///< the state of the lock will be incremented. In any case, the (new) state
       ///< of the lock will be copied to the StateKey's state.
public:
  cStateLock(const char *Name = NULL);
  bool Lock(cStateKey &StateKey, bool Write = false, int TimeoutMs = 0);
       ///< Tries to get a lock and returns true if successful.
       ///< If TimoutMs is not 0, it waits for the given number of milliseconds
       ///< and returns false if no lock has been obtained within that time.
       ///< Otherwise it waits indefinitely for the lock. The given StateKey
       ///< will store which lock it has been used with, and will use that
       ///< information when its Remove() function is called.
       ///< There are two possible locks, one only for read access, and one
       ///< for reading and writing:
       ///<
       ///< If Write is false (i.e. a read-lock is requested), the lock's state
       ///< is compared to the given StateKey's state, and true is returned if
       ///< they differ.
       ///< If true is returned, the read-lock is still in place and the
       ///< protected data structures can be safely accessed (in read-only mode!).
       ///< Once the necessary operations have been performed, the lock must
       ///< be released by a call to the StateKey's Remove() function.
       ///< If false is returned, the state has not changed since the last check,
       ///< and the read-lock has been released. In that case the protected
       ///< data structures have not changed since the last call, so no action
       ///< is required. Note that if TimeoutMs is used with read-locks, Lock()
       ///< might return false even if the states of lock and key differ, just
       ///< because it was unable to obtain the lock within the given time.
       ///< You can call cStateKey::TimedOut() to detect this.
       ///<
       ///< If Write is true (i.e. a write-lock is requested), the states of the
       ///< lock and the given StateKey don't matter, it will always try to obtain
       ///< a write lock.
  void SetExplicitModify(void) { explicitModify = true; }
       ///< If you have obtained a write lock on this lock, and you don't want its
       ///< state to be automatically incremented when the lock is released, a call to
       ///< this function will disable this, and you can explicitly call IncState()
       ///< to increment the state.
  void IncState(void);
       ///< Increments the state of this lock.
  };

class cStateKey {
  friend class cStateLock;
private:
  cStateLock *stateLock;
  bool write;
  int state;
  bool timedOut;
public:
  cStateKey(bool IgnoreFirst = false);
       ///< Sets up a new state key. If IgnoreFirst is true, the first use
       ///< of this key with a lock will not return true if the lock's state
       ///< hasn't explicitly changed.
  ~cStateKey();
  void Reset(void);
       ///< Resets the state of this key, so that the next call to a lock's
       ///< Lock() function with this key will return true, even if the
       ///< lock's state hasn't changed.
  void Remove(bool IncState = true);
       ///< Removes this key from the lock it was previously used with.
       ///< If this key was used to obtain a write lock, the state of the lock will
       ///< be incremented and copied to this key. You can set IncState to false
       ///< to prevent this.
  bool StateChanged(void);
       ///< Returns true if this key is used for obtaining a write lock, and the
       ///< lock's state differs from that of the key. When used with a read lock,
       ///< it always returns true, because otherwise the lock wouldn't have been
       ///< obtained in the first place.
  bool InLock(void) { return stateLock; }
       ///< Returns true if this key is currently in a lock.
  bool TimedOut(void) const { return timedOut; }
       ///< Returns true if the last lock attempt this key was used with failed due
       ///< to a timeout.
  };

class cIoThrottle {
private:
  static cMutex mutex;
  static int count;
  bool active;
public:
  cIoThrottle(void);
  ~cIoThrottle();
  void Activate(void);
       ///< Activates the global I/O throttling mechanism.
       ///< This function may be called any number of times, but only
       ///< the first call after an inactive state will have an effect.
  void Release(void);
       ///< Releases the global I/O throttling mechanism.
       ///< This function may be called any number of times, but only
       ///< the first call after an active state will have an effect.
  bool Active(void) { return active; }
       ///< Returns true if this I/O throttling object is currently active.
  static bool Engaged(void);
       ///< Returns true if any I/O throttling object is currently active.
  };

// cPipe implements a pipe that closes all unnecessary file descriptors in
// the child process.

class cPipe {
private:
  pid_t pid;
  FILE *f;
public:
  cPipe(void);
  ~cPipe();
  operator FILE* () { return f; }
  bool Open(const char *Command, const char *Mode);
  int Close(void);
  };

// SystemExec() implements a 'system()' call that closes all unnecessary file
// descriptors in the child process.
// With Detached=true, calls command in background and in a separate session,
// with stdin connected to /dev/null.

int SystemExec(const char *Command, bool Detached = false);

#endif //__THREAD_H