#include <stdio.h>
#include <string.h>
#include <sys/mman.h>
extern "C" {
#ifdef boolean
#define HAVE_BOOLEAN
#endif
#undef boolean
}
#include <cstring>
#include <unistd.h>
#include <stdint.h>
#include <netinet/in.h>
#include <signal.h>
#include <errno.h>
#include "libdvb_parts.h"
#include "helpfunc.h"
#include "device.h"
#include "i18n.h"
#include "pvr.h"
#include "ivtv.h"
#include <vdr/sources.h>


cPTV_Device::cPTV_Device(int index, int dev) // 4. VDR call 
  {
  auto struct v4l2_input       v4l2inp;
  auto struct v4l2_capability  v4l2cap;
  int i;
  d(5, "PTV_Dev[%d]::cPTV_Device(index=%d, videodev=%d)", card.index, index, dev);
  card.fd = -1;
  card.index=index;
  card.videodev = dev;
  card.Apid = card.Vpid = card.Tpid = -1;
  card.hasRadio = 0;
  encoder = 0;
  dropPES = 1; vdr_ready = 0;
  memset(&v4l2inp, 0, sizeof(v4l2inp));
  sprintf(card.name, "/dev/video%d", card.videodev);
  if ((card.fd = open(card.name, O_RDWR)) < 0) {
    d(0,"cPTV_Device::cPTV_Device(index=%d, dev=%d): cannot open device!!",index, dev);
    return;
    }
  for (i = 0; i < MAXSOURCES; i++) {       
    v4l2inp.index = i;
    if (IOCTL(card.fd, VIDIOC_ENUMINPUT, &v4l2inp) == -1)
      continue;
    if (!memcmp(v4l2inp.name, "Composite 0", 11)) {card.composite0=i; continue;}
    if (!memcmp(v4l2inp.name, "Composite 1", 11)) {card.composite1=i; continue;}
    if (!memcmp(v4l2inp.name, "Composite 2", 11)) {card.composite2=i; continue;}
    if (!memcmp(v4l2inp.name, "Composite 3", 11)) {card.composite3=i; continue;}
    if (!memcmp(v4l2inp.name, "Composite 4", 11)) {card.composite4=i; continue;}
    if (!memcmp(v4l2inp.name, "Composite", 9))    {card.composite0=i; continue;}
    if (!memcmp(v4l2inp.name, "S-Video 0", 9))    {card.svideo0=i;    continue;}
    if (!memcmp(v4l2inp.name, "S-Video 1", 9))    {card.svideo1=i;    continue;}
    if (!memcmp(v4l2inp.name, "S-Video 2", 9))    {card.svideo2=i;    continue;}
    if (!memcmp(v4l2inp.name, "S-Video 3", 9))    {                   continue;}
    if (!memcmp(v4l2inp.name, "S-Video", 7))      {card.svideo0=i;    continue;}
    if (!memcmp(v4l2inp.name, "Tuner", 5))        {card.tuner0=i;     continue;}
    d(0, "Warning: unknown input name %s ignored.", v4l2inp.name);
    }
  if (IOCTL(card.fd, VIDIOC_QUERYCAP, &v4l2cap) != -1)
    if (v4l2cap.capabilities & V4L2_CAP_RADIO)
      card.hasRadio = 1;   
  d(6, "Composite0=%d", card.composite0);
  d(6, "Composite1=%d", card.composite1);
  d(6, "Composite2=%d", card.composite2);
  d(6, "Composite3=%d", card.composite3);
  d(6, "Composite4=%d", card.composite4);
  d(6, "S-Video0=%d",   card.svideo0);
  d(6, "S-Video1=%d",   card.svideo1);
  d(6, "S-Video2=%d",   card.svideo2);
  d(6, "Tuner0=%d",     card.tuner0);
  }

void cPTV_Device::StopEncoder() {
  d(5, "PTV_Dev[%d]::StopEncoder()", card.index);
 if (encoder) {
   delete encoder;
   encoder = NULL;
   }
  vdr_ready = 0;
  }

