#include "diseqc.h"
#include "menu.h"
#include "menuitems.h"
#include <sys/ioctl.h>
#include <linux/dvb/frontend.h>
#include <math.h>
#include <ctype.h>
#include <vdr/interface.h>
#include <vdr/plugin.h>

cMenuSetupRotor::cMenuSetupRotor(void)
{
  Setup();
}

void cMenuSetupRotor::Setup(void)
{
  int current=Current();
  Clear();
  texts[0]="GotoX";
  texts[1]="GotoPosition";
  texts[2]=tr("no");
  achsw=data.UseGotoX ? 0 : data.UseGotoPos ? 1 : 2;
  newDvbCard=data.DvbCard+1;
  Add(new cMenuEditIntItem (tr("Card, connected with motor"), &newDvbCard,1,cDevice::NumDevices()));
  Add(new cMenuEditBoolItem(tr("Repeat DiSEqC-Commands?"), &data.repeat));
  Add(new cMenuEditIntpItem(tr("Latitude"), &data.Lat,0,900,&data.SN,tr("North"),tr("South")));
  Add(new cMenuEditIntpItem(tr("Longitude"), &data.Long,0,1800,&data.EW,tr("West"),tr("East")));
  Add(new cMenuEditStraItem(tr("Move on channel switch"), &achsw, 3 , texts));
//  Add(new cMenuEditBoolItem(tr("Setup.OSD$Info on channel switch"), &newShowInfo));
  SetCurrent(Get(current));
  SetHelp(tr("Configure"));
  Display();
}

void cMenuSetupRotor::Store(void)
{
  if (data.DvbCard!=newDvbCard-1)
  {
    static char buffer[PATH_MAX];
    snprintf(buffer, sizeof(buffer), "%s%d/%s%d", "/dev/dvb/adapter",newDvbCard-1,"frontend",0);
    close(data.fd_frontend);
    data.fd_frontend = open(buffer,0);
  } 
  SetupStore("DVB-Karte", data.DvbCard=newDvbCard-1);
  SetupStore("Wiederholen", data.repeat);
  SetupStore("Latitude", data.Lat);
  SetupStore("SouthNorth", data.SN);
  SetupStore("Longitude", data.Long);
  SetupStore("EastWest", data.EW);
  SetupStore("UseGotoX",data.UseGotoX = achsw == 0);
  SetupStore("UseGotoPos",data.UseGotoPos = achsw == 1);
  SetupStore("Card1", data.card[0]);
  SetupStore("Card2", data.card[1]);
  SetupStore("Card3", data.card[2]);
  SetupStore("Card4", data.card[3]);
  SetupStore("ShowInfo",data.ShowInfo);
}

eOSState cMenuSetupRotor::ProcessKey(eKeys Key)
{
  eOSState state = cOsdMenu::ProcessKey(Key);
  if (state==osUnknown)
  {
    Key=NORMALKEY(Key);
    switch (Key) {
      case kRed: return AddSubMenu(new cSubMenuSetupRotor);
                 break;
      case kOk:  Store();
                 state = osBack;
                 break;
      default: ;
      }
  }
  return state;
}

// --- cSubMenuSetupRotor -----------------------------------------------

cSubMenuSetupRotor::cSubMenuSetupRotor(void)
{
  cp=1;
  cOsdItem *i1 = new cOsdItem(tr("Satellite-Table"));
  i1->SetSelectable(false);
  cOsdItem *i2 = new cOsdItem("");
  i2->SetSelectable(false);
  Add(i1);  
  Add(new cMenuEditRotorConfItem(&cp));
  Add(i2);
  if (cDevice::NumDevices()>=2)
  {
    cOsdItem *i3 = new cOsdItem(tr("Cards connected with steerable dish"));
    i3->SetSelectable(false);
    Add(i3);
    Add(new cMenuEditBoolItem(tr("React to switching of 1.Card"), &data.card[0]));
    Add(new cMenuEditBoolItem(tr("React to switching of 2.Card"), &data.card[1]));
  }
  if (cDevice::NumDevices()>=3)
    Add(new cMenuEditBoolItem(tr("React to switching of 3.Card"), &data.card[2]));
  if (cDevice::NumDevices()>=4)
    Add(new cMenuEditBoolItem(tr("React to switching of 4.Card"), &data.card[3]));
  CursorDown();
}

