/*
 * csmenu.c channelscan menu
 * writen by reel-multimedia
 *
 * See README for copyright information and
 * how to reach the authors.
 *
 */

#include <iostream>
#include <vector>
#include <string>
#include <linux/dvb/frontend.h> 
#include <sys/ioctl.h> 
#include <vdr/channels.h>
#include <vdr/device.h>
#include <vdr/diseqc.h>
#include <vdr/menu.h>
#include <vdr/menuitems.h>
#include <vdr/tools.h>
#include <vdr/osdbase.h>
#include <vdr/status.h>
#include <vdr/menu.h>
#include "csmenu.h"
#include "filter.h"


#define CHNUMWIDTH 16
#define DVBFRONTEND "/dev/dvb/adapter%d/frontend0"
#define POLLDELAY 4

using std::endl;
using std::time;
using std::string;
using std::cout;
using std::cerr;
using std::vector;
using std::ofstream;

// XXX check all static vars!

vector<string> tvChannelNames;
vector<string> radioChannelNames;
vector<string> dataChannelNames;

cMutex mutexNames;
cMutex mutexScan;

int cMenuChannelscan::autoSearchStat = 1;
int cMenuChannelscan::sourceStat = 0;

bool cMenuChannelscan::expertSettings = 0;


volatile bool cMenuChannelscan::scanning = false;
bool cMenuChannelscan::scanActiveMenu = false;

// OK
cTransponders *cTransponders::instance_ = NULL;

 
int loopSrcIndex = 0;
int pollCount;
bool loopMode = false;

// --- Class cMenuChannelscan ------------------------------------------------

cMenuChannelscan::cMenuChannelscan(int CurrentChannelNr)
:cOsdMenu(tr("Channelscan"), CHNUMWIDTH)
{

  cTransponders::Create();
  Channels.Save();
  frequency = 11837;
  symbolrate = 27500;
  polarization = 'H';

  autoSearch = autoSearchStat;
  loopMode = false;

  srcTexts[0] = "DVB-S - Satellite";
  srcTexts[1] = "DVB-C - Cable";
  srcTexts[2] = "DVB-T - Terrestial";

  fecTexts[0] = "None";
  fecTexts[1] = "1/2";
  fecTexts[2] = "2/3";
  fecTexts[3] = "3/4";
  fecTexts[4] = "4/5";
  fecTexts[5] = "5/6";
  fecTexts[6] = "6/7";
  fecTexts[7] = "7/8";
  fecTexts[8] = "8/9";
  fecTexts[9] = "Auto";
  fecStat = 3;

  qam256 = 0;
  sRate6875 = 0;

  // CurrentChannel has to be greater than 0!
  CurrentChannelNr = CurrentChannelNr == 0 ? 1 : CurrentChannelNr;
  cChannel *channel = Channels.GetByNumber(CurrentChannelNr);
  source = channel->Source();
  currentChannel = CurrentChannelNr;
  scanActiveMenu = false;
  // timer for loop mode
  pollCount = POLLDELAY;

  Setup();
}

void cMenuChannelscan::Setup()
{
    //dsyslog (DBG "   cMenuChannelscan::Setup  ");
    int current = Current();
    Clear();

   //  for DVB-T or DVB-C   
   // we loop through all satalites
   if (loopMode && ::Setup.DiSEqC)  
   {
       Add(new cMenuInfoItem(tr("Scanning configured satellites")));
       AddBlankLineItem(4);
   }
   else
   {
      Add(new cMenuEditStraItem(tr("Source"), &sourceStat, 3, srcTexts)); // 1

      if (sourceStat == 0)
      {
        Add(new cMenuEditSrcItem(tr("Position"),   &source)); //XXX all sources in Diseqc?  2 
        Add(new cMenuEditBoolItem(tr("Search Mode"), &autoSearch, tr("Manual"),  tr("SearchMode$Auto") )); // 3
      }
      else 
      {
         AddBlankLineItem(2);
         autoSearch=1; // only blindscan for dvb t und c
      }

      // cable only
      if (expertSettings && sourceStat == 1)
      {
          Add(new cMenuEditBoolItem(tr("Modulation"), &qam256, "QAM - 64", "QAM - 256"));
          Add(new cMenuEditBoolItem(tr("Symbolrate"), &sRate6875, "6900", "6875"));
      }

      // sat only
      if (autoSearch == 0 && sourceStat == 0)
      {
          AddBlankLineItem(1);
          Add(new cMenuEditIntItem(tr("Frequency"), &frequency));  
          Add(new cMenuEditChrItem(tr("Polarization"),&polarization, "HVLR"));
          Add(new cMenuEditIntItem(tr("Symbolrate"), &symbolrate));
          Add(new cMenuEditStraItem("FEC", &fecStat, 10, fecTexts ));
      }
      else 
         AddBlankLineItem(5);

   }
    // vertical space
   int blanks = expertSettings?3:0;
   if (::Setup.DiSEqC) 
      AddBlankLineItem((Diseqcs.Count()/4)-blanks);
   else
      AddBlankLineItem(4-blanks);


   Add(new cMenuInfoItem("DiSEqC", static_cast<bool>(::Setup.DiSEqC))); 

   if (::Setup.DiSEqC)
       DiseqShow();  // 2

   if (!loopMode) SetHelp(tr("Button$Start"), /*tr("DiSEqC"),*/ sourceStat==1?tr("Expert"):"");

   SetCurrent(Get(current));
   Display();
}

