/*
 * Analogue TV Player plugin for VDR
 *
 * Copyright (C) 2003, 2005 Andreas Kool <akool@gmx.de>
 *
 * This code is distributed under the terms and conditions of the
 * GNU GENERAL PUBLIC LICENSE. See the file COPYING for details.
 *
 * Version: $Id: pvr.c,v 1.17 2005/01/09 18:16:09 akool Exp $
 * Release: $Name:  $
 *
 */

#include <unistd.h>
#include <features.h> /* Uses _GNU_SOURCE to define getsubopt in stdlib.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 <sys/ioctl.h>
#include <math.h>


#include "player-analogtv.h"
#include "ivtv.h"
#include "msg.h"
#include "pvr.h"

#define saveIOCTL(fd, cmd, data)                                        \
({                                                                      \
  register int cc = 0;                                                  \
  auto 	   int num_of_retries = 20, retries = 0;                        \
  auto 	   int wait = 100 * 1000;                                       \
                                                                        \
  do {                                                                  \
    cc = IOCTL(fd, cmd, data);                                          \
                                                                        \
    if (cc) {                                                       	\
      if (retries++ < num_of_retries) {                                 \
        fprintf(stderr, "Retrying it after sleeping some time (try %2d)\033[K\r", retries); \
	usleep(wait);  	  		      	   	      		\
      }                                                                 \
      else {                                                            \
        d(0, "Giving up after %d retries", retries - 1);                \
        break;                                                          \
      }                                                                 \
    }                                                                   \
  } while (cc);                                                     	\
                                                                        \
  cc;                                                                   \
})

