/*
 * ringbuffer.c: A ring buffer
 *
 * See the main source file 'vdr.c' for copyright information and
 * how to reach the author.
 *
 * Parts of this file were inspired by the 'ringbuffy.c' from the
 * LinuxDVB driver (see linuxtv.org).
 *
 * $Id: ringbuff.c,v 1.1 2005/02/12 19:18:17 dom Exp $
 */

#include "ringbuff.h"
#include <stdlib.h>
#include <unistd.h>
#include <vdr/tools.h>

// --- cRingBuff -----------------------------------------------------------

#define OVERFLOWREPORTDELTA 5 // seconds between reports

cRingBuff::cRingBuff(int Size, bool Statistics)
{
  size = Size;
  statistics = Statistics;
  maxFill = 0;
  lastPercent = 0;
  putTimeout = getTimeout = 0;
  lastOverflowReport = 0;
  overflowCount = overflowBytes = 0;
}

cRingBuff::~cRingBuff()
{
  if (statistics)
     dsyslog("buffer stats: %d (%d%%) used", maxFill, maxFill * 100 / (size - 1));
}

void cRingBuff::WaitForPut(void)
{
  if (putTimeout) {
     putMutex.Lock();
     readyForPut.TimedWait(putMutex, putTimeout);
     putMutex.Unlock();
     }
}

void cRingBuff::WaitForGet(void)
{
  if (getTimeout) {
     getMutex.Lock();
     readyForGet.TimedWait(getMutex, getTimeout);
     getMutex.Unlock();
     }
}

void cRingBuff::EnablePut(void)
{
  if (putTimeout)
     readyForPut.Broadcast();
}

void cRingBuff::EnableGet(void)
{
  if (getTimeout)
     readyForGet.Broadcast();
}

void cRingBuff::SetTimeouts(int PutTimeout, int GetTimeout)
{
  putTimeout = PutTimeout;
  getTimeout = GetTimeout;
}

void cRingBuff::ReportOverflow(int Bytes)
{
  overflowCount++;
  overflowBytes += Bytes;
  if (time(NULL) - lastOverflowReport > OVERFLOWREPORTDELTA) {
     esyslog("ERROR: %d ring buffer overflow%s (%d bytes dropped)", overflowCount, overflowCount > 1 ? "s" : "", overflowBytes);
     overflowCount = overflowBytes = 0;
     lastOverflowReport = time(NULL);
     }
}

// --- cRingBuffLinear -----------------------------------------------------

cRingBuffLinear::cRingBuffLinear(int Size, int Margin, bool Statistics)
:cRingBuff(Size, Statistics)
{
  margin = Margin;
  buffer = NULL;
  getThreadTid = 0;
  if (Size > 1) { // 'Size - 1' must not be 0!
     buffer = MALLOC(uchar, Size);
     if (!buffer)
        esyslog("ERROR: can't allocate ring buffer (size=%d)", Size);
     Clear();
     }
  else
     esyslog("ERROR: illegal size for ring buffer (%d)", Size);
}

cRingBuffLinear::~cRingBuffLinear()
{
  free(buffer);
}

int cRingBuffLinear::Available(void)
{
  Lock();
  int diff = head - tail;
  Unlock();
  return (diff >= 0) ? diff : Size() + diff - margin;
}

void cRingBuffLinear::Clear(void)
{
  Lock();
  head = tail = margin;
  lastGet = -1;
  Unlock();
  EnablePut();
  EnableGet();
}

int cRingBuffLinear::Put(const uchar *Data, int Count)
{
  if (Count > 0) {
     Lock();
     int rest = Size() - head;
     int diff = tail - head;
     int free = ((tail < margin) ? rest : (diff > 0) ? diff : Size() + diff - margin) - 1;
     if (statistics) {
        int fill = Size() - free - 1 + Count;
        if (fill >= Size())
           fill = Size() - 1;
        if (fill > maxFill)
           maxFill = fill;
        int percent = maxFill * 100 / (Size() - 1) / 5 * 5;
        if (abs(lastPercent - percent) >= 5) {
           if (percent > 75)
              dsyslog("buffer usage: %d%% (tid=%ld)", percent, getThreadTid);
           lastPercent = percent;
           }
        }
     if (free > 0) {
        if (free < Count)
           Count = free;
        if (Count > maxFill)
           maxFill = Count;
        if (Count >= rest) {
           memcpy(buffer + head, Data, rest);
           if (Count - rest)
              memcpy(buffer + margin, Data + rest, Count - rest);
           head = margin + Count - rest;
           }
        else {
           memcpy(buffer + head, Data, Count);
           head += Count;
           }
        }
     else
        Count = 0;
     Unlock();
     EnableGet();
     if (Count == 0)
        WaitForPut();
     }
  return Count;
}

uchar *cRingBuffLinear::Get(int &Count)
{
  uchar *p = NULL;
  Lock();
  if (getThreadTid <= 0)
     getThreadTid = pthread_self();
  int rest = Size() - tail;
  if (rest < margin && head < tail) {
     int t = margin - rest;
     memcpy(buffer + t, buffer + tail, rest);
     tail = t;
     }
  int diff = head - tail;
  int cont = (diff >= 0) ? diff : Size() + diff - margin;
  if (cont > rest)
     cont = rest;
  if (cont >= margin) {
     p = buffer + tail;
     Count = lastGet = cont;
     }
  Unlock();
  if (!p)
     WaitForGet();
  return p;
}

void cRingBuffLinear::Del(int Count)
{
  if (Count > 0 && Count <= lastGet) {
     Lock();
     tail += Count;
     lastGet -= Count;
     if (tail >= Size())
        tail = margin;
     Unlock();
     EnablePut();
     }
  else
     esyslog("ERROR: invalid Count in cRingBuffLinear::Del: %d", Count);
}