cPTV_Device::~cPTV_Device() {
  d(5, "PTV_Dev[%d]::~cPTV_Device()", card.index);
  StopEncoder();
  if (card.fd >= 0) close(card.fd);
  }

bool cPTV_Device::SetPid(cPidHandle *Handle, int Type, bool On) {
  switch (Type) {
    case TYPE_VPID : {
      d(5, "SetPid: VPid = %d",  On ? Handle->pid : -1);
      card.Vpid = On ? Handle->pid : -1;
      break;
      }
    case TYPE_APID : {
      d(5, "SetPid: APid = %d",  On ? Handle->pid : -1);
      card.Apid = On ? Handle->pid : -1;
      break;
      }
    case TYPE_TPID : {
      d(5, "SetPid: TPid = %d",  On ? Handle->pid : -1);
      card.Tpid = On ? Handle->pid : -1;
      break;
      }
    default: {
      d(5, "SetPid: Unknown Type=%d, On=%d",Type, On);
      return false;
      }
    }
  return true;
  }

bool cPTV_Device::OpenDvr(void) {
  d(5, "PTV_Dev[%d]::OpenDvr(void)", card.index);
  vdr_ready = 0;
  bufferFilled = false;
  bytestotal = skipped = dup = af = vf = pf = sf = TSerrors = CCErrors = 0;
  LastCC = -1;
  dropAudio = true; droppedAudio = 0;
  pts[AUDIO] = pts[VIDEO] = last_pts[AUDIO] = last_pts[VIDEO] = 0;
  dts[AUDIO] = dts[VIDEO] = last_dts[AUDIO] = last_dts[VIDEO] = 0;
  firstVideoPts = 0;
  if (setPVRpids(card.videodev, card.Apid, card.Vpid, card.Ppid)) {
    d(0, "Cannot set Pids on card %d = /dev/video/d. -> exiting.", \
         card.index, card.videodev);
    return false;
    }
  buf_read = buf_write = buf_read_pos = buf_write_pos = 0;
  full_buffers = buffered_bytes = max_used = 0;
  ringbuffer_warned = ready_warned = read_warned = pes = spes = 0;
  if (encoder) {
    StopEncoder();               //destroying instance of encoder
    buf_read = buf_write = buf_read_pos = buf_write_pos = 0;
    full_buffers = buffered_bytes = max_used = 0;
    encoder = new cEncoder(this);    //recreating encoder 
    return true;
    }
  else {
    encoder = new cEncoder(this);  
    return true;
    }
  }

void cPTV_Device::CloseDvr(void) {
  d(3, "PTV_Dev[%d]::CloseDvr()", card.index);
  dropPES = 1;
  if (encoder)
    encoder->Needed(0);
  }

uint64_t cPTV_Device::transPtsDts(uint8_t *pts) {
  uint64_t wts;
  wts = ((uint64_t)((pts[0] & 0x0E) << 5) | ((pts[1] & 0xFC) >> 2)) << 24;
  wts |= (((pts[1] & 0x03) << 6)          | ((pts[2] & 0xFC) >> 2)) << 16;
  wts |= (((pts[2] & 0x02) << 6)          | ((pts[3] & 0xFE) >> 1)) << 8;
  wts |= (((pts[3] & 0x01) << 7)          | ((pts[4] & 0xFE) >> 1));
  return wts * 300ULL;
  }

