/*
 * analogradio - A plugin for the Video Disk Recorder
 *
 * See the README file for copyright information and how to reach the author.
 */

#include <stdio.h>
#include <stdlib.h>
#include <typeinfo>
#include <sys/ioctl.h>
#include <sys/soundcard.h>
#include <vdr/player.h>
#include <vdr/device.h>

static const int BYTE_PER_PES = 2000; //The maximum for a PES-packet is 2048. With headers this is the maximum, for completely filled packets
static const int LOF = 9750;          //Local oscillator frequency

#include "device.h"
#include "videodev.h"
#include "videodev2.h"
#include "v4l_extension.h"

//-----cAnalogRadioDevice------------------------------------------------------------

cAnalogRadioDevice::cAnalogRadioDevice(char * sDevice, char * sAudioDevice, e_audio_mode eAudioMode, char * sAfter, char * sBefore, analogRadioSetup * arsSettings) {
  source = cSource::FromString("T");
  frequency = -1;
  device = sDevice;
  audio_device = sAudioDevice;
  audio_mode = eAudioMode;
  after = sAfter;
  before = sBefore;
  settings = arsSettings;
}

cAnalogRadioDevice::~cAnalogRadioDevice() {
}

bool cAnalogRadioDevice::ProvidesSource(int Source) const {
  return source == Source;
}

bool cAnalogRadioDevice::ProvidesChannel(const cChannel *Channel, int Priority, bool *NeedsDetachReceivers) const {
  if (ProvidesSource(Channel->Source())) {
    if (Receiving()) {
      if ((frequency == Channel->Frequency()) || (Channel->Frequency() == -1)) {
        *NeedsDetachReceivers = false;
        return true;
      } else {
        *NeedsDetachReceivers = true;
        return (Priority < 0 || Priority > this->Priority());
      }
    } else {
      *NeedsDetachReceivers = false;
      return true;
    }
  } else
    return false;
}

bool cAnalogRadioDevice::SetChannelDevice(const cChannel *Channel, bool LiveView) {
  if (LiveView)
    return false; //No MPEG2-Decoder here

  apid = Channel->Apid(0);

  use_stereo = (Channel->Inversion() == 1);
  use_adr    = (Channel->CoderateH() == 1);
  adr_h      = (Channel->CoderateL() == 1);

  frequency = Channel->Frequency();

  if (use_adr)
    frequency = (frequency - LOF) * 1000;

  return true;
}

bool cAnalogRadioDevice::setModeOSS(void) {
  //Encoding
  audio_encoding = AFMT_S16_BE;
  if (ioctl(audio_fd, SNDCTL_DSP_SETFMT, &audio_encoding) < 0) {
    dsyslog("%s: Could not set sample format (SNDCTL_DSP_SETFMT failed)", __FILE__);
    return false;
  }

  if ((audio_encoding != AFMT_S16_BE) && (audio_encoding != AFMT_S16_LE)) {
    audio_encoding = AFMT_S16_LE; //Ask for 16 bit little endian
    if (ioctl(audio_fd, SNDCTL_DSP_SETFMT, &audio_encoding) < 0) {
      dsyslog("%s: Could not set sample format (SNDCTL_DSP_SETFMT failed)", __FILE__);
      return false;
    }
  }

  switch (audio_encoding) {
    case AFMT_S16_BE: dsyslog("%s: Using AFMT_S16_BE.", __FILE__);
                      break;
    case AFMT_S16_LE: dsyslog("%s: Driver does not support AFMT_S16_BE. Using AFMT_S16_LE.", __FILE__);
                      break;
    default: dsyslog("%s: Driver does not support AFMT_S16_BE or AFMT_S16_LE. Encoding %d set by driver, but not supported.", __FILE__, audio_encoding);
             return false;
  }

  //Stereo/mono
  audio_stereo = 1;
  if (ioctl(audio_fd, SNDCTL_DSP_STEREO, &audio_stereo) < 0) {
    dsyslog("%s: Could not set stereo mode (SNDCTL_DSP_STEREO failed)", __FILE__);
    return false;
  }

  switch (audio_stereo) {
    case 0:  dsyslog("%s: Driver does not support stereo. Using mono.", __FILE__);
             break;
    case 1:  dsyslog("%s: Using stereo.", __FILE__);
             break;
    default: dsyslog("%s: Driver does not support stereo. Stereo mode %d set by driver, but not supported.", __FILE__, audio_stereo);
             return false;
  }

  //Rate
  audio_rate = 48000;
  if (ioctl(audio_fd, SNDCTL_DSP_SPEED, &audio_rate) < 0) {
    dsyslog("%s: Could not set sampling rate (SNDCTL_DSP_SPEED failed)", __FILE__);
    return false;
  }

  if ((audio_rate != 48000) && (audio_rate != 44100) && (audio_rate != 32000)) {
    audio_rate = 44100; //Ask for 44100 Hz
    if (ioctl(audio_fd, SNDCTL_DSP_SPEED, &audio_rate) < 0) {
      dsyslog("%s: Could not set sampling rate (SNDCTL_DSP_SPEED failed)", __FILE__);
      return false;
    }
    if ((audio_rate != 48000) && (audio_rate != 44100) && (audio_rate != 32000)) { //Still no supported rate?
      audio_rate = 32000; //Ask for 32000 Hz
      if (ioctl(audio_fd, SNDCTL_DSP_SPEED, &audio_rate) < 0) {
        dsyslog("%s: Could not set sampling rate (SNDCTL_DSP_SPEED failed)", __FILE__);
        return false;
      }
    }
  }

  switch (audio_rate) {
    case 32000: dsyslog("%s: Driver does not support 48000 Hz. Using 32000 Hz.", __FILE__);
                break;
    case 44100: dsyslog("%s: Driver does not support 48000 Hz. Using 44100 Hz.", __FILE__);
                break;
    case 48000: dsyslog("%s: Using 48000 Hz.", __FILE__);
                break;
    default:    dsyslog("%s: Driver does not support 48000 Hz. %d Hz set by driver, but not supported.", __FILE__, audio_rate);
                return false;
  }

  return true;
}

