#include <unistd.h>
#include <features.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <inttypes.h>
#include <getopt.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include "helpfunc.h"
#include <sys/ioctl.h>
#include <math.h>
#include "ivtv.h"
#include "i18n.h"
#include "pvr.h"
#include <vdr/channels.h>
#include <vdr/skins.h>

#define SYNCTIME 500000
#define MAXTRY 5

int doChannelScan(int fd) {
 /*   very basic channel scan function.
      based on my w_pvrscan, up to now only pal-europe.
      this is first version, will be modified later.
 */
  uint i=0, trials=0, freq=0, chFreq=0;
  char ChannelStr[255]; 
  cChannel ch;
  Skins.Message(mtInfo, tr("Setup.PTV_$ptv channel scan running - please wait."));
  if (fd < 0) {
    d(0,"doChannelScan: cannot open device");
    Skins.Message(mtInfo, tr("ptv channel scan failed."));
    return -1;
    }
  chFreq = getFreq(fd);
  for (i=0;i<=5;i++) {     /* pal-europe: k02 ..k05 */   
    freq = 48250 + i*7000; 
    setFreq (fd, freq);
    trials = 0; 
    while (getSignalstatus(fd) == -1) {
      usleep(SYNCTIME);
      trials++;
      if (trials > MAXTRY) {
        d(0,"%d No Signal", freq);
        break;
        }
      }
    if (trials > MAXTRY) continue;     
    sprintf(ChannelStr, "%s;PTV Plugin:%d:B6:P:0:301:300:305:0:%d:0:0:%d",\
        Freq2Channelname(freq), freq, freq*16/1000,i+1000);
    if (ch.Parse(ChannelStr)) {
      if (Channels.HasUniqueChannelID(&ch)) {
        d(5, "channel data ok");
        cChannel *channel = new cChannel;
        *channel = ch;
        Channels.Add(channel);
        Channels.ReNumber();
        Channels.SetModified(true);
        isyslog("new channel %d %s", channel->Number(), *channel->ToText());
        d(0, "%d %s", channel->Number(), *channel->ToText());
        }
      else
        d(0, "Channel data already known.");
      }
    else
      d(0, "Error in channel settings");
    }
  for (i=0;i<=27;i++) {     /* pal-europe: S01..03; SE1..10; k05..12; SE11..20 */
    freq = 105250 + i*7000;
    setFreq (fd, freq);
    trials = 0; 
    while (getSignalstatus(fd) == -1) {
      usleep(SYNCTIME);
      trials++;   
      if (trials > MAXTRY) {
        d(0,"%d No Signal",freq);
        break;
        }
      }
    if (trials > MAXTRY) continue;     
    sprintf(ChannelStr, "%s;PTV Plugin:%d:B6:P:0:301:300:305:0:%d:0:0:%d",\
        Freq2Channelname(freq), freq, freq*16/1000,i+1000);
    if (ch.Parse(ChannelStr)) {
      if (Channels.HasUniqueChannelID(&ch)) {
        d(5, "channel data ok");
        cChannel *channel = new cChannel;
        *channel = ch;
        Channels.Add(channel);
        Channels.ReNumber();
        Channels.SetModified(true);
        isyslog("new channel %d %s", channel->Number(), *channel->ToText());
        d(0, "%d %s", channel->Number(), *channel->ToText());
        }
      else
        d(0, "Channel data already known.");
      }
    else
      d(0, "Error in channel settings");
    }
  for (i=0;i<=69;i++) {     /* pal-europe, S21..41; k21..69 */   
    freq = 303250 + i*8000;
    setFreq (fd, freq);
    trials = 0; 
    while (getSignalstatus(fd) == -1) {
      usleep(SYNCTIME);
      trials++;
      if (trials > MAXTRY) {
        d(0,"%d No Signal",freq);
        break;
        }
      }
    if (trials > MAXTRY) continue;     
    sprintf(ChannelStr, "%s;PTV Plugin:%d:B6:P:0:301:300:305:0:%d:0:0:%i",\
        Freq2Channelname(freq), freq, freq*16/1000,i+1000);
    if (ch.Parse(ChannelStr)) {
      if (Channels.HasUniqueChannelID(&ch)) {
        d(5, "channel data ok");
        cChannel *channel = new cChannel;
        *channel = ch;
        Channels.Add(channel);
        Channels.ReNumber();
        Channels.SetModified(true);
        isyslog("new channel %d %s", channel->Number(), *channel->ToText());
        d(0, "%d %s", channel->Number(), *channel->ToText());
        }
      else
        d(0, "Channel data already known.");
      }
    else
      d(0, "Error in channel settings");
    }
  setFreq(fd, chFreq);
  Skins.Message(mtInfo, tr("Setup.PTV_$ptv channel scan finished."));
  return 0;
  }