cSubMenuSetupRotor::~cSubMenuSetupRotor()
{
  RotorPositions.Save();
}

eOSState cSubMenuSetupRotor::ProcessKey(eKeys Key)
{
  eOSState state = cOsdMenu::ProcessKey(Key);
  if (Current()==1)
    SetHelp(cp ? tr("Satellites") : tr("Positions"));
  else
  { 
    SetHelp(NULL);
    cp=1;
  }
  return state;
}


// --- cMainMenuRotor --------------------------------------------------

cMainMenuRotor::cMainMenuRotor(void):cOsdMenu(tr("Rotor")), cThread("Rotor-Plugin")
{
#if VDRVERSNUM >= 10330
  int tmp=false;
  cPluginManager::CallFirstService("Text2Skin-TTF",&tmp);
#endif
  edw=cSkinDisplay::Current()->EditableWidth();
  oldupdate=Setup.UpdateChannels;
  Setup.UpdateChannels=0;
  assign=false;
  SetHelp(tr("Store"),tr("Goto"),tr("Recalc"),tr("Limits off"));
  OldChannel=Channels.GetByNumber(cDevice::GetDevice(data.DvbCard)->CurrentChannel());
  HasSwitched=false;
  RotorPos=RotorPositions.GetfromSource(OldChannel->Source());
  Position=RotorPos->GetPos();
  Frequenz=OldChannel->Frequency();
  if ((*OldChannel).Polarization() == 'v' || (*OldChannel).Polarization() == 'V')
    Pol='V';
  else
    Pol='H';
  Symbolrate=OldChannel->Srate();
  m_Active = false;

/*
  enum { NUMCHARS = cFont::NUMCHARS } ;
  const cFont::tCharData *data;
  
    
  for (int i = 32, n=0; i < NUMCHARS; i++) {
    data = cFont::GetFont(fontSml)->CharData(i);
    CharData[n++]=data->width;
    CharData[n++]=data->height;
    for (ulong j=0; j<data->height; j++) {
      CharData[n++]=data->lines[j];
    }
  }
  CharData[(127-32)*(cFont::GetFont(fontSml)->Height()+2)]=1;
  CharData[(128-32)*(cFont::GetFont(fontSml)->Height()+2)]=1;
  for (int i=6; i<=cFont::GetFont(fontSml)->Height()-3 ;i++)
    CharData[(128-32)*(cFont::GetFont(fontSml)->Height()+2)+i]=0x00000001;
  cFont::SetFont(fontSml, CharData);
  cFont::SetFont(fontOsd, CharData);
*/
  
  AddMenuItems();
  Start();
}

cMainMenuRotor::~cMainMenuRotor()
{
  if (m_Active)
  {
    m_Active = false;
    Cancel(3);
  }
  Setup.UpdateChannels=oldupdate;
  if (HasSwitched)
    cDevice::GetDevice(data.DvbCard)->SwitchChannel(OldChannel,true);
/*
  cFont::SetFont(fontSml);
  cFont::SetFont(fontOsd);
*/
#if VDRVERSNUM >= 10330
  int tmp=true;
  cPluginManager::CallFirstService("Text2Skin-TTF",&tmp);
#endif

}