eOSState cMenuChannelscan::ProcessKey(eKeys Key)
{

  //dsyslog (DBG "  ProcessKey autoSearchStat%d",autoSearchStat);
  int oldAutoSearch = autoSearch;
  int oldSourceStat = sourceStat;
  bool oldExpertSettings = expertSettings;

  eOSState state = cOsdMenu::ProcessKey(Key);


  // wizard loop - o - matic 
  if (loopMode && ::Setup.DiSEqC  && loopSrcIndex > 0)
  {
    if (pollCount <= 0 && !cMenuChannelscan::scanActiveMenu)
    {
        loopSrcIndex--;
        pollCount = POLLDELAY;
        return AddSubMenu(new cMenuScanActive());
    }
   else 
     if (!scanActiveMenu) pollCount--;
  }
  else if (loopMode && !scanActiveMenu &&  loopSrcIndex <= 0)
  {
       loopMode = false;
       return osBack;
  }

  // wizard loop - o - matic  end
  // items return osUnkown if 
  if (state == osUnknown)
  {
     switch(Key)
     {
        case kBack:  return osEnd;

        case kRed:
        case kOk:
          if (autoSearch == 0)
		 {
		    cTransponders &transponders = cTransponders::GetInstance();
                    transponders.Load(source, frequency, polarization, symbolrate, fecStat);
                    return AddSubMenu(new cMenuScanActive());
		 }
          else
		 {
                    source = sourceStat==1?0x4000:sourceStat==2?0xC000:source;
	            cTransponders &transponders = cTransponders::GetInstance();
                    transponders.Load(source, sRate6875, qam256); 
                    return AddSubMenu(new cMenuScanActive());
		 }

       /*
       case  kGreen:
             AddSubMenu(new cMenuSetupLNB);
             //Setup();
             break;
      */

       case  kGreen: //kYellow:
             if (sourceStat ==  1)
             {
                expertSettings  = expertSettings?false:true;
                //dsyslog (DBG "   expertSettings %s", expertSettings?"YES":"NO");
                Setup();
             }
             break;
             
       default:  state = osContinue;
     }
  } 

  // forces setup if menu layout should be changed
  if (Key != kNone)
  {
     
     if (autoSearch != oldAutoSearch ||
         oldExpertSettings != expertSettings || 
         oldSourceStat != sourceStat)
     {
         oldSourceStat = sourceStat;
         Setup();
     }
  }
  return state;
}
void cMenuChannelscan::DiseqShow()
{
   int i = 0;
   for(cDiseqc *diseqc = Diseqcs.First(); diseqc; diseqc=Diseqcs.Next(diseqc))
   {
      
      if (diseqc != Diseqcs.First() && diseqc->Source() == Diseqcs.Prev(diseqc)->Source() )
          continue;

      char *buffer = NULL;

      asprintf(&buffer,"LNB %c: %s", 'A'+i, *cSource::ToString(diseqc->Source()));
      Add(new cMenuInfoItem(buffer));
      free(buffer);
      i++;
   }
       loopSrcIndex = i;

       Add(new cMenuInfoItem(tr("channels in current list"), Channels.MaxNumber()));
    }

cMenuChannelscan::~cMenuChannelscan()
{
  cTransponders::Destroy();
  tvChannelNames.clear();
  radioChannelNames.clear();
  dataChannelNames.clear();
  ::Setup.UpdateChannels = oldUpdateChannels;
}

// taken fron vdr/menu.c
void cMenuChannelscan::AddBlankLineItem(int lines)
{
   for(int i = 0;i < lines;i++)
   {
      cOsdItem *item = new cOsdItem;
      item->SetSelectable(false);
      item->SetText(strndup(" ", 1), false);
      Add(item);
   }
}