int getSignalstatus(int fd)
  {
 /*   very basic signal status function, based on my w_pvrscan.
      used for channel scan and channel switching.
      this is first version, will be modified later.
      Needs a new ivtv version.
 */
  int regval1e, regval1f;
  struct ivtv_saa71xx_reg saa7115_reg;
  saa7115_reg.reg = (unsigned char)0x1e;
   if (fd >= 0) {
    if (ioctl(fd, IVTV_IOC_G_SAA7115_REG, &saa7115_reg) < 0) {
      d(5,"GetSignslstatus failed (cannot read 0x1e)");
      return -2;
      }
    regval1e = saa7115_reg.val;
    saa7115_reg.reg = (unsigned char)0x1f;
    if (ioctl(fd, IVTV_IOC_G_SAA7115_REG, &saa7115_reg) < 0) {
      d(5,"GetSignslstatus failed (cannot read 0x1f)");
      return -2;
      }
    regval1f = saa7115_reg.val;
    if ((regval1e & 0x80) && (regval1f & 0x01) && !(regval1f & 0x40) && (regval1f & 0x80))
      return 0;
    else
      return -1;
    }
  else {
    d(5,"getSignalstatus: cannot open device.");
    return -2;
    }
  }

int getFreq (int fd)
  {
  auto struct v4l2_frequency vf;
  if (fd >= 0) {
    memset(&vf, 0, sizeof(fd));
    vf.tuner = 0;
    vf.type = V4L2_TUNER_ANALOG_TV;
    IOCTL(fd, VIDIOC_G_FREQUENCY, &vf);
    return (vf.frequency*1000)/16;
    }    
  else
    return -1;
  return 0;
  }


int setFreq (int fd, uint freq) {
  auto struct v4l2_frequency vf;
  if (fd >= 0) {
    memset(&vf, 0, sizeof(fd));
    vf.tuner = 0;
    vf.type = V4L2_TUNER_ANALOG_TV;
    IOCTL(fd, VIDIOC_G_FREQUENCY, &vf);
    if (vf.frequency != uint((freq * 16) / 1000)) {
      vf.frequency = (freq * 16) / 1000;
      if (IOCTL(fd, VIDIOC_S_FREQUENCY, &vf)) {
        d(0, "setFreq: TV frequency=%d - failed,\n     %d:%s", \
            freq, errno, strerror(errno));
        return -1;
        }
      else
        d(1, "setFreq: %d - done.", freq);
      }
    }
  else
    return -1;
  return 0;
  }

int setNorm(int fd, uint norm) {
  int err = 0;
  auto v4l2_std_id id = V4L2_STD_PAL;
  if (fd >= 0) {
    IOCTL(fd, VIDIOC_G_STD, &id);
    switch (id) {
      case V4L2_STD_PAL: if (norm != 0) {
                           switch (norm) {
                             case 0 : id = V4L2_STD_PAL; break;
                             case 1 : id = V4L2_STD_NTSC; break;
                             case 2 : id = V4L2_STD_SECAM; break;
                             }
                           err=IOCTL(fd, VIDIOC_S_STD, &id);
                           } break;	  
      case V4L2_STD_NTSC: if (norm != 1) {
                           switch (norm)  {
                             case 0 : id = V4L2_STD_PAL; break;
                             case 1 : id = V4L2_STD_NTSC; break;
                             case 2 : id = V4L2_STD_SECAM; break;
                             }
                           err=IOCTL(fd, VIDIOC_S_STD, &id);
                           } break;
      case V4L2_STD_SECAM: if (norm != 2) {
                           switch (norm)   {
                             case 0 : id = V4L2_STD_PAL; break;
                             case 1 : id = V4L2_STD_NTSC; break;
                             case 2 : id = V4L2_STD_SECAM; break;
                             }
                           err=IOCTL(fd, VIDIOC_S_STD, &id);
                           } break;
    }
    if (err) {
      d(0, "setNorm: setting video norm=%s failed, %d:%s", tvnorms[norm], errno, strerror(errno));
      return -1;
      }
    else
      d(1, "setNorm: %s - done.", tvnorms[norm]);
    }
  else
    return -1;
  return 0;
  }