void cMainMenuRotor::AddMenuItems()
{
  cOsdItem* n[6];
  for (int i=0; i<6; i++)
  {
    n[i]=new cOsdItem("");
    n[i]->SetSelectable(false);
  }
  Add(n[0]);
  Add(n[1]);
  Add(n[2]);
  Add(n[3]);
  Add(new cMenuEditEWItem(ThreeItems(tr("Drive East"),tr("Halt"),tr("Drive West"),edw)));
  Add(new cMenuEditStepsEWItem());
  Add(new cMenuEditLimitsItem(ThreeItems(tr("Set East Limit"),tr("Enable Limits"),tr("Set West Limit"),edw)));
  Add(n[4]);
  Add(new cMenuEditSatItem(&RotorPos,&Position,assign));
  Add(n[5]);
  Add(new cMenuEditFreqItem(tr("Frequency"),&Frequenz,&Pol,osUser1));
  Add(new cMenuEditSymbItem(tr("Symbolrate"),&Symbolrate,0,50000,osUser1));
  Add(new cOsdItem(CenterText(tr("Scan Transponder"),edw),osUser2));
  CursorDown();
}


void cMainMenuRotor::Action(void)
{
  unsigned int SNRChip=0,SSChip=0;
  int i;
  char buf[700];
  m_Active = true;
  while (m_Active)
  {
    if (!HasSubMenu())
    {
      // SNR 

      CHECK(ioctl(Fd_Frontend, FE_READ_SNR, &SNRChip));
      usleep(15);
      for (i=0; ((i+1)*Width(LINE))<=(signed long)(SNRChip*(edw-Width("SNR:  99% = -99.9dB"))/65536);i++) 
        buf[i]=edw>100 ? LINE : '|';
      int bw = i*Width(LINE);
      for ( ; Width(BLANK)<=(signed long)(edw-Width("SNR:  99% = -99.9dB")-bw); i++,bw+=Width(BLANK))
        buf[i]=edw>100 ? BLANK : ' ';
      buf[i]='\0';
      sprintf(SNR,"SNR: %s %d%% = %.1fdB",buf,SNRChip/655,SNRChip>57000 ? (logf((double)SNRChip/6553.5)*10.0):(-3/(logf(SNRChip/65535.0))));

      // SS
 
      CHECK(ioctl(Fd_Frontend, FE_READ_SIGNAL_STRENGTH, &SSChip));
      usleep(15);
      for (i=0; ((i+1)*Width(LINE))<=(signed long)(SSChip*(edw-Width("SNR:  99% = -99.9dB"))/65535);i++)
        buf[i]=edw>100 ? LINE : '|';
      bw = i*Width(LINE);
      for ( ; Width(BLANK)<=(signed long)(edw-Width("SNR:  99% = -99.9dB")-bw); i++,bw+=Width(BLANK))
        buf[i]=edw>100 ? BLANK : ' ';
      buf[i]='\0';
      char buf2[100];
      sprintf(buf2,"SS: ");
      for (i=0; (i*Width(BLANK))<(Width("SNR:")-Width("SS:")); i++)
	buf2[i+4]=edw>100 ? BLANK : ' ';
      buf2[i+4]='\0';
      sprintf(SS,"%s%s %d%% = %.1fdB",buf2,buf,SSChip/655,(logf((double)SSChip/65535)*10.8));

      // STATUS

      fe_status_t status = fe_status_t(0);
      CHECK(ioctl(Fd_Frontend, FE_READ_STATUS, &status));
      usleep(15);
      buf[0]='\0';
      char h[]=" ";
      h[0]=edw>100 ? BLANK : ' ';
      if (status & FE_HAS_SIGNAL)
        strcat(buf,"SIGNAL");
      while(Width(buf) < edw/4)
        strcat(buf,h);
      if (status & FE_HAS_CARRIER)
        strcat(buf,"CARRIER");
      while(Width(buf) < edw/2)
        strcat(buf,h);
      if (status & FE_HAS_VITERBI)
        strcat(buf,"VITERBI");
      while(Width(buf) < (3*edw)/4)
        strcat(buf,h);
      if (status & FE_HAS_SYNC)
        strcat(buf,"SYNC");

      DisplayMenu()->SetItem(SNR,0,false,false);
      DisplayMenu()->SetItem(SS,1,false,false);
      DisplayMenu()->SetItem(buf,2,false,false);
    }
    if (edw!=cSkinDisplay::Current()->EditableWidth())
    {
      edw=cSkinDisplay::Current()->EditableWidth();
      Clear();
      AddMenuItems();
      Display();
    }
    usleep(500000);
  }
}