// --- cMenuScanActiveItem ----------------------------------------------------
cMenuScanActiveItem::cMenuScanActiveItem(const char *TvChannel,const char *RadioChannel)
{
  ::Setup.UpdateChannels = 4;

  tvChannel = strdup(TvChannel);
  radioChannel = strdup(RadioChannel);
  char *buffer = NULL;
  asprintf(&buffer, "%s \t%s", tvChannel, radioChannel);
  SetText(buffer, false);
  SetSelectable(false);
}

cMenuScanActiveItem::~cMenuScanActiveItem()
{
  free(tvChannel);
  free(radioChannel);
}

// --- cMenuScanActive -------------------------------------------------------

cMenuScanActive::cMenuScanActive()
:cOsdMenu(tr("TV CHANNELS                     RADIO"), 25)
{
  cMenuChannelscan::scanActiveMenu = true;
  oldChannelNumbers = Channels.MaxNumber();

  // Make class 
  tvChannelNames.clear();
  radioChannelNames.clear();

  cTransponders &transponders = cTransponders::GetInstance();
  transponderNum_ = transponders.v_tp_.size();
  Scan.reset(new cScan());

  if (!Scan->StartScanning())
  {
     esyslog (ERR "   Tuner Error");
     Add(new cMenuInfoItem(tr("Tuner Error")));
  }
  
  //Scan->StartScanning();
  Setup();
}

void cMenuScanActive::Setup()
{
  Clear();
  mutexNames.Lock();
  int i = Scan->GetCurrentTp();

  vector<string>::iterator tv;
  vector<string>::iterator radio;
  tv = tvChannelNames.begin();
  radio = radioChannelNames.begin();

  while (tvChannelNames.size() > 10)
  {
    tvChannelNames.erase(tv);
    ++tv;
  }

  while (radioChannelNames.size() > 10)
  {
    radioChannelNames.erase(radio);
    ++radio;
  }

  tv = tvChannelNames.begin();
  radio = radioChannelNames.begin();

  while(1)
  {
      if (tv == tvChannelNames.end() &&  radio == radioChannelNames.end())
        break;

      cMenuScanActiveItem *Item = new cMenuScanActiveItem(tv == tvChannelNames.end()?"":tv->c_str(),
                                  radio == radioChannelNames.end()?"":radio->c_str());
      Add(Item);

      if (tv != tvChannelNames.end()) 
			++tv;
      if (radio != radioChannelNames.end()) 
			++radio;

  }
  int nameLines = tvChannelNames.size() > radioChannelNames.size() ? tvChannelNames.size() : radioChannelNames.size();
  mutexNames.Unlock();

  AddBlankLineItem(12 - nameLines);

  /*
    Add(new cMenuScanInfoItem(transponders.Position(),transponders[i].Frequency(),
    transponders[i].Polarisation(), transponders[i].Symbolrate(), transponders[i].FEC()));
  */
 
  if (cMenuChannelscan::scanning)
  {
      Add(new cMenuStatusBar(transponderNum_, i));
      Add(new cMenuInfoItem(tr("Scanning in Progress\tPlease Wait")));
  }
  else  // ScanStat == success 
  {
     Channels.Save();
    if (Channels.MaxNumber() > oldChannelNumbers ) //&& ScanStat == success)
          Add(new cMenuInfoItem(tr("Added new channels"), (Channels.MaxNumber() - oldChannelNumbers)));
     else if (Channels.MaxNumber() == oldChannelNumbers ) //&& ScanStat == success)
         Add(new cMenuInfoItem(tr("No new channels found")));
  }

  Display();
}


eOSState cMenuScanActive::ProcessKey(eKeys Key)
{
  if (!cMenuChannelscan::scanning && loopMode) 
  {
        sleep(3);
        return osBack;
  }

  eOSState state = cOsdMenu::ProcessKey(Key);

  if (state == osUnknown)
  {
     switch (Key) 
	  {
        case kOk: return osBack;
        case kBack:
           cMenuChannelscan::scanning = false; //ScanStat = aborted;
           Skins.Message(mtError, tr("Scanning aborted by User"));
           sleep(3);
           return osBack;
        default: state = osContinue;
     }
  Setup();
  }
  return state;
}
void cMenuScanActive::AddBlankLineItem(int lines)
{
   for(int i = 0;i < lines;i++)
   {
     cOsdItem *item = new cOsdItem;
     item->SetSelectable(false);
     item->SetText(strndup(" ", 1), false);
     Add(item);
   }
}