int setFormat(int fd, uint width, uint height) {
  auto struct v4l2_format vfmt;
  if (fd >= 0) {
    IOCTL(fd, VIDIOC_G_FMT, &vfmt);
    if ((vfmt.fmt.pix.width != width) || (vfmt.fmt.pix.height != height)) {
      vfmt.fmt.pix.width=width;
      vfmt.fmt.pix.height=height;
      if (IOCTL(fd, VIDIOC_S_FMT, &vfmt)) {
        d(0, "setFormat: format=%dx%d - failed, %d:%s", width, height, errno, strerror(errno));
        return -1;
        }
      else
        d(1, "setFormat: %dx%d - done.", width, height);
      }
    else
      d(5,"setFormat: format=%dx%d - skipped.", width, height);
    }
  else
    {
    d(0,"setFormat: cannot open device");
    return -1;
    }
  return 0;
  }

int setCodec (int fd, uint aspect, uint pulldown, uint spatial, uint temporal, uint vnorm)
  {
  auto struct ivtv_ioctl_codec codec;
  if (fd >= 0) {
    IOCTL(fd, IVTV_IOC_G_CODEC, &codec);
    if ((codec.stream_type  != IVTV_STREAM_DVD_S2) ||
        (codec.aspect       != aspect + 1) ||
        (codec.pulldown     != pulldown) ||
        (codec.bitrate      = (uint32_t)5000000) ||
        (codec.bitrate_peak = (uint32_t)6000000) ||
        (codec.bitrate_mode = 1) ||
        (codec.framespergop = 12) ||
        (codec.bframes      = 3) ||
        (codec.gop_closure  = 1) ||
        (codec.dnr_mode     = 0) ||
        (codec.dnr_type     = 0) ||
        (codec.dnr_spatial  = spatial) ||
        (codec.dnr_temporal = temporal) ||
        (codec.audio_bitmask = 0xd9)) {
      codec.stream_type  = IVTV_STREAM_DVD_S2;
      codec.aspect       = aspect + 1;
      codec.pulldown     = pulldown;
      codec.bitrate      = (uint32_t)5000000;
      codec.bitrate_peak = (uint32_t)6000000;
      codec.bitrate_mode = 1;
      codec.framespergop = 12;
      codec.bframes      = 3;
      codec.gop_closure  = 1;
      codec.dnr_mode     = 0;
      codec.dnr_type     = 0;
      codec.dnr_spatial  = spatial;
      codec.dnr_temporal = temporal;
      codec.audio_bitmask = 0xd9;
      codec.framerate    = !vnorm ? 1 : 0;
      if (IOCTL(fd, IVTV_IOC_S_CODEC, &codec)) {
        d(0, "setCodec: %d:%s", errno, strerror(errno));
        return -1;
        }
      else
        d(1, "setCodec - done.");
      }
    else
      d(0,"setCodec - skipped.");
    }
  else {
    d(0,"setCodec: cannot open device");
    return -1;
    }
  return 0;
  }

int setInput(int fd, uint input) {
  uint inp_ind;
  if (fd < 0) {
    d(0,"setInput: couldn't open device");
    return -1;
    }
  IOCTL(fd, VIDIOC_G_INPUT, &inp_ind);
  if (inp_ind != input) {
    if (IOCTL(fd, VIDIOC_S_INPUT, &input)) {
      d(0, "setInput: %s - failed.\n      err=%d:%s", \
	        inputs[input], errno, strerror(errno));
      return -1;
      }
    else
      d(1, "setInput: %s - done.", inputs[input]);
    }
  else
    d(5, "setInput: %s - skipped.", inputs[input]);
  return 0;
  }