bool cAnalogRadioDevice::setModeRAW(void) {
  audio_encoding = AFMT_S16_LE;
  audio_stereo = 1;
  audio_rate = 48000;
  return true;
}

bool cAnalogRadioDevice::setModeALSA(void) {
  return true;
}

bool cAnalogRadioDevice::OpenDvr(void) {
  if (before)
    system(before);

  fd = open(device, O_RDONLY);
  if(fd == -1) {
    dsyslog("%s: Could not open device %s", __FILE__, device);
    return false;
  }

  //Check for V4L2
  struct v4l2_capability capability;
  if (ioctl(fd, VIDIOC_QUERYCAP, &capability) < 0) {
    dsyslog("%s: Device does not support V4L2, trying V4L1 (VIDIOC_QUERYCAP failed)", __FILE__);
    v4l2 = false;
  } else
    v4l2 = true;

  //Is the device tuned in 1/16 kHz or MHz steps?
  if (v4l2) {
    struct v4l2_tuner tuner;
    memset(&tuner, 0, sizeof(tuner));
    if (ioctl(fd, VIDIOC_G_TUNER, &tuner) == 0 && (tuner.capability & V4L2_TUNER_CAP_LOW))
      freqfact = 16000;
    else
      freqfact = 16;
  } else { //V4L1
    struct video_tuner tuner;
    memset(&tuner, 0, sizeof(tuner));
    if (ioctl(fd, VIDIOCGTUNER, &tuner) == 0 && (tuner.flags & VIDEO_TUNER_LOW))
      freqfact = 16000;
    else
      freqfact = 16;
  }

  dsyslog("%s: Opened %s with %s", __FILE__, device, (v4l2 ? "V4L2" : "V4L1"));

  if (!unmute())
    return false;
  if (!setFrequency())
    return false;

  audio_fd = open(audio_device, O_RDONLY);
  byte_count = 0;
  ts_count = 0x00;
  timestamp = 0.0f;

  if (audio_fd == -1) {
    dsyslog("%s: Could not open device %s", __FILE__, audio_device);
    return false;
  }

  switch (audio_mode) {
    case AUDIO_MODE_OSS:  if (!setModeOSS())
                            return false;
                          break;
    case AUDIO_MODE_RAW:  if (!setModeRAW())
                            return false;
                          break;
    case AUDIO_MODE_ALSA: if (!setModeALSA())
                            return false;
                          break;
  }
  dsyslog("%s: Opened %s with mode %d", __FILE__, audio_device, audio_mode);

  return true;
}

void cAnalogRadioDevice::CloseDvr(void) {
  close(audio_fd);
  if (after)
    system(after);
  if (settings->muteTuner > 0)
    mute();
  close(fd);
}