int setPVR(int opts, int input, int freq, int ofs, int norm, int brightness, int contrast, int colour, int hue)
{
  register int cc = 0, cc2 = 0, cc3, f = -1, retry;
  auto struct ivtv_ioctl_codec codec;
  auto struct v4l2_format      vfmt;
  auto struct v4l2_frequency   vf;
  auto 	      v4l2_std_id      std;
  auto struct v4l2_control     ctrl;

  if (opts)
    if ((f = open(video_device, O_RDWR)) >= 0) {

      if (opts & STANDARD) { // _zuerst_ TV-Norm, sonst gibts "ivtv: EOS interrupt not received! stopping anyway."
        switch (norm) {
          case 0 : std = V4L2_STD_PAL;   break;
      	  case 1 : std = V4L2_STD_NTSC;  break;
      	  case 2 : std = V4L2_STD_SECAM; break;
        }

        if (IOCTL(f, VIDIOC_S_STD, &std)) {
      	  d(0, "VIDIOC_S_STD failed, %d:%s", errno, strerror(errno));
          cc++;
        }
        else
          d(1, "PVR Norm=%d:%s - done.", norm, tvnorms[norm]);
      }

      if (opts & CODEC) {
        if (!IOCTL(f, IVTV_IOC_G_CODEC, &codec)) {
          // for PES, we now uses IVTV_STREAM_DVD_S2, not IVTV_STREAM_PS (XP MediaCenter edition uses it too)
          codec.stream_type  = (s.mpeg_encoder == PVRTS) ? IVTV_STREAM_TS : IVTV_STREAM_DVD_S2;
      	  codec.aspect       = s.aspect + 1;
	  codec.pulldown     = s.pulldown;
      	  codec.bitrate      = (uint32_t)(s.video_bit_rate * 1e5);
      	  // codec.bitrate_peak = (uint32_t)(s.video_bit_rate * 1e5 * 1.5);
      	  codec.bitrate_peak = (uint32_t)15000000; //XXX AK:like Premiere
      	  codec.bitrate_mode = s.bitrate_mode;

      	  d(1, "PVR MPEG-%s, aspect %s, %s%s",
            (s.mpeg_encoder == PVRTS) ? "TS" : "PES",
            aspects[s.aspect],
            bitratemodes[codec.bitrate_mode],
            s.pulldown ? "" : ", 3:2 pulldown");

      	  d(1, "PVR Videobitrate=%3.1g Mbit/s (max=%3.1g Mbit/s)",
            (double)codec.bitrate / 1000.0 / 1000.0,
            (double)codec.bitrate_peak / 1000.0 / 1000.0);

          codec.framespergop = (videonorm == 1) ? 15 : 12; //XXX s.framespergop;
      	  codec.bframes      = s.bframes;
      	  codec.gop_closure  = s.gop_closure;

      	  d(1, "PVR %d frames/GOP, %d B-frames, GOP %s",
            codec.framespergop, codec.bframes, closures[codec.gop_closure]);

          codec.dnr_mode     = s.dnr_mode;
      	  codec.dnr_type     = s.dnr_type;
      	  codec.dnr_spatial  = s.dnr_spatial;
      	  codec.dnr_temporal = s.dnr_temporal;

          d(1, "PVR DNR:%s (type=%d, spatial=%d, temporal=%d)",
            dnr_modes[codec.dnr_mode], codec.dnr_type, codec.dnr_spatial, codec.dnr_temporal);

          switch (s.audio_bit_rate) {
            case   0 ...  32 : codec.audio_bitmask = 0x19; break;
            case  33 ...  48 : codec.audio_bitmask = 0x29; break;
            case  49 ...  56 : codec.audio_bitmask = 0x39; break;
            case  57 ...  64 : codec.audio_bitmask = 0x49; break;
            case  65 ...  80 : codec.audio_bitmask = 0x59; break;
            case  81 ...  96 : codec.audio_bitmask = 0x69; break;
            case  97 ... 112 : codec.audio_bitmask = 0x79; break;
            case 113 ... 128 : codec.audio_bitmask = 0x89; break;
            case 129 ... 160 : codec.audio_bitmask = 0x99; break;
            case 161 ... 192 : codec.audio_bitmask = 0xa9; break;
            case 193 ... 224 : codec.audio_bitmask = 0xb9; break;
            case 225 ... 256 : codec.audio_bitmask = 0xc9; break;
            case 257 ... 320 : codec.audio_bitmask = 0xd9; break;
            default          : codec.audio_bitmask = 0xe9; break;
          }

      	  codec.framerate    = !videonorm ? 1 : 0;

      	  d(1, "PVR Audiobitrate=%d kBit/s (Mask=0x%x)", s.audio_bit_rate, codec.audio_bitmask);

	  if (IOCTL(f, IVTV_IOC_S_CODEC, &codec)) {
      	    d(0, "IVTV_IOC_S_CODEC failed, %d:%s", errno, strerror(errno));
            cc++;
          }
          else {
      	    d(1, "PVR MPEG-Encoder parameter(s) - done.");
    	    Assign(2);
          }
        }
      }

      if (opts & FORMAT) {
        if (!IOCTL(f, VIDIOC_G_FMT, &vfmt)) {
          sscanf(imagesizes[s.image_size], "%ix%i", &vfmt.fmt.pix.width, &vfmt.fmt.pix.height);

          if (IOCTL(f, VIDIOC_S_FMT, &vfmt)) {
      	    d(0, "VIDIOC_S_FMT failed, %d:%s", errno, strerror(errno));
            cc++;
          }
          else {
            d(1, "PVR Picture size=%dx%d - done.", vfmt.fmt.pix.width, vfmt.fmt.pix.height);
    	    Assign(3);
          }
        }
      }

#if 0
      if (opts & REGISTER)
        cc +=!IOCTL(f, SAA7115_SET_REG, &saa7115_reg);
#endif

      if (opts & INPUT) {
	if (s.enabledigibox && isPVR && (input == 1))
	  input = 0;

        if (IOCTL(f, VIDIOC_S_INPUT, &input)) {
      	  d(0, "VIDIOC_S_INPUT failed, %d:%s", errno, strerror(errno));
          cc++;
        }
      	else
          d(1, "PVR Input=%d:%s - done.", input, sources[input]);
      }

      if (opts & FREQUENCY) {
        ofs = (ofs - 16) * FREQDELTA; // 16 is "ground zero"

        freq = (freq + ofs) * 16 / 1000;

        memset(&vf, 0, sizeof(f));
        vf.tuner = analogModule; // FIXME??
        vf.type = V4L2_TUNER_ANALOG_TV;
        vf.frequency = freq;

        if (IOCTL(f, VIDIOC_S_FREQUENCY, &vf)) {
      	  d(0, "VIDIOC_S_FREQUENCY failed, %d:%s", errno, strerror(errno));
          cc++;
        }
        else
          d(1, "PVR Frequency=%d - done.", freq);

      }

      // FIXME: Hier _war_  "if (opts & STANDARD) {" lt. test_ioctl ...

#if 0
      if (opts & MATRIX) {
        if ((mspm.input < 9) & (mspm.output < 3))
          cc += IOCTL(f, MSP_SET_MATRIX, &mspm);
      }
#endif

      if (opts & CONTROL) {

        cc2 = 0;

        d(1, "PVR brightness=%d (%.1f%%) contrast=%d (%.1f%%) colour=%d (%.1f%%) hue=%d (%.1f%%)",
          brightness, (brightness / 34.0 * 100.0),
          contrast, (contrast / 34.0 * 100.0),
          colour, (colour / 34.0 * 100.0),
          hue, (hue / 34.0 * 100));

        ctrl.id = V4L2_CID_BRIGHTNESS;
        ctrl.value = (int)round(brightness * 7.5);
        cc2 += IOCTL(f, VIDIOC_S_CTRL, &ctrl);

        ctrl.id = V4L2_CID_CONTRAST;
        ctrl.value = (int)round(contrast * 3.76);
        cc2 += IOCTL(f, VIDIOC_S_CTRL, &ctrl);

        ctrl.id = V4L2_CID_SATURATION;
        ctrl.value = (int)round(colour * 3.76);
        cc2 += IOCTL(f, VIDIOC_S_CTRL, &ctrl);

        ctrl.id = V4L2_CID_HUE;
        ctrl.value = (int)round(hue * 7.5) - 128;
        cc2 += IOCTL(f, VIDIOC_S_CTRL, &ctrl);

        if (cc2) {
      	  d(0, "VIDIOC_S_CTRL failed, %d:%s", errno, strerror(errno));

          if (s.propHack)
            d(1, "setPictureProperties(%d,%d,%d,%d) FAILED - continuing anyway ...", s.brightness, s.contrast, s.colour, s.hue);
          else
            cc++;
        }
        else {
          d(1, "PVR saa7115 brightness=%d contrast=%d colour=%d hue=%d - done.",
	    (int)round(brightness * 7.5), (int)round(contrast * 3.76), (int)round(colour * 3.76), (int)round(hue * 7.5) - 128);
          Assign(0);
        }
      }

      if (opts & VOLUME) {
        cc2 = 0;

        for (retry = 0; retry < 2; retry++) {
          ctrl.id = V4L2_CID_AUDIO_VOLUME;
          ctrl.value = (int)(s.mixer_volume * 655.35); // 0..100 -> 0..65535

          cc3 = IOCTL(f, VIDIOC_S_CTRL, &ctrl);

          if (cc3 && ((errno == EINVAL) || (errno == ERANGE))) {
            d(0, "PVR Invalid mixer volume '%d' in your '%s/setup.conf' -- retrying with default value '91'",
              s.mixer_volume, confdir);

            s.mixer_volume = 91;
          }
          else
            retry = 99;
        }

        if (cc3)
      	  d(0, "VIDIOC_S_CTRL failed, %d:%s", errno, strerror(errno));
        else
          d(1, "PVR Volume=%d - done.", (int)(s.mixer_volume * 655.35));

        cc2 += cc3;

        ctrl.id = V4L2_CID_AUDIO_MUTE;
        ctrl.value = 0;
        cc2 += IOCTL(f, VIDIOC_S_CTRL, &ctrl);

        if (cc2) {
      	  d(0, "VIDIOC_S_CTRL failed, %d:%s", errno, strerror(errno));
          cc++;
        }
        else
          Assign(4);
      }

      close(f);

      if (!cc && s.winingHack)
        usleep(PVRSLEEP); // this fixes the "wining" sound problem
    }
    else {
      d(0, "Cannot open %s, %d:%s", video_device, errno, strerror(errno));
      cc++;
    }

  return cc;
}

