/*
 * param.c: SAT>IP plugin for the Video Disk Recorder
 *
 * See the README file for copyright information and how to reach the author.
 *
 */

#include <ctype.h>
#include <vdr/dvbdevice.h>
#include "common.h"
#include "param.h"

// --- cSatipParameterMaps ----------------------------------------------------

struct tSatipParameterMap {
  int         driverValue;
  const char *satipString;
};

static const tSatipParameterMap SatipBandwidthValues[] = {
  {  5000000, "&bw=5"     },
  {  6000000, "&bw=6"     },
  {  7000000, "&bw=7"     },
  {  8000000, "&bw=8"     },
  { 10000000, "&bw=10"    },
  {  1712000, "&bw=1.712" },
  {       -1, NULL        }
};

static const tSatipParameterMap SatipPilotValues[] = {
  {  PILOT_OFF, "&plts=off" },
  {   PILOT_ON, "&plts=on"  },
  { PILOT_AUTO, ""          },
  {         -1, NULL        }
};

static const tSatipParameterMap SatipSisoMisoValues[] = {
  {   0, "&sm=0" },
  {   1, "&sm=1" },
  {  -1, NULL    }
};

static const tSatipParameterMap SatipCodeRateValues[] = {
  { FEC_NONE, ""         },
  {  FEC_1_2, "&fec=12"  },
  {  FEC_2_3, "&fec=23"  },
  {  FEC_3_4, "&fec=34"  },
  {  FEC_3_5, "&fec=35"  },
  {  FEC_4_5, "&fec=45"  },
  {  FEC_5_6, "&fec=56"  },
  {  FEC_6_7, "&fec=67"  },
  {  FEC_7_8, "&fec=78"  },
  {  FEC_8_9, "&fec=89"  },
  { FEC_9_10, "&fec=910" },
  { FEC_AUTO, ""         },
  {       -1, NULL       }
};

static const tSatipParameterMap SatipModulationValues[] = {
  {     QPSK, "&mtype=qpsk"   },
  {    PSK_8, "&mtype=8psk"   },
  {  APSK_16, "&mtype=16apsk"  },
  {  APSK_32, "&mtype=32apsk"  },
  {    VSB_8, "&mtype=8vsb"   },
  {   VSB_16, "&mtype=16vsb"  },
  {   QAM_16, "&mtype=16qam"  },
  {   QAM_64, "&mtype=64qam"  },
  {  QAM_128, "&mtype=128qam" },
  {  QAM_256, "&mtype=256qam" },
  { QAM_AUTO, ""              },
  {       -1, NULL            }
};

static const tSatipParameterMap SatipSystemValuesSat[] = {
  {   0, "&msys=dvbs"  },
  {   1, "&msys=dvbs2" },
  {  -1, NULL          }
};

static const tSatipParameterMap SatipSystemValuesTerrestrial[] = {
  {   0, "&msys=dvbt"  },
  {   1, "&msys=dvbt2" },
  {  -1, NULL          }
};

static const tSatipParameterMap SatipSystemValuesCable[] = {
  {   0, "&msys=dvbc"  },
  {   1, "&msys=dvbc2" },
  {  -1, NULL          }
};

static const tSatipParameterMap SatipSystemValuesAtsc[] = {
  {   0, "&msys=atsc" },
  {  -1, NULL         }
};

static const tSatipParameterMap SatipTransmissionValues[] = {
  {   TRANSMISSION_MODE_1K, "&tmode=1k"  },
  {   TRANSMISSION_MODE_2K, "&tmode=2k"  },
  {   TRANSMISSION_MODE_4K, "&tmode=4k"  },
  {   TRANSMISSION_MODE_8K, "&tmode=8k"  },
  {  TRANSMISSION_MODE_16K, "&tmode=16k" },
  {  TRANSMISSION_MODE_32K, "&tmode=32k" },
  { TRANSMISSION_MODE_AUTO, ""           },
  {                     -1, NULL         }
};