eOSState cMainMenuRotor::ProcessKey(eKeys Key)
{
  eOSState state = cOsdMenu::ProcessKey(Key);
  if (state == osUser1)
  {
    cChannel *SChannel = new cChannel;
    *SChannel = *OldChannel;
    SChannel->cChannel::SetSatTransponderData(RotorPos->R_Code(),Frequenz,Pol,Symbolrate,FEC_AUTO);
    if (cDevice::GetDevice(data.DvbCard)==cDevice::ActualDevice())
      HasSwitched=true;
    bool UseDiseqc=Setup.DiSEqC;
    Setup.DiSEqC=false;
    cDevice::GetDevice(data.DvbCard)->SwitchChannel(SChannel,HasSwitched);
    Setup.DiSEqC=UseDiseqc;
  }
  if (state == osUser2)
  {
    cChannel *SChannel = new cChannel;
    *SChannel = *OldChannel;
    SChannel->cChannel::SetSatTransponderData(RotorPos->R_Code(),Frequenz,Pol,Symbolrate,FEC_AUTO);
    if (cDevice::GetDevice(data.DvbCard)==cDevice::ActualDevice())
      HasSwitched=true;
    bool UseDiseqc=Setup.DiSEqC;
    Setup.DiSEqC=false;
    data.ActualSource=RotorPos->GetCode();
    cDevice::GetDevice(data.DvbCard)->SwitchChannel(SChannel,HasSwitched);
    Setup.DiSEqC=UseDiseqc;
    AddSubMenu(new cMenuScan);
  }
  if (state == osUnknown)
  {
    Key = NORMALKEY(Key);
    switch (Key) {
      case kRed   :
                    if (Interface->Confirm(tr("Are you sure?"),10))
                      DiseqcCommand(Store,Position);
                    break;
      case kGreen : Setup.UpdateChannels=0;
                    DiseqcCommand(Goto,Position);
                    break;
      case kYellow:
                    if (Interface->Confirm(tr("Are you sure?")))
                      DiseqcCommand(Recalc,Position);
                    break;
      case kBlue  :
                    if (Interface->Confirm(tr("Are you sure?")))
                      DiseqcCommand(LimitsOff);
                    break;
      default     : return state;
                 }
    state=osContinue;
  }
  return state;
}


cMenuScan::cMenuScan():cOsdMenu("Transponder-Scan")
{
  num=0;
  PFilter = new PatFilter(this);
  SFilter = new SdtFilter(PFilter,this);
  PFilter->SetSdtFilter(SFilter);
  cDevice::GetDevice(data.DvbCard)->AttachFilter(SFilter);
  cDevice::GetDevice(data.DvbCard)->AttachFilter(PFilter);
  SetHelp(tr("Add Channel"),tr("Add All"),"","");
}

cMenuScan::~cMenuScan()
{
  if (PFilter)
    cDevice::GetDevice(data.DvbCard)->Detach(PFilter);
  if (SFilter)
    cDevice::GetDevice(data.DvbCard)->Detach(SFilter);
}

eOSState cMenuScan::ProcessKey(eKeys Key)
{
  eOSState state = cOsdMenu::ProcessKey(Key);
  if (state == osUnknown)
  {
    Key = NORMALKEY(Key);
    switch (Key) {
      case kRed:   AddChannel(Current());
                   break;
      case kGreen: for (int i=0; i<num; i++)
                     AddChannel(i);
                   break;
      case kOk: if (PFilter && PFilter->EndOfScan())
                {
                  cDevice::GetDevice(data.DvbCard)->Detach(PFilter);
                  PFilter=NULL;
                  cDevice::GetDevice(data.DvbCard)->Detach(SFilter);
                  SFilter=NULL;
                }     
                cDevice::GetDevice(data.DvbCard)->SwitchChannel(&Channel[Current()],true);
                break;
      default: return state;
      }
    state=osContinue;
  }
  return state;
}

