/*
 * recorder.c: The actual DVB recorder
 *
 * See the main source file 'vdr.c' for copyright information and
 * how to reach the author.
 *
 * $Id: recorder.c 1.19 2007/02/24 16:36:24 kls Exp $
 */

#include "recorder.h"
#include <stdarg.h>
#include <stdio.h>
#include <unistd.h>
#ifdef USE_TTXTSUBS
#include <stdint.h>
#endif /* TTXTSUBS */
#include "shutdown.h"

#define RECORDERBUFSIZE  MEGABYTE(5)

// The maximum time we wait before assuming that a recorded video data stream
// is broken:
#define MAXBROKENTIMEOUT 30 // seconds

#define MINFREEDISKSPACE    (512) // MB
#define DISKCHECKINTERVAL   100 // seconds

// --- cFileWriter -----------------------------------------------------------

class cFileWriter : public cThread {
private:
#ifdef USE_TTXTSUBS
  cTtxtSubsRecorderBase *ttxtSubsRecorder;
#endif /* TTXTSUBS */
  cRemux *remux;
  cFileName *fileName;
  cIndexFile *index;
  uchar pictureType;
  int fileSize;
  cUnbufferedFile *recordFile;
  time_t lastDiskSpaceCheck;
  bool RunningLowOnDiskSpace(void);
  bool NextFile(void);
protected:
  virtual void Action(void);
public:
#ifdef USE_TTXTSUBS
  cFileWriter(const char *FileName, cRemux *Remux, cTtxtSubsRecorderBase *tsr);
#else
  cFileWriter(const char *FileName, cRemux *Remux);
#endif /* TTXTSUBS */
  virtual ~cFileWriter();
  };

#ifdef USE_TTXTSUBS
cFileWriter::cFileWriter(const char *FileName, cRemux *Remux, cTtxtSubsRecorderBase *tsr)
#else
cFileWriter::cFileWriter(const char *FileName, cRemux *Remux)
#endif /* TTXTSUBS */
:cThread("file writer")
{
#ifdef USE_TTXTSUBS
  ttxtSubsRecorder = tsr;
#endif /* TTXTSUBS */
  fileName = NULL;
  remux = Remux;
  index = NULL;
  pictureType = NO_PICTURE;
  fileSize = 0;
  lastDiskSpaceCheck = time(NULL);
  fileName = new cFileName(FileName, true);
  recordFile = fileName->Open();
  if (!recordFile)
     return;
  // Create the index file:
  index = new cIndexFile(FileName, true);
  if (!index)
     esyslog("ERROR: can't allocate index");
     // let's continue without index, so we'll at least have the recording
}

cFileWriter::~cFileWriter()
{
  Cancel(3);
  delete index;
  delete fileName;
#ifdef USE_TTXTSUBS
  if (ttxtSubsRecorder)
     delete ttxtSubsRecorder;
#endif /* TTXTSUBS */
}

bool cFileWriter::RunningLowOnDiskSpace(void)
{
  if (time(NULL) > lastDiskSpaceCheck + DISKCHECKINTERVAL) {
     int Free = FreeDiskSpaceMB(fileName->Name());
     lastDiskSpaceCheck = time(NULL);
     if (Free < MINFREEDISKSPACE) {
        dsyslog("low disk space (%d MB, limit is %d MB)", Free, MINFREEDISKSPACE);
        return true;
        }
     }
  return false;
}