bool cPTV_Device::getPES(uchar *b, int l) {
  register int c = 3, type = -1;
  auto     uint8_t streamID;
  auto     uint8_t flags = 0;
  auto     uint8_t pes_hlength = 0;
  auto     unsigned short *pl;
  auto     int plength;
  if (!memcmp(b, "\0\0\1", 3))
    { // Synchronisation Bytes
    streamID = b[c++];
    switch (streamID) {
      case AUDIO_STREAM_S :
      case AUDIO_STREAM_E : type = AUDIO;
                            break;
      case VIDEO_STREAM_S :
      case VIDEO_STREAM_E : type = VIDEO;
                            break;
      }
    pl = (unsigned short *)(b + c);
    plength = ntohs(*pl);
    c += 2;
    if (type != -1) {
      c++;
      flags = b[c++];
      pes_hlength = b[c++];
      if (((flags & PTS_DTS_FLAGS) == PTS_ONLY) || ((flags & PTS_DTS_FLAGS) == PTS_DTS)) {
        pts[type] = transPtsDts(b + c);
        // "vdrsync" approach: drop audio until (audio_pts > first_video_pts)
        if (dropAudio) {
          if ((type == VIDEO) && !firstVideoPts)
            firstVideoPts = pts[type];
          else if (type == AUDIO)
            if (pts[type] >= firstVideoPts)
              {
              dropAudio = false;
	      if (droppedAudio)
                d(1, "skipped %d audio-packets to sync A/V", droppedAudio);
              }
          }
        last_pts[type] = pts[type];
        memcpy(LastPts[type], b + c, 5);
        c += 5;
        if ((flags & PTS_DTS_FLAGS) == PTS_DTS) {
          dts[type] = transPtsDts(b + c); c += 5;
          }
        }
      }
    return streamID == GROUP_START_CODE;
    }
  return false;
  }

unsigned short int cPTV_Device::getPid(void) {
  return ((buf[1] & PID_MASK_HI) << 8) | buf[2];
  }

bool cPTV_Device::validPid(unsigned short int pid) {
  if (pid == card.Apid)
    af++;
  else if (pid == card.Vpid)
    vf++;
  else if (pid == card.Ppid)
    pf++;
  else {
    sf++;
    return false;
    }
  return true;
  }

bool cPTV_Device::getTS(void) {
  auto int  c = 4;
  if (buf[1] & TRANSPORT_ERROR)
    TSerrors++;
  if (buf[3] & ADAPTION_FIELD) {
    if (buf[5] & OPCR_FLAG)
      memcpy(opcr, buf + 12, 6);
    c += (buf[4] + 1);
    if (c > 187)
      c = 0;
    }
  if (c && (buf[3] & PAYLOAD) && (buf[1] & PAYLOAD_START) && (TS_SIZE - c) > 13)
    getPES(buf + c, TS_SIZE - c);
  return c && ((TS_SIZE - c) > 3) && (buf[c + 3] == GROUP_START_CODE);
  }

bool cPTV_Device::GetTSPacket(uchar *&Data) {
  register int  r, n1 = 0, l = 0, w = TS_SIZE;
  auto     bool skip, res;
  if (!vdr_ready) {
    d(2, "PTV_Dev[%d]::GetTSPacket() -- first call", card.index);
    vdr_ready = 1;
    dropPES = 0;
    }
    do {
      skip = false;
      n1 = buf[0] = 0;
      while (encoder->ReaderLoop() && (buf[0] != TS)) {
        r = ReadBuffer(buf, 1);
        if (r < 1)
          usleep(USLEEP);
        else
          n1++;
        }
      if (encoder->ReaderLoop()) {
        if (n1 > 1)
          d(1, "skipped %d byte to sync on next TS packet", n1);
        bytestotal += n1;
        l = 1;
        w = TS_SIZE - 1;
        while (encoder->ReaderLoop() && (w > 0)) {
          r = ReadBuffer(buf + l, w);
          if (r < w)
            usleep(USLEEP);
          w -= r;
          l += r;
          }
        if (encoder->ReaderLoop()) {
          bytestotal += TS_SIZE - 1;
          pid = getPid();
          skip = !validPid(pid);
          if (!skip) {
            res = getTS();
            if (!skip && dropAudio && (pid == card.Apid)) {
              skip = true;
              sf++;
              droppedAudio++;
              }
            }
          }
        }
      } while (skip && encoder->ReaderLoop());
    if (encoder->ReaderLoop()) {
      Data = buf;
      return true;
      }
    else
      return false;
  }

bool cPTV_Device::ProvidesSource(int Source) const {
  d(6, "PTV_Dev[%d]::ProvidesSource = %s", card.index, Source == cSource::stPvr? "true":"false");
  return Source == cSource::stPvr;
  }