void cMenuScan::AddChannel(int Num)
{
  cChannel *channel=Channels.GetByServiceID(Channel[Num].Source(),Channel[Num].Transponder(), Channel[Num].Sid());
  if (channel)
  {
    channel->SetName(Channel[Num].Name(),Channel[Num].ShortName(),Channel[Num].Provider());
    channel->SetId(Channel[Num].Nid(),Channel[Num].Tid(),Channel[Num].Sid(),channel->Rid());
    int Apids[MAXAPIDS + 1] = { 0 };
    int Dpids[MAXDPIDS + 1] = { 0 };
    int Spids[MAXDPIDS + 1] = { 0 };
#if VDRVERSNUM>=10332
    char ALangs[MAXAPIDS + 1][MAXLANGCODE2] = { "" };
    char DLangs[MAXDPIDS + 1][MAXLANGCODE2] = { "" };
    char SLangs[MAXDPIDS + 1][MAXLANGCODE2] = { "" };
#else
    char ALangs[MAXAPIDS + 1][4] = { "" };
    char DLangs[MAXDPIDS + 1][4] = { "" };
    char SLangs[MAXDPIDS + 1][4] = { "" };
#endif
    int CaIds[MAXCAIDS+1] = { 0 };
    for (int i=0; i<=MAXAPIDS; i++)
    {
      Apids[i]=Channel[Num].Apid(i);
      strcpy(ALangs[i],Channel[Num].Alang(i));
    }
    for (int i=0; i<=MAXDPIDS; i++)
    {
      Dpids[i]=Channel[Num].Dpid(i);
      strcpy(DLangs[i],Channel[Num].Dlang(i));
    }
    for (int i=0; i<=MAXCAIDS; i++)
      CaIds[i]=Channel[Num].Ca(i);
    channel->SetPids(Channel[Num].Vpid(),Channel[Num].Ppid(),Apids,ALangs,Dpids,DLangs,Spids,SLangs,Channel[Num].Tpid());
    channel->SetCaIds(CaIds);
  }
  else
  {
    for (int i=0; i<1000; i++)
      if (!Channels.GetByChannelID(tChannelID(Channel[num].Source(), Channel[num].Nid(), Channel[num].Tid(), Channel[num].Sid(),i))) 
      {
        channel=Channels.NewChannel(&Channel[Num], Channel[Num].Name(), Channel[Num].ShortName(), Channel[Num].Provider(), Channel[Num].Nid(), Channel[Num].Tid(), Channel[Num].Sid(),i);
        break;
      }
  }
}

void cMenuScan::NewChannel(const cChannel *Transponder, const char *Name, const char *ShortName, const char *Provider, int Nid, int Tid, int Sid)
{
  for (int i=0; i<num; i++)
    if (Sid==Channel[i].Sid())
      return;
  Channel[num]=*Transponder;
  Channel[num].SetNumber(0);
  Channel[num].SetName(Name,ShortName,Provider);
  Channel[num].SetId(Nid,Tid,Sid);
  n[num]=new cOsdItem("no Info");
  Add(n[num]);
  display(num);
  num++;
}

#if VDRVERSNUM>=10332
void cMenuScan::SetPids(int Sid,int Vpid, int Ppid, int *Apids, char ALangs[][MAXLANGCODE2], int *Dpids, char DLangs[][MAXLANGCODE2], int *Spids, char SLangs[][MAXLANGCODE2], int Tpid)
#else
void cMenuScan::SetPids(int Sid,int Vpid, int Ppid, int *Apids, char ALangs[][4], int *Dpids, char DLangs[][4], int Tpid)
#endif
{
  for (int i=0; i<num; i++)
    if (Sid==Channel[i].Sid())
    {
      Channel[i].SetPids(Vpid,Ppid,Apids,ALangs,Dpids,DLangs,Spids,SLangs,Tpid);
      display(i);
    }
}

void cMenuScan::SetCaIds(int Sid,const int *CaIds)
{
  for (int i=0; i<num; i++)
    if (Sid==Channel[i].Sid())
    {
      Channel[i].SetCaIds(CaIds);
      display(i);
    }
}