int setPVRpids(int apid, int vpid, int ppid)
{
  register int cc = 0, f = -1;
  auto	   struct ivtv_ioctl_fwapi req;

  if ((vpid != 301) && (apid != 300))
    if ((f = open(video_device, O_RDWR)) >= 0) {
      req.cmd = 0x89; // Assigns the transport stream ID of the encoded audio stream
      req.args = 1;
      req.data[0] = apid;

      if (!saveIOCTL(f, IVTV_IOC_FWAPI, &req)) {
        req.cmd = 0x8b; // Set video transport stream ID
        req.args = 1;
        req.data[0] = vpid;

        if (!IOCTL(f, 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(f, 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++;
      }

      close(f);
    }
    else {
      d(0, "Cannot open %s, %d:%s", video_device, errno, strerror(errno));
      cc++;
    }

  return cc;
}

int setPassThrough(int on)
{
  register int cc = 0, f = -1;

  if ((f = open(video_device, O_RDWR)) >= 0) {
    if (saveIOCTL(f, IVTV_IOC_PASSTHROUGH, &on)) {
      d(0, "Cannot set PassThrough mode to %s, %d:%s",
        on ? "ON" : "OFF", errno, strerror(errno));
      cc++;
    }
    else
      d(1, "PVR PassThrough=%s - done.", on ? "ON" : "OFF");

  }
  else {
    d(0, "Cannot open %s, %d:%s", video_device, errno, strerror(errno));
    cc++;
  }

  return cc;
}