static const tSatipParameterMap SatipGuardValues[] = {
  {    GUARD_INTERVAL_1_4, "&gi=14"    },
  {    GUARD_INTERVAL_1_8, "&gi=18"    },
  {   GUARD_INTERVAL_1_16, "&gi=116"   },
  {   GUARD_INTERVAL_1_32, "&gi=132"   },
  {  GUARD_INTERVAL_1_128, "&gi=1128"  },
  { GUARD_INTERVAL_19_128, "&gi=19128" },
  { GUARD_INTERVAL_19_256, "&gi=19256" },
  {   GUARD_INTERVAL_AUTO, ""          },
  {                    -1, NULL        }
};

static const tSatipParameterMap SatipRollOffValues[] = {
  { ROLLOFF_AUTO, ""         },
  {   ROLLOFF_20, "&ro=0.20" },
  {   ROLLOFF_25, "&ro=0.25" },
  {   ROLLOFF_35, "&ro=0.35" },
  {           -1, NULL       }
};

static const tSatipParameterMap SatipInversionValues[] = {
  { INVERSION_AUTO, ""           },
  {  INVERSION_OFF, "&specinv=0" },
  {   INVERSION_ON, "&specinv=1" },
  {             -1, NULL         }
};

static int SatipUserIndex(int valueP, const tSatipParameterMap *mapP)
{
  const tSatipParameterMap *map = mapP;
  while (map && map->driverValue != -1) {
        if (map->driverValue == valueP)
           return map - mapP;
        map++;
        }
  return -1;
}

static int PrintUrlString(char *bufP, int lenP, int valueP, const tSatipParameterMap *mapP)
{
  int n = SatipUserIndex(valueP, mapP);
  return ((n >= 0) && (lenP > 0)) ? snprintf(bufP, lenP, "%s", mapP[n].satipString) : 0;
}

cString GetTransponderUrlParameters(const cChannel *channelP)
{
  if (channelP) {
     char buffer[255];
     cDvbTransponderParameters dtp(channelP->Parameters());
     int DataSlice = 0;
     int C2TuningFrequencyType = 0;
     float freq = channelP->Frequency();
     char type = cSource::ToChar(channelP->Source());
     cSource *source = Sources.Get(channelP->Source());
     int src = (strchr("S", type) && source) ? atoi(source->Description()) : 1;
     char *q = buffer;
     *q = 0;
     // Scale down frequencies to MHz
     while (freq > 20000L)
           freq /= 1000L;
#define ST(s) if (strchr(s, type) && (strchr(s, '0' + dtp.System() + 1) || strchr(s, '*')))
#define STBUFLEFT (sizeof(buffer) - (q - buffer))
     ST(" S 1") { // to comply with SAT>IP protocol specification 1.2.2
       dtp.SetPilot(PILOT_OFF);
       dtp.SetModulation(QPSK);
       dtp.SetRollOff(ROLLOFF_35);
       }
     if ((channelP->Rid() % 100) > 0)
                q += snprintf(q,       STBUFLEFT, "&fe=%d",           channelP->Rid() % 100);
     ST("  S *") q += snprintf(q,       STBUFLEFT, "&src=%d",          ((src > 0) && (src <= 255)) ? src : 1);
     if (freq >= 0L)
                q += snprintf(q,       STBUFLEFT, "&freq=%s",         *dtoa(freq, "%lg"));
     ST("  S *") q += snprintf(q,       STBUFLEFT, "&pol=%c",          tolower(dtp.Polarization()));
     ST("  S *") q += PrintUrlString(q, STBUFLEFT, dtp.RollOff(),      SatipRollOffValues);
     ST(" C  2") q += snprintf(q,       STBUFLEFT, "&c2tft=%d",        C2TuningFrequencyType);
     ST("   T*") q += PrintUrlString(q, STBUFLEFT, dtp.Bandwidth(),    SatipBandwidthValues);
     ST(" C  2") q += PrintUrlString(q, STBUFLEFT, dtp.Bandwidth(),    SatipBandwidthValues);
     ST("  S *") q += PrintUrlString(q, STBUFLEFT, dtp.System(),       SatipSystemValuesSat);
     ST(" C  *") q += PrintUrlString(q, STBUFLEFT, dtp.System(),       SatipSystemValuesCable);
     ST("   T*") q += PrintUrlString(q, STBUFLEFT, dtp.System(),       SatipSystemValuesTerrestrial);
     ST("A   *") q += PrintUrlString(q, STBUFLEFT, dtp.System(),       SatipSystemValuesAtsc);
     ST("   T*") q += PrintUrlString(q, STBUFLEFT, dtp.Transmission(), SatipTransmissionValues);
     ST("  S *") q += PrintUrlString(q, STBUFLEFT, dtp.Modulation(),   SatipModulationValues);
     ST("   T*") q += PrintUrlString(q, STBUFLEFT, dtp.Modulation(),   SatipModulationValues);
     ST(" C  1") q += PrintUrlString(q, STBUFLEFT, dtp.Modulation(),   SatipModulationValues);
     ST("A   *") q += PrintUrlString(q, STBUFLEFT, dtp.Modulation(),   SatipModulationValues);
     ST("  S *") q += PrintUrlString(q, STBUFLEFT, dtp.Pilot(),        SatipPilotValues);
     ST("  S *") q += snprintf(q,       STBUFLEFT, "&sr=%d",           channelP->Srate());
     ST(" C  1") q += snprintf(q,       STBUFLEFT, "&sr=%d",           channelP->Srate());
     ST("   T*") q += PrintUrlString(q, STBUFLEFT, dtp.Guard(),        SatipGuardValues);
     ST(" CST*") q += PrintUrlString(q, STBUFLEFT, dtp.CoderateH(),    SatipCodeRateValues);
     ST(" C  2") q += snprintf(q,       STBUFLEFT, "&ds=%d",           DataSlice);
     ST(" C T2") q += snprintf(q,       STBUFLEFT, "&plp=%d",          dtp.StreamId());
     ST("   T2") q += snprintf(q,       STBUFLEFT, "&t2id=%d",         dtp.T2SystemId());
     ST("   T2") q += PrintUrlString(q, STBUFLEFT, dtp.SisoMiso(),     SatipSisoMisoValues);
     ST(" C  1") q += PrintUrlString(q, STBUFLEFT, dtp.Inversion(),    SatipInversionValues);
     ST("A   *") q += PrintUrlString(q, STBUFLEFT, dtp.Inversion(),    SatipInversionValues);
#undef ST
     return &buffer[1];
     }
  return NULL;
}