void cMenuScan::SetCaDescriptors(int Sid,int Level)
{
  for (int i=0; i<num; i++)
    if (Sid==Channel[i].Sid())
    {
      Channel[i].SetCaDescriptors(Level);
      display(i);
    }
}

cChannel* cMenuScan::GetChannel(int Sid)
{
  for (int i=0; i<num; i++)
    if (Sid==Channel[i].Sid())
      return &Channel[i];
  return NULL;
}


void cMenuScan::display(int num)
{
  char buf[200]="";
  sprintf(buf,"%s - %d - %d - %d (%s) %c",Channel[num].Name(),Channel[num].Sid(),Channel[num].Vpid(),Channel[num].Apid(0),*cSource::ToString(Channel[num].Source()),Channel[num].Ca(0) ? '*' : ' ');
  n[num]->SetText(buf);
  cOsdMenu::Display();
}

config data;

cRotorPos::cRotorPos(int Code, int Pos)
{
  code = Code;
  e_code = Code;
  pos = Pos;
  description = strdup("");
}

cRotorPos::~cRotorPos()
{
  free(description);
}

int cRotorPos::GetPos()
{
  if (e_code!=code && RotorPositions.GetfromSource(e_code)!=RotorPositions.First())
    return RotorPositions.GetfromSource(e_code)->GetPos();
  else
    return pos;
}

int cRotorPos::GetCode()
{
  if (e_code!=code && RotorPositions.GetfromSource(e_code)!=RotorPositions.First())
    return RotorPositions.GetfromSource(e_code)->GetCode();
  else
    return e_code;
}

bool cRotorPos::Parse(const char *s)
{
  char *buf1 = NULL;
  char *buf2 = NULL;
  if (2 == sscanf(s, "%a[^=]=%a[^\n]",&buf1,&buf2))
  {
    char *b1=buf1;
    char *b2=buf2;
    int i;
    while (isspace(*b1)) b1++;
    i=0;
    while (b1[i] && !isspace(b1[i])) i++;
    b1[i]='\0';
    while (isspace(*b2)) b2++;
    i=0;
    while (b2[i] && !isspace(b2[i])) i++;
    b2[i]='\0';
    code = cSource::FromString(b1);
    i=0;
    while (b2[i])
    {
      b2[i]=toupper(b2[i]);
      i++;
    }
    if (strcmp(b2,"OFF")==0)
      e_code=0;
    else 
    {
      int t=strtol(b2,NULL,10); 
      if (*b2!='S')
      {
        pos=t;
        e_code=code;
      }
      else 
      {
        int e = cSource::FromString(b2);
        e_code = e ? e : code;
      }
    }
  }
  free(buf1);
  free(buf2);
  return code!=0;
}

bool cRotorPos::Save(FILE *f)
{
  cSource *source = Sources.Get(code);
  if (!source)
    return true;
  cSource *e_source = Sources.Get(e_code);
  if (e_code && !e_source)
    e_code=code;
  char buf[200];
  if (code==e_code)
    sprintf(buf,"%d",pos);
  else
    sprintf(buf,"%s",e_code ? *cSource::ToString(e_source->Code()) : "OFF");
  return fprintf(f, "%s = %s\n",*cSource::ToString(source->Code()),buf)>0;
}

void cRotorPos::AddDescription(const char *s)
{
  free(description);
  description=strdup(s);
}

// --- cRotorPositions ----------------------------------------------

cRotorPositions RotorPositions;

cRotorPos *cRotorPositions::GetfromSource(int Code)
{
  for (cRotorPos *p = First(); p; p = Next(p)) 
  {
    if (p->R_Code() == Code)
      return p;
  }
  return First();
}

cRotorPos *cRotorPositions::GetfromPos(int Pos)
{
  for (cRotorPos *p = First(); p; p = Next(p))
  {
    if (p->Pos() == Pos)
      return p;
  }
  return First();
}