bool cPTV_Device::ProvidesTransponder(const cChannel *Channel) const {
  return false; // can't provide any actual transponder
  }

int cPTV_Device::ProvidesCa(const cChannel *Channel) const {
  d(6, "PTV_Dev[%d]::ProvidesCa(%X)=%d", card.index, Channel->Ca(), Channel->IsPvr());
  return Channel->IsPvr();
  }

bool cPTV_Device::ProvidesChannel(const cChannel *Channel, int Priority, \
     bool *NeedsDetachReceivers) const {
  auto bool result = false;
  auto bool hasPriority = Priority < 0 || Priority > this->Priority();
  auto bool needsDetachReceivers = true;
  auto int  freq = Channel->Frequency();
  if (freq == 1) freq = 0;
  d(3, "PTV_Dev[%d]::ProvidesChannel(Receiving=%d, Channel->Frequency=%d",
      card.index, Receiving(), freq);    
  if (ProvidesSource(Channel->Source()) && ProvidesCa(Channel) && \
     ((Channel->CoderateH() > 8) || (Channel->CoderateH() == card.videodev)) && \
     (Channel->Apid(0) != 0 || card.hasRadio)) {
    if (Receiving()) {
      if ((getFreq((card.videodev*1000)/16) == freq) || (freq == -1)) {
        needsDetachReceivers = false;
        result = true;
        }
      else
        result = hasPriority;
      }
    else {
      needsDetachReceivers = false;
      result = true;
      }
    }
  if (NeedsDetachReceivers)
    *NeedsDetachReceivers = needsDetachReceivers;
  d(3, "PTV_Dev[%d]::ProvidesChannel: NeedsDetachReceivers=%d, result=%d, hasPriority=%d",
    card.index, needsDetachReceivers, result, hasPriority);
  return result;
  }

int cPTV_Device::ReadBuffer(unsigned char *data, int len) {
  register int len2 = 0, x;
  if (!encoder || !encoder->ReaderLoop()) {
    d(2, "read_buffer() no more data available -- encoder is shutting down!");
    return 0;
    }
  if (!rbuffer[0]) {
    if (!read_warned) {
      d(2, "read_buffer() - waiting for data.");
      read_warned = 1;
      }
    return 0;
  }
  while (mutex)
    usleep(USLEEP);
  mutex = 1;
  while (len > 0) {
    if (!full_buffers) // no more data buffered!
      break;
    x = BUFFSIZE - buf_read_pos;
    if (x > len)
      x = len;
    fast_memcpy(data + len2, rbuffer[buf_read] + buf_read_pos, x);
    len2 += x;
    len -= x;
    buffered_bytes -= x;
    buf_read_pos += x;
    if (buf_read_pos >= BUFFSIZE) { // block is empty, find next!
      buf_read = (buf_read + 1) % NUM_BUFS;
      --full_buffers;
      buf_read_pos = 0;
      }
    }
  mutex = 0;
  return len2;
  }