int setControl(int fd, uint Brightness, uint Contrast, uint Saturation, uint Hue) {
  auto struct v4l2_control ctrl;
  if (fd < 0) {
    d(0,"setInput: couldn't open device");
    return -1;
    }
  d(1, "setControl: brightness=%d (%.1f%%) contrast=%d (%.1f%%) colour=%d (%.1f%%) hue=%d (%.1f%%)",
          Brightness, (Brightness / 34.0 * 100.0),
          Contrast, (Contrast / 34.0 * 100.0),
          Saturation, (Saturation / 34.0 * 100.0),
          Hue, (Hue / 34.0 * 100));
  ctrl.id = V4L2_CID_BRIGHTNESS;
  IOCTL(fd, VIDIOC_G_CTRL, &ctrl);
  if (ctrl.value != (int)round(Brightness * 7.5)) {
    ctrl.value = (int)round(Brightness * 7.5);
    if (IOCTL(fd, VIDIOC_S_CTRL, &ctrl)) {
      d(0,"Setting brightness failed");
      return -1;
      }
    else
      d(5,"Setting brightness - done");
    }
  else
    d(5,"Setting brightness - skipped");
  ctrl.id = V4L2_CID_CONTRAST;
  IOCTL(fd, VIDIOC_G_CTRL, &ctrl);
  if (ctrl.value != (int)round(Contrast * 3.76)) {
    ctrl.value = (int)round(Contrast * 3.76);
    if (IOCTL(fd, VIDIOC_S_CTRL, &ctrl)) {
      d(0,"Setting contrast failed");
      return -1;
      }
    else
      d(5,"Setting contrast - done");
    }
  else
    d(5,"Setting contrast - skipped");
  ctrl.id = V4L2_CID_SATURATION;
  IOCTL(fd, VIDIOC_G_CTRL, &ctrl);
  if (ctrl.value != (int)round(Saturation * 3.76)) {
    ctrl.value = (int)round(Saturation * 3.76);
    if (IOCTL(fd, VIDIOC_S_CTRL, &ctrl)) {
      d(0,"Setting saturation failed");
      return -1;
      }
    else
      d(5,"Setting saturation - done");
    }
  else
    d(5,"Setting saturation - skipped");
  ctrl.id = V4L2_CID_HUE;
  IOCTL(fd, VIDIOC_G_CTRL, &ctrl);
  if (ctrl.value != (int)(round(Hue * 7.5) - 128)) {
    ctrl.value = (int)round(Hue * 7.5) - 128;
    if (IOCTL(fd, VIDIOC_S_CTRL, &ctrl)) {
      d(0,"Setting hue failed");
      return -1;
      }
    else
      d(5,"Setting hue - done");
    }
  else
    d(5,"Setting hue - skipped");
  return 0;
  }

int setVolume(int fd, uint volume) {
  auto struct v4l2_control ctrl;
  if (fd >= 0) {
    ctrl.id = V4L2_CID_AUDIO_MUTE;
    IOCTL(fd, VIDIOC_G_CTRL, &ctrl);
    if (ctrl.value != 0) {
      ctrl.value = 0;
      if (IOCTL(fd, VIDIOC_S_CTRL, &ctrl)) {
        d(0, "setVolume: mute = %d - failed, %d:%s", ctrl.value, errno, strerror(errno));
        return -1;
        }
      else {
        d(1, "setVolume: mute = %d - done.", ctrl.value);
        }
      }
    else
      d(5,"setVolume: mute = %d - skipped.", ctrl.value);
    ctrl.id = V4L2_CID_AUDIO_VOLUME;
    IOCTL(fd, VIDIOC_G_CTRL, &ctrl);
    if (ctrl.value != (int)(volume * 655.35)) {
      ctrl.value = (int)(volume * 655.35); // 0..100 -> 0..65535
      if (ctrl.value > 65535) ctrl.value = 65535;
      if (IOCTL(fd, VIDIOC_S_CTRL, &ctrl)) {
        d(0, "setVolume: volume = %d - failed, %d:%s", volume, errno, strerror(errno));
        return -1;
        }
      else {
        d(1, "setVolume: volume = %d - done.", volume);
        }
      }
    else
      d(5,"setVolume: volume = %d - skipped.", volume);
    }
  else {
    d(0,"setVolume: cannot open device");
    return -1;
    }
  return 0;
  }

int setPVRpids(int fd, int apid, int vpid, int ppid) {
  register int cc = 0;
  auto	   struct ivtv_ioctl_fwapi req;
 d(5,"setPVRpids: apid=%d, vpid=%d, ppid=%d", apid, vpid, ppid);
 if (apid != 300)
    if (fd >= 0) {
      req.cmd = 0x89; // Assigns the transport stream ID of the encoded audio stream
      req.args = 1;
      req.data[0] = apid;
      if (! saveIOCTL(fd, IVTV_IOC_FWAPI, &req)) {
        req.cmd = 0x8b; // Set video transport stream ID
        req.args = 1;
        req.data[0] = vpid;
        if (!IOCTL(fd, IVTV_IOC_FWAPI, &req)) {
          req.cmd = 0x8d; // Assigns the transport stream ID for PCR packets
      	  req.args = 1;
      	  req.data[0] = ppid;
      	  if ((ppid == -1) || !IOCTL(fd, IVTV_IOC_FWAPI, &req))
      	    d(1, "PVR Audio-PID=%d, Video-PID=%d, PCR-PID=%d - done.", apid, vpid, ppid);
          else {
      	    d(0, "Setting of PCR-Pid failed, %d:%s", errno, strerror(errno));
            cc++;
            }
          }
        else {
          d(0, "Setting of Video-Pid failed, %d:%s", errno, strerror(errno));
          cc++;
          }
        }
      else {
        d(0, "Setting of Audio-Pid failed, %d:%s", errno, strerror(errno));
        cc++;
        }
      }
    else {
      d(0, "setPVRPids: Cannot open device: %d:%s", errno, strerror(errno));
      cc++;
      }
  return cc;
  }
