/*
 * 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.17 2006/01/08 11:01:25 kls Exp $
 */

#include "recorder.h"
#include <stdarg.h>
#include <stdio.h>
#include <unistd.h>
#include "cutter.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::cFileWriter(const char *FileName, cRemux *Remux, bool IsLiveRec, bool IsBeginning)
:cThread("file writer")
{
  isLiveRec = IsLiveRec;
  isBeginning=IsBeginning;
  ResumeLiveRec=false;
  filename=strdup(FileName);
  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:
  if (!IsBeginning)
  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;
  free(filename);
}

bool cFileWriter::RunningLowOnDiskSpace(void)
{
  if (time(NULL) > lastDiskSpaceCheck + DISKCHECKINTERVAL) {
     int Free = FreeDiskSpaceMB(fileName->Name());
     lastDiskSpaceCheck = time(NULL);
     if (Free < MINFREEDISKSPACE && (!isLiveRec || Free == 0)) {
        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
     if (fileSize > MEGABYTE(Setup.MaxVideoFileSize) || RunningLowOnDiskSpace()) {
        if (isLiveRec && fileSize)
        {
          index->Write(NO_PICTURE, fileName->Number(), fileSize);
          index->Write(NO_PICTURE, fileName->Number(), fileSize);
        }
        recordFile = fileName->SetOffset(fileName->Number()+1,0,isLiveRec && fileName->Number()>=0);
        fileSize = 0;
        }
     }
  return recordFile != NULL;
}

void cFileWriter::Action(void)
{
  time_t t = time(NULL);
  while (Running()) {
        int Count;
        u_int64_t PTS;
        uchar *p = remux->Get(Count, &pictureType);
        if (ResumeLiveRec && p &&  pictureType == I_FRAME && pts<(PTS=((u_int64_t)(p[9]&0x0E)<<29)+((u_int64_t)p[10]<<22)+((u_int64_t)(p[11]&0xFE)<<14)+((u_int64_t)p[12]<<7)+((u_int64_t)(p[13]&0xFE)>>1) ) )
        {
          while (cCutter::Active())
            usleep(1);
          index = new cIndexFile(filename, true);
          ResumeLiveRec=false;
          isBeginning=false; 
        }
        if (isBeginning && p)
        {
          remux->Del(Count);
          p=NULL;
          t = time(NULL);
        }
        if (p) {
           if (!Running() && pictureType == I_FRAME) // finish the recording before the next 'I' frame
              break;
           if (isLiveRec && (fileSize/1024/1024)>=(Setup.LiveBufferSize-Setup.MaxVideoFileSize*(fileName->Number()-1)) && pictureType == I_FRAME)
           {
              index->Write(NO_PICTURE, fileName->Number(), fileSize);
              index->Write(NO_PICTURE, fileName->Number(), fileSize);
              recordFile = fileName->SetOffset(1,fileSize=0,isLiveRec);
              recordFile->Seek(0,0);
           }
           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);
              }
           else
              break;
           t = time(NULL);
           }
        else if (!isLiveRec && time(NULL) - t > MAXBROKENTIMEOUT) {
           esyslog("ERROR: video data stream broken");
           cThread::EmergencyExit(true);
           t = time(NULL);
           }
        }
}

void cFileWriter::RecordNextIFrame(u_int64_t PTS)
{
  recordFile = fileName->NextFile();
  fileSize = 0;
  ResumeLiveRec = true;  
  pts=PTS;
}

cRecorder::cRecorder(const char *FileName, int Ca, int Priority, int VPid, const int *APids, const int *DPids, const int *SPids, bool IsLiveRec, bool IsBeginning)
:cReceiver(Ca, Priority, VPid, APids, Setup.UseDolbyInRecordings ? DPids : NULL, SPids)
,cThread("recording")
{
  isLiveRec=IsLiveRec;
  attached=false;
  // Make sure the disk is up and running:
  SpinUpDisk(FileName);
  ringBuffer = new cRingBufferLinear(RECORDERBUFSIZE, TS_SIZE * 2, true, "Recorder");
  ringBuffer->SetTimeouts(0, 100);
  remux = new cRemux(VPid, APids, Setup.UseDolbyInRecordings ? DPids : NULL, SPids, !isLiveRec);
  oldRemux=NULL;
  writer = new cFileWriter(FileName, remux,IsLiveRec,IsBeginning);
}

cRecorder::~cRecorder()
{
  Detach();
  delete writer;
  delete remux;
  if (oldRemux)
    delete (oldRemux);
  delete ringBuffer;
}

void cRecorder::Activate(bool On)
{
  if (On) {
     writer->Start();
     Start();
     attached=true;
     }
  else {
     attached=false;
     Cancel(3);
}
}

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

void cRecorder::ChangePIDs(cDevice *Device, int VPid, const int *APids, const int *DPids, const int *SPids)
{
  Detach();
  SetPIDs(VPid, APids, Setup.UseDolbyDigital ? DPids : NULL, SPids);
  Device->AttachReceiver(this);
  if (oldRemux)
    delete oldRemux;
  oldRemux = remux;
  writer->SetNewRemux(remux = new cRemux(VPid, APids, Setup.UseDolbyDigital ? DPids : NULL, SPids, !isLiveRec));
  Start();
}

void cRecorder::Action(void)
{
  while (Running()) {
        int r;
        uchar *b = ringBuffer->Get(r);
        if (b) {
           int Count = remux->Put(b, r);
           if (Count)
              ringBuffer->Del(Count);
           else
              cCondWait::SleepMs(100); // avoid busy loop when resultBuffer is full in cRemux::Put()
           }
        }
}