bool cPTV_Device::SetChannelDevice(const cChannel *Channel, bool LiveView) {
  uint tmr;
  uint videosource;
  d(1, "PTV_Dev[%d]::SetChannelDevice(Receiving=%d", card.index, Receiving());
  d(1, "Channel: %s, SID=%d", Channel->Name(), Channel->Sid());
  if (Channel->Vpid() != 301 && Channel->Apid(0) == 300) {
    d(0, "Seems to be a radio channel - not supported by ptv plugin up to now.");
    }
  d(1, "Channel->Frequency = %3.2f MHz (%s)", Channel->Frequency()/1000.0,\
        Freq2Channelname(Channel->Frequency()));
  if (Channel->Frequency() > 1) {
    if (setFreq(card.fd, Channel->Frequency())) {
      d(0,"SetChannelDevice: setFreq failed.");
      return false;    
      }
    }   
  d(1, "video norm = %s", tvnorms[Channel->Bandwidth() > 2? 0 : 2-Channel->Bandwidth()]);
  if (setNorm(card.fd, Channel->Bandwidth() > 2? 0 : 2-Channel->Bandwidth())) {
    d(0,"SetChannelDevice: setNorm  failed.");
    return false;    
    }
  ptvsetup.videonorm = Channel->Bandwidth() > 2? 0 : 2-Channel->Bandwidth(); /* für menusetup.c */
  switch (Channel->CoderateL()) {
    case 0:  videosource=card.composite0; break;
    case 1:  videosource=card.composite1; break;
    case 2:  videosource=card.composite2; break;
    case 3:  videosource=card.composite3; break;
    case 4:  videosource=card.tuner0;     break;
    case 5:  videosource=card.composite4; break;
    case 6:  videosource=card.svideo0;    break;
    case 7:  videosource=card.svideo1;    break;
    case 8:  videosource=card.svideo2;    break;
    default: videosource=card.tuner0;     break;
    }
  d(1, "video input = %s", inputs[videosource]);
  if (setInput(card.fd, videosource)) {
    d(0,"SetChannelDevice: setInput failed.");
    return false;    
    }
  d(1, "brightness=%.1f%% contrast=%.1f%% color=%.1f%% hue=%.1f%%",
          (ptvsetup.brightness / 34.0 * 100.0), (ptvsetup.contrast / 34.0 * 100.0),
          (ptvsetup.color / 34.0 * 100.0), (ptvsetup.hue / 34.0 * 100));
  if (setControl(card.fd, ptvsetup.brightness, ptvsetup.contrast, \
	ptvsetup.color, ptvsetup.hue)) {
    d(0,"SetChannelDevice: setControl failed.");
    return false;    
    }
  d(1, "volume=%d", ptvsetup.volume);
  if (setVolume(card.fd, ptvsetup.volume)) {
    d(0,"SetChannelDevice: setVolume failed.");
    return false;    
    }
  d(1, "dnr_spatial=%d, dnr_temporal=%d",ptvsetup.dnr_spatial,ptvsetup.dnr_temporal);    
  if (setCodec (card.fd, ptvsetup.aspect, ptvsetup.pulldown, ptvsetup.dnr_spatial,\
                ptvsetup.dnr_temporal, Channel->Bandwidth() > 2? 0 : 2-Channel->Bandwidth())) {
    d(0,"SetChannelDevice: setCodec failed.");
    return false;    
    }
  tmr = 0; 
  while (getSignalstatus(card.fd) == -1) {
    if (Channel->Vpid() != 301)
      return true; /*radio channel: no video -> therefore no lock*/
    usleep(20);
    tmr++;
    if (tmr > 200) {
      d(0,"Bad Signal, timeout waiting for sync");
      return false;
      }
    }
  return true;
  }


bool cPTV_Device::HasEncoder(void) {
  return encoder ? true : false; }

bool cPTV_Device::EncoderIsRunning(void) {
  return encoder->IsRunning(); }

bool cPTV_Device::EncoderIsNeeded(void) { 
  return encoder->IsNeeded(); } 

void cPTV_Device::WriteBuffer(uint8_t *data, int len, void *p) {
  register int len2 = 0, x;
  if (!encoder->ReaderLoop()) {
    if (++ringbuffer_warned < MAXWARN)
      d(2, "%d. TS frame dropped - encoder is shutting down", ringbuffer_warned);
    return;
    }
  while (mutex)
    usleep(USLEEP);
  mutex = 1;
  while (len > 0) {
    if (full_buffers == NUM_BUFS) {
      if (++ringbuffer_warned < MAXWARN)
        d(0, "%d. TS frame dropped (ringbuffer full)", ringbuffer_warned);
      break;
      }
    x = BUFFSIZE - buf_write_pos;
    if(x > len)
      x = len;
    fast_memcpy(rbuffer[buf_write] + buf_write_pos, data + len2, x);
    len2 += x;
    len -= x;
    buffered_bytes += x;
    if (buffered_bytes > max_used)
      max_used = buffered_bytes;
    buf_write_pos += x;
    if (buf_write_pos >= BUFFSIZE)
      { // block is full, find next!
      buf_write = (buf_write + 1) % NUM_BUFS;
      ++full_buffers;
      buf_write_pos = 0;
      }
    }
  mutex = 0;
  }