bool cAnalogRadioDevice::GetTSPacket(uchar *&Data) {
  int header;
  int max_size;

  //TS-Header-->
  buf[0] = TS_SYNC_BYTE;             //Sync-Byte
  buf[1] = (apid >> 8) & 0x1F;       //APID
  buf[2] = apid & 0xFF;              //Still APID
  buf[3] = (ts_count & 0x0F) | 0x10; //Counter + Payload

  if ((byte_count >= BYTE_PER_PES) || (byte_count == 0)) {
    byte_count = 0;
    header = 28;    //Header-Size
    buf[1] |= 0x40; //PES-Start
    buf[3] |= 0x20; //Adaption-Field
    buf[4] = 0x02;  //3 Stuffing bytes
    buf[5] = 0x00;  //This is meant to make the data size in a packet a multiple of 4
    buf[6] = 0x00;  //By doing so, no sample (16bit stereo -> 4 byte) will be split
  } else {
    header = 4;     //Header-Size
  }

  //<--TS-Header

  //How much data fits into this TS-Packet?
  max_size = BYTE_PER_PES - byte_count;
  if (max_size > (TS_SIZE - header))
    max_size = TS_SIZE - header;

  //Get the data
  int size = 0;
  if (audio_encoding == AFMT_S16_BE) {
    //Audio device supplies little endian - Great!
    size = read(audio_fd, buf + header, max_size);
  } else {
    //Get the data and directly convert from little to big endian
    bool data_left = true;
    while ((max_size > size) && data_left) {
      data_left = (read(audio_fd, buf + header + size + ((size % 2) ? (-1) : 1), 1) == 1);
      if (data_left)
        size++;
    }
  }

  //No data or not enough data from audio device? Drop it!
  //A better way would be, to use a buffer and to be able to create TS-Packets with dynamic stuffing
  if (size < max_size) {
    Data = NULL;
    return true;
  }

  //We have data, so we have a TS-Packet
  ts_count++;
  if (ts_count > 0x0F)
    ts_count = 0x00;

  timestamp += (float(size) / (audio_stereo ? 4 : 2)) / audio_rate * 90000;

  if (header == 28) {
    //PES-Header-->
    buf[7] = 0x00;
    buf[8] = 0x00;
    buf[9] = 0x01;
    buf[10] = 0xBD; //Private Stream 1

    buf[11] = (BYTE_PER_PES + 8 + 7) >> 8;    //PES-Packet size
    buf[12] = (BYTE_PER_PES + 8 + 7) & 0xFF;  //dito

    buf[13] = 0x81;
    buf[14] = 0x80; //timestamp
    buf[15] = 0x05; //5 byte timestamp

    //Timestamp
    int x;
    x = (0x02 << 4) | (((int(timestamp) >> 30) & 0x07) << 1) | 1;
    buf[16] = x;
    x = ((((int(timestamp) >> 15) & 0x7fff) << 1) | 1);
    buf[17] = x >> 8;
    buf[18] = x & 255;
    x = (((int(timestamp) & 0x7fff) << 1) | 1);
    buf[19] = x >> 8;
    buf[20] = x & 255;
    //<--PES-Header

    //PCM-Header-->
    buf[21] = 0xA0; //LPCM
    buf[22] = 0xFF;
    buf[23] = 0x00;
    buf[24] = 0x00;
    buf[25] = 0x00;
    switch (audio_rate) {
      case 32000: buf[26] = 0x30;
                  break;
      case 44100: buf[26] = 0x20;
                  break;
      case 48000: buf[26] = 0x00;
                  break;
    }
    buf[26] |= audio_stereo;
    buf[27] = 0x80;
    //<--PES-Header
  }

  Data = buf;
  byte_count += size;

  return true;
}

bool cAnalogRadioDevice::mute() {
  if (v4l2) {

    struct v4l2_control control;
    control.id = V4L2_CID_AUDIO_MUTE;
    control.value = 1;
    if (ioctl(fd, VIDIOC_S_CTRL, &control) < 0) {
      dsyslog("%s: Could not mute (VIDIOC_S_CTRL failed)", __FILE__);
      return false;
    }

  } else { //V4L1

    struct video_audio vid_aud;
    if (ioctl(fd, VIDIOCGAUDIO, &vid_aud) < 0) {
      dsyslog("%s: Could not mute (VIDIOCGAUDIO failed)", __FILE__);
      return false;
    }
    vid_aud.flags |= VIDEO_AUDIO_MUTE;
    if (ioctl(fd, VIDIOCSAUDIO, &vid_aud) < 0) {
      dsyslog("%s: Could not mute (VIDIOCSAUDIO failed)", __FILE__);
      return false;
    }

  }
  return true;
}