cMenuScanActive::~cMenuScanActive()
{
  //XXX 
  tvChannelNames.clear();
  radioChannelNames.clear();
  dataChannelNames.clear();
  // XXX
  cMenuChannelscan::scanning = false;
  cMenuChannelscan::scanActiveMenu = false;
}

// cMenuEditSrcItem taken fron vdr/menu.c
// --- cMenuEditSrcItem ------------------------------------------------------

cMenuEditSrcItem::cMenuEditSrcItem(const char *Name, int *Value)
:cMenuEditIntItem(Name, Value, 0)
{
   source = Sources.Get(*Value);
   Set();
}

void cMenuEditSrcItem::Set(void)
{
    if (source) {
        char *buffer = NULL;
        asprintf(&buffer, "%s - %s", *cSource::ToString(source->Code()), source->Description());
        SetValue(buffer);
        free(buffer);
    }
    else
      cMenuEditIntItem::Set();
}

eOSState cMenuEditSrcItem::ProcessKey(eKeys Key)
{
    eOSState state = cMenuEditItem::ProcessKey(Key);

    if (state == osUnknown) {
        if (NORMALKEY(Key) == kLeft) { // TODO might want to increase the delta if repeated quickly?
            if (source && source->Prev()) {
                source = (cSource *)source->Prev();
                *value = source->Code();
            }
        } else if (NORMALKEY(Key) == kRight) {
            if (source) {
                if (source->Next())
                    source = (cSource *)source->Next();
            }
            else
                source = Sources.First();
                if (source)
                    *value = source->Code();
        }
        else
            return state; // we don't call cMenuEditIntItem::ProcessKey(Key) here since we don't accept numerical input

        Set();
        state = osContinue;
    }
    return state;
}

//taken from rotor rotor plugin

cMenuScanInfoItem::cMenuScanInfoItem(string Pos, int f, char pol, int sr, int fecIndex)
{
    //dsyslog (DBG "  ++++ cMenuScanInfoItem Pos %s freq: %d p %c sr: %d fec %d",
               //Pos.c_str(), f, pol, sr, fecIndex);
    // Source TP-NR, Frequenz, Pol, SymbolRate, FEC
    const char *pos = Pos.c_str();
    char *buffer = NULL;
    asprintf(&buffer, "%s :\t%s %d %c %d %s",tr("Scanning on transponder"), pos, f, pol, sr, FECToStr(fecIndex));

    SetText(strdup(buffer), false);
    SetSelectable(false);
}
const char *cMenuScanInfoItem::FECToStr(int Index)
{
    switch (Index)
    {
        case 1: return "1/2";
        case 2: return "2/3";
        case 3: return "3/4";
        case 4: return "4/5";
        case 5: return "5/6";
        case 6: return "6/7";
        case 7: return "7/8";
        default : return tr("Auto");
    }
    return tr("Auto");
}

// --- cMenuStatusBar ----------------------------------------------------

cMenuStatusBar::cMenuStatusBar(int Total, int Part)
{
    int barWidth = 70;
    Part+=1;

    if (Part > Total)
    {
      Part = Total;
       cerr << "error! Wrong transponder Values\n";
    }
    if (Total < 1)
    {
      Total = 1;
       cerr << "error! Wrong transponder Values\n";
    }

    proCent = static_cast<int>(((Part) * barWidth/(Total)));

    char buffer[barWidth];
    for(int i = 0;i < proCent; i++)
      buffer[i] = '|';

    for(int i = proCent; i<barWidth; i++)
      buffer[i] = ' ';

   buffer[barWidth] = '\0';
   char *tmp;
    int l = asprintf(&tmp,"%s   %d / %d", buffer, Part, Total);

    SetText(strndup(tmp,l), false);
    SetSelectable(false);
    free(tmp);
}

// --- Class cMenuInfoItem -------------------------------------

cMenuInfoItem::cMenuInfoItem(const char *text, const char *textValue)
{
    char *buffer = NULL;
    asprintf(&buffer,"%s  %s",text, textValue? textValue:"");

    SetText(strdup(buffer), false);
    SetSelectable(false);
   free(buffer);
}

cMenuInfoItem::cMenuInfoItem(const char *text, int intValue)
{
    char *buffer = NULL;
    asprintf(&buffer,"%s: %d",text, intValue);

    SetText(strdup(buffer), false);
    SetSelectable(false);
    free(buffer);
}
cMenuInfoItem::cMenuInfoItem(const char *text, bool boolValue)
{
    char *buffer = NULL;
    asprintf(&buffer,"%s: %s",text, boolValue? tr("enabled"):tr("disabled"));

    SetText(strdup(buffer), false);
    SetSelectable(false);
    free(buffer);
}