int cPTV_Device::ReadPVR(int fd, int Apid, int Vpid) {
  register int r = 0, rmax = 0, cc = 0, i;
  register int  dropped = 0, dropwarned = 1;
  auto     unsigned char buffer[BUFFSIZE];
  p2p p2t;
  d(1, "PTV_Dev[%d]::ReadPVR(int Apid=%d, int Vpid=%d)", card.index, Apid, Vpid);
  for (i = 0; i < NUM_BUFS; i++)
    rbuffer[i] = (unsigned char *)malloc(BUFFSIZE);
  init_p2p(&p2t, 2048);
  p2t.data = this;
  p2t.pida = Apid;
  p2t.pidv = Vpid;
  p2t.acounter = 0;
  p2t.vcounter = 0;
  p2t.count1 = 0;
  p2t.count0 = 0;
  while (encoder->ReaderLoop()) {
    if (dropPES) {
      if (!encoder || !encoder->ReaderLoop())
        break;
      if (!dropwarned) {
        dropwarned++;
        usleep(10); //20050826 -w.
        d(1, "PVR reader suspended");
        encoder->SetReaderLoop(0);
        }
      dropped++;
      usleep(10); //20050826 -w.
      }
    else {
      if (dropwarned) {
        dropwarned = 0;
        if (fd < 0) {
          d(0, "ReadPVR: Cannot open device: %d:%s", errno, strerror(errno));
          cc = -2;
          break;
          }
        d(1, "PVR reader continuing");
        }
      r = read(card.fd, buffer, BUFFSIZE);
      if (r == -1) {
        d(0, "ReadPVR: Cannot read device, %d:%s", errno, strerror(errno));
        cc = -1;
        break;
        }
      if (r > rmax)
        rmax = r;
      pes++;
      spes += r;
      if (r > 0)
        get_pes(buffer, r, &p2t); // PES -> TS
      }
    }
  report(rmax, dropped);
  for (i = 0; i < NUM_BUFS; i++) {
    if (rbuffer[i])
      free(rbuffer[i]);
    rbuffer[i] = NULL;
    }
  return cc;
  }

void cPTV_Device::report(int rmax, int dropped)
  {
  d(1, "Ringbuffer max usage: %d/%d byte = %.1f%%",
      max_used, NUM_BUFS * BUFFSIZE, (double)(max_used * 100.0 / (NUM_BUFS * BUFFSIZE)));
  d(1, "Largest data packet from PVR card was %d byte long", rmax);
  d(1, "%d TS frame(s) dropped (ringbuffer was full)", ringbuffer_warned);
  d(1, "%d data packets dropped", ready_warned);
  d(1, "%d data packets dropped while not on a analogue channel", dropped);
  }



/* cEncoder: thread writing data to Buffer and converting PES -> TS */



cEncoder::cEncoder(cPTV_Device* Parent)
  {
  d(5, "cEncoder::cEncoder()");
  parent = Parent;
  ReaderLoopRunning=1;
  Start();
  EncoderRunning = 1;
  EncoderNeeded = 1;
  }

cEncoder::~cEncoder()
  {
  d(5, "cEncoder::~cEncoder()");
  ReaderLoopRunning = 0;
  Cancel(2);
  EncoderRunning = 0;
  }

void cEncoder::Action(void)
  {
  register int cc = 0, retries = 3;
  d(1, "cEncoder::Action()");
  EncoderRunning = 1;
  retry:
  cc = parent->ReadPVR(parent->getCardHandle(), parent->DeviceApid(), parent->DeviceVpid());
  switch (cc)
    {
    case  0 : d(1, "PVR reader stopped.");
              break;
    case -1 : d(0, "PVR reader failed!%s", 
                (--retries > 0) ? " - retrying" : "");
              if ((retries > 0)) {
                usleep(100);
                goto retry;
                }
                break;
    case -2 : d(0, "open error!");
              break; 
    }
  }