cString GetTnrUrlParameters(const cChannel *channelP)
{
  if (channelP) {
     cDvbTransponderParameters dtp(channelP->Parameters());
     eTrackType track = cDevice::PrimaryDevice()->GetCurrentAudioTrack();

     // TunerType: Byte;
     //   0 = cable, 1 = satellite, 2 = terrestrial, 3 = atsc, 4 = iptv, 5 = stream (URL, DVBViewer GE)
     int TunerType = 0;
     if (channelP->IsCable())
        TunerType = 0;
     else if (channelP->IsSat())
        TunerType = 1;
     else if (channelP->IsTerr())
        TunerType = 2;
     else if (channelP->IsAtsc())
        TunerType = 3;

     // Frequency: DWord;
     //   DVB-S: MHz if < 1000000, kHz if >= 1000000
     //   DVB-T/C, ATSC: kHz
     //   IPTV: IP address Byte3.Byte2.Byte1.Byte0
     int Frequency = channelP->Frequency() / 1000;

     // Symbolrate: DWord;
     //   DVB S/C: in kSym/s
     //   DVB-T, ATSC: 0
     //   IPTV: Port
     int Symbolrate = (channelP->IsSat() || channelP->IsCable()) ? channelP->Srate() : 0;

     // LNB_LOF: Word;
     //   DVB-S: Local oscillator frequency of the LNB
     //   DVB-T/C, ATSC: 0
     //   IPTV: Byte0 and Byte1 of Source IP
     int LNB_LOF = channelP->IsSat() ? Setup.LnbSLOF : 0;

     // Tone: Byte;
     //   0 = off, 1 = 22 khz
     int Tone = (channelP->Frequency() < Setup.LnbSLOF) ? 0 : 1;

     // Polarity: Byte;
     //   DVB-S polarity: 0 = horizontal, 1 = vertical, 2 = circular left, 3 = circular right
     //   DVB-C modulation: 0 = Auto, 1 = 16QAM, 2 = 32QAM, 3 = 64QAM, 4 = 128QAM, 5 = 256 QAM
     //   DVB-T bandwidth: 0 = 6 MHz, 1 = 7 MHz, 2 = 8 MHz
     //   IPTV: Byte3 of SourceIP
     int Polarity = 0;
     if (channelP->IsSat()) {
        switch (tolower(dtp.Polarization())) {
          case 'h':
               Polarity = 0;
               break;
          case 'v':
               Polarity = 1;
               break;
          case 'l':
               Polarity = 2;
               break;
          case 'r':
               Polarity = 3;
               break;
          default:
               break;
          }
        }
     else if (channelP->IsCable()) {
        switch (dtp.Modulation()) {
          case 999:
               Polarity = 0;
               break;
          case 16:
               Polarity = 1;
               break;
          case 32:
               Polarity = 2;
               break;
          case 64:
               Polarity = 3;
               break;
          case 128:
               Polarity = 4;
               break;
          case 256:
               Polarity = 5;
               break;
          default:
               break;
          }
        }
     else if (channelP->IsTerr()) {
        switch (dtp.Bandwidth()) {
          case 6:
               Polarity = 0;
               break;
          case 7:
               Polarity = 1;
               break;
          case 8:
               Polarity = 2;
               break;
          default:
               break;
          }
        }

     // DiSEqC: Byte;
     //   0 = None
     //   1 = Pos A (mostly translated to PosA/OptA)
     //   2 = Pos B (mostly translated to PosB/OptA)
     //   3 = PosA/OptA
     //   4 = PosB/OptA
     //   5 = PosA/OptB
     //   6 = PosB/OptB
     //   7 = Preset Position (DiSEqC 1.2, see DiSEqCExt)
     //   8 = Angular Position (DiSEqC 1.2, see DiSEqCExt)
     //   9 = DiSEqC Command Sequence (see DiSEqCExt)
     int DiSEqC = 0;

     // FEC: Byte;
     //   0 = Auto
     //   1 = 1/2
     //   2 = 2/3
     //   3 = 3/4
     //   4 = 5/6
     //   5 = 7/8
     //   6 = 8/9
     //   7 = 3/5
     //   8 = 4/5
     //   9 = 9/10
     //   IPTV: Byte2 of SourceIP
     //   DVB C/T, ATSC: 0
     int FEC = 0;
     if (channelP->IsSat()) {
        switch (dtp.CoderateH()) {
          case 999:
               FEC = 0;
               break;
          case 12:
               FEC = 1;
               break;
          case 23:
               FEC = 2;
               break;
          case 34:
               FEC = 3;
               break;
          case 56:
               FEC = 4;
               break;
          case 78:
               FEC = 5;
               break;
          case 89:
               FEC = 6;
               break;
          case 35:
               FEC = 7;
               break;
          case 45:
               FEC = 8;
               break;
          case 910:
               FEC = 9;
               break;
          default:
               break;
          }
        }

     // Audio_PID: Word;
     int Audio_PID = channelP->Apid(0);
     if (IS_AUDIO_TRACK(track))
        Audio_PID = channelP->Apid(int(track - ttAudioFirst));
     else if (IS_DOLBY_TRACK(track))
        Audio_PID = channelP->Dpid(int(track - ttDolbyFirst));

     // Video_PID: Word;
     int Video_PID = channelP->Vpid();

     // PMT_PID: Word;
     int PMT_PID = channelP->Ppid();

     // Service_ID: Word;
     int Service_ID = channelP->Sid();

     // SatModulation: Byte;
     //   Bit 0..1: satellite modulation. 0 = Auto, 1 = QPSK, 2 = 8PSK, 3 = 16QAM or APSK for DVB-S2
     //   Bit 2: modulation system. 0 = DVB-S/T/C, 1 = DVB-S2/T2/C2
     //   Bit 3..4: DVB-S2: roll-off. 0 = 0.35, 1 = 0.25, 2 = 0.20, 3 = reserved
     //   Bit 5..6: spectral inversion, 0 = undefined, 1 = auto, 2 = normal, 3 = inverted
     //   Bit 7: DVB-S2: pilot symbols, 0 = off, 1 = on
     //          DVB-T2: DVB-T2 Lite, 0 = off, 1 = on
     int SatModulation = 0;
     if (channelP->IsSat() && dtp.System()) {
        switch (dtp.Modulation()) {
          case 999:
               SatModulation |= (0 & 0x3) << 0;
               break;
          case 2:
               SatModulation |= (1 & 0x3) << 0;
               break;
          case 5:
               SatModulation |= (2 & 0x3) << 0;
               break;
          case 6:
               SatModulation |= (3 & 0x3) << 0;
               break;
          default:
               break;
          }
        }
     SatModulation |= (dtp.System() & 0x1) << 2;
     if (channelP->IsSat() && dtp.System()) {
        switch (dtp.RollOff()) {
          case 35:
               SatModulation |= (0 & 0x3) << 3;
               break;
          case 25:
               SatModulation |= (1 & 0x3) << 3;
               break;
          case 20:
               SatModulation |= (2 & 0x3) << 3;
               break;
          default:
               break;
          }
        }
     switch (dtp.Inversion()) {
       case 999:
           SatModulation |= (1 & 0x3) << 5;
           break;
       case 0:
           SatModulation |= (2 & 0x3) << 5;
           break;
       case 1:
           SatModulation |= (3 & 0x3) << 5;
           break;
       default:
           break;
       }
     if (channelP->IsSat() && dtp.System()) {
        switch (dtp.Pilot()) {
          case 0:
               SatModulation |= (0 & 0x1) << 7;
               break;
          case 1:
               SatModulation |= (1 & 0x1) << 7;
               break;
          default:
               break;
          }
        }

     // DiSEqCExt: Word;
     //   DiSEqC Extension, meaning depends on DiSEqC
     //   DiSEqC = 0..6: 0
     //   DiSEqC = 7: Preset Position (DiSEqC 1.2)
     //   DiSEqC = 8: Orbital Position (DiSEqC 1.2, USALS, for calculating motor angle)
     //               Same format as OrbitalPos above
     //   DiSEQC = 9: Orbital Position referencing DiSEqC sequence defined in DiSEqC.xml/ini
     //               Same format as OrbitalPos above
     int DiSEqCExt = 0;

     // Flags: Byte;
     //   Bit 0: 1 = encrypted channel
     //   Bit 1: reserved, set to 0
     //   Bit 2: 1 = channel broadcasts RDS data
     //   Bit 3: 1 = channel is a video service (even if the Video PID is temporarily = 0)
     //   Bit 4: 1 = channel is an audio service (even if the Audio PID is temporarily = 0)
     //   Bit 5: 1 = audio has a different samplerate than 48 KHz
     //   Bit 6: 1 = bandstacking, internally polarisation is always set to H
     //   Bit 7: 1 = channel entry is an additional audio track of the preceding
     //              channel with bit 7 = 0
     int Flags = (channelP->Ca() > 0xFF) ? 1 : 0;

     // ChannelGroup: Byte;
     //   0 = Group A, 1 = Group B, 2 = Group C etc.
     int ChannelGroup = 0;

     // TransportStream_ID: Word;
     int TransportStream_ID = channelP->Tid();

     // OriginalNetwork_ID: Word;
     int OriginalNetwork_ID = channelP->Nid();

     // Substream: Word;
     //   DVB-S/C/T, ATSC, IPTV: 0
     //   DVB-T2: 0 = PLP_ID not set, 1..256: PLP_ID + 1, 257... reserved
     int Substream = (channelP->IsTerr() && dtp.System()) ? dtp.StreamId() - 1 : 0;

     // OrbitalPos: Word;
     //   DVB-S: orbital position x 10, 0 = undefined, 1..1800 east, 1801..3599 west (1°W = 3599)
     //   DVB-C: 4000..4999
     //   DVB-T: 5000..5999
     //   ATSC:  6000..6999
     //   IPTV:  7000..7999
     //   Stream: 8000..8999
     int OrbitalPos = 0;
     if (channelP->IsSat()) {
        OrbitalPos = cSource::Position(channelP->Source());
        if (OrbitalPos != 3600)
           OrbitalPos += 1800;
        }

     return cString::sprintf("%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d",
                             TunerType, Frequency, Symbolrate, LNB_LOF, Tone, Polarity, DiSEqC, FEC, Audio_PID, Video_PID, PMT_PID, Service_ID,
                             SatModulation, DiSEqCExt, Flags, ChannelGroup, TransportStream_ID, OriginalNetwork_ID, Substream, OrbitalPos);
     }
  return NULL;
}