bool cFileWriter::NextFile(void)
{
  if (recordFile && pictureType == I_FRAME) { // every file shall start with an I_FRAME
#ifndef USE_HARDLINKCUTTER
     if (fileSize > MEGABYTE(Setup.MaxVideoFileSize) || RunningLowOnDiskSpace()) {
#else
     if (fileSize > fileName->MaxFileSize() || RunningLowOnDiskSpace()) {
#endif /* HARDLINKCUTTER */
        recordFile = fileName->NextFile();
        fileSize = 0;
        }
     }
  return recordFile != NULL;
}

void cFileWriter::Action(void)
{
  time_t t = time(NULL);
  while (Running()) {
        int Count;
        uchar *p = remux->Get(Count, &pictureType);
        if (p) {
           if (!Running() && pictureType == I_FRAME) // finish the recording before the next 'I' frame
              break;
           if (NextFile()) {
              if (index && pictureType != NO_PICTURE)
                 index->Write(pictureType, fileName->Number(), fileSize);
              if (recordFile->Write(p, Count) < 0) {
                 LOG_ERROR_STR(fileName->Name());
                 break;
                 }
              fileSize += Count;
              remux->Del(Count);
#ifdef USE_TTXTSUBS
              // not sure if the pictureType test is needed, but it seems we can get
              // incomplete pes packets from remux if we are not getting pictures?
              if (ttxtSubsRecorder && pictureType != NO_PICTURE) {
                 uint8_t *subsp;
                 size_t len;
                 if (ttxtSubsRecorder->GetPacket(&subsp, &len)) {
                    recordFile->Write(subsp, len);
                    fileSize += len;
                    }
                 }
#endif /* TTXTSUBS */
              }
           else
              break;
           t = time(NULL);
           }
        else if (time(NULL) - t > MAXBROKENTIMEOUT) {
           esyslog("ERROR: video data stream broken");
           ShutdownHandler.RequestEmergencyExit();
           t = time(NULL);
           }
        }
}

// --- cRecorder -------------------------------------------------------------

#if defined (USE_LIVEBUFFER) || defined (USE_TTXTSUBS)
cRecorder::cRecorder(const char *FileName, tChannelID ChannelID, int Priority, int VPid, const int *APids, const int *DPids, const int *SPids
#ifdef USE_TTXTSUBS
   ,cTtxtSubsRecorderBase *tsr
#endif /* TTXTSUBS */
#ifdef USE_LIVEBUFFER
   ,cLiveBuffer *LiveBuffer
#endif /* LIVEBUFFER */
   )
#else
cRecorder::cRecorder(const char *FileName, tChannelID ChannelID, int Priority, int VPid, const int *APids, const int *DPids, const int *SPids)
#endif /* LIVEBUFFER || TTXTSUBS */
#ifdef USE_DOLBYINREC
:cReceiver(ChannelID, Priority, VPid, APids, Setup.UseDolbyInRecordings ? DPids : NULL, SPids)
#else
:cReceiver(ChannelID, Priority, VPid, APids, Setup.UseDolbyDigital ? DPids : NULL, SPids)
#endif /* DOLBYINREC */
,cThread("recording")
{
  // Make sure the disk is up and running:

  SpinUpDisk(FileName);

  ringBuffer = new cRingBufferLinear(RECORDERBUFSIZE, TS_SIZE * 2, true, "Recorder");
  ringBuffer->SetTimeouts(0, 100);
#ifdef USE_DOLBYINREC
  remux = new cRemux(VPid, APids, Setup.UseDolbyInRecordings ? DPids : NULL, SPids, true);
#else
  remux = new cRemux(VPid, APids, Setup.UseDolbyDigital ? DPids : NULL, SPids, true);
#endif /* DOLBYINREC */
#ifdef USE_LIVEBUFFER
  fileName = strdup(FileName);
  writer = NULL;
  liveBuffer = LiveBuffer;
  if (!LiveBuffer)
#endif /* LIVEBUFFER */
#ifdef USE_TTXTSUBS
  writer = new cFileWriter(FileName, remux, tsr);
#else
  writer = new cFileWriter(FileName, remux);
#endif /* TTXTSUBS */
}

cRecorder::~cRecorder()
{
  Detach();
  delete writer;
  delete remux;
  delete ringBuffer;
#ifdef USE_LIVEBUFFER
  free(fileName);
#endif /* LIVEBUFFER */
}

void cRecorder::Activate(bool On)
{
  if (On) {
#ifdef USE_LIVEBUFFER
     if (writer)
#endif /* LIVEBUFFER */
     writer->Start();
     Start();
     }
  else
     Cancel(3);
}

void cRecorder::Receive(uchar *Data, int Length)
{
  if (Running()) {
     int p = ringBuffer->Put(Data, Length);
     if (p != Length && Running())
        ringBuffer->ReportOverflow(Length - p);
     }
}

void cRecorder::Action(void)
{
  while (Running()) {
        int r;
        uchar *b = ringBuffer->Get(r);
        if (b) {
           int Count = remux->Put(b, r);
#ifdef USE_LIVEBUFFER
           if (!writer && liveBuffer) {
              int c;
              uchar pictureType;
              uchar *p = remux->Get(c, &pictureType);
              if (pictureType == I_FRAME && p && p[0]==0x00 && p[1]==0x00 && p[2]==0x01 && (p[7] & 0x80) && p[3]>=0xC0 && p[3]<=0xEF) {
                 int64_t pts  = (int64_t) (p[ 9] & 0x0E) << 29 ;
                 pts |= (int64_t)  p[ 10]         << 22 ;
                 pts |= (int64_t) (p[ 11] & 0xFE) << 14 ;
                 pts |= (int64_t)  p[ 12]         <<  7 ;
                 pts |= (int64_t) (p[ 13] & 0xFE) >>  1 ;
                 liveBuffer->CreateIndexFile(fileName,pts);
#ifdef USE_TTXTSUBS
                 writer = new cFileWriter(fileName, remux, ttxtSubsRecorder);
#else
                 writer = new cFileWriter(fileName, remux);
#endif /* TTXTSUBS */
                 writer->Start();
                 }
              else
                 remux->Del(c);
              }
#endif /* LIVEBUFFER */
           if (Count)
              ringBuffer->Del(Count);
           else
              cCondWait::SleepMs(100); // avoid busy loop when resultBuffer is full in cRemux::Put()
           }
        }
}