bool cAnalogRadioDevice::unmute() {
  if (v4l2) {

    //Unmute
    struct v4l2_control control;
    control.id = V4L2_CID_AUDIO_MUTE;
    control.value = 0;
    if (ioctl(fd, VIDIOC_S_CTRL, &control) < 0) {
      dsyslog("%s: Could not unmute (VIDIOC_S_CTRL failed)", __FILE__);
      return false;
    }
    //Volume
    control.id = V4L2_CID_AUDIO_VOLUME;
    control.value = int(settings->initVolume * 0xffff / 100.0f);
    if (ioctl(fd, VIDIOC_S_CTRL, &control) < 0) {
      dsyslog("%s: Could not set volume (VIDIOC_S_CTRL failed)", __FILE__);
      return false;
    }
    //Stereo/Mono
    struct v4l2_tuner tuner;
    memset(&tuner, 0, sizeof(tuner));
    if (ioctl(fd, VIDIOC_G_TUNER, &tuner) < 0) {
      dsyslog("%s: Could not set audio mode (VIDIOC_G_TUNER failed)", __FILE__);
      return false;
    }
    tuner.audmode = (use_stereo ? V4L2_TUNER_MODE_STEREO : V4L2_TUNER_MODE_MONO);
    if (ioctl(fd, VIDIOC_S_TUNER, &tuner) < 0) {
      dsyslog("%s: Could not set audio mode (VIDIOC_S_TUNER failed)", __FILE__);
      return false;
    }

  } else { //V4L1

    struct video_audio vid_aud;
    if(ioctl(fd, VIDIOCGAUDIO, &vid_aud) < 0) {
      dsyslog("%s: Could not unmute (VIDIOCGAUDIO failed)", __FILE__);
      return false;
    }
    vid_aud.volume = int(settings->initVolume * 0xffff / 100.0f);
    vid_aud.flags &= ~VIDEO_AUDIO_MUTE;
    vid_aud.mode = (use_stereo ? VIDEO_SOUND_STEREO : VIDEO_SOUND_MONO);
    if (ioctl(fd, VIDIOCSAUDIO, &vid_aud) < 0) {
      dsyslog("%s: Could not unmute (VIDIOCSAUDIO failed)", __FILE__);
      return false;
    }

  }
  return true;
}

bool cAnalogRadioDevice::setFrequency() {
  //Usual analog frequency or transponder frequency for ADR
  int ifreq = int((frequency/1000.0f + .5/freqfact) * freqfact);

  if (v4l2) {

    struct v4l2_frequency v4l2_freq;
    v4l2_freq.tuner = 0;
    v4l2_freq.frequency = ifreq;
    if (ioctl(fd, VIDIOC_S_FREQUENCY, &v4l2_freq) < 0) {
      dsyslog("%s: Could not set frequency to %d (VIDIOC_S_FREQUENCY failed)", __FILE__, frequency);
      return false;
    }

  } else { //V4L1

    if(ioctl(fd, VIDIOCSFREQ, &ifreq) < 0) {
      dsyslog("%s: Could not set frequency to %d (VIDIOCSFREQ failed)", __FILE__, frequency);
      return false;
    }

    if (use_adr) {
      //ADR antenna
      struct video_antenna adr_antenna;
      adr_antenna.settings = (adr_h ? VIDEO_ANTENNA_1814V : 0);
      adr_antenna.diseqc[0] = 0;
      if(ioctl(fd, VIDIOCSANTENNA, &adr_antenna) < 0) {
        dsyslog("%s: Could not set ADR antenna (VIDIOCSANTENNA failed)", __FILE__);
        return false;
      }

      //Enable ADR
      struct video_adr adr_mode;
      adr_mode.mode = VIDEO_ADR_ON;
      if(ioctl(fd, VIDIOCSADR, &adr_mode) < 0) {
        dsyslog("%s: Could not set ADR mode (VIDIOCSADR failed)", __FILE__);
        return false;
      }

      //ADR carrier frequency
      struct video_audio_carrier adr_carrier;
      adr_carrier.l_freq = adr_carrier.r_freq = apid;
      adr_carrier.deemphasis = 0;
      if (ioctl(fd, VIDIOCSAUDIOCARRIER, &adr_carrier) < 0){
        dsyslog("%s: Could not set ADR carrier frequency to %d (VIDIOCSAUDIOCARRIER failed)", __FILE__, adr_carrier.l_freq);
        return false;
      }
    }

  }
  return true;
}
