/*
 * channelscan: A plugin for the Video Disk Recorder
 *
 * See the README file for copyright information and how to reach the author.
 *
 * $Id$
 */

#include <linux/dvb/frontend.h>
#include <sys/ioctl.h>
#include <vdr/config.h>
#include <vdr/plugin.h>
#include <vdr/sources.h>
#include <vdr/device.h>
#include <vdr/channels.h>
#include <vdr/osd.h>
#include <math.h>

#include "filter.h"
#include "i18n.h"
#include "transponders.h"

#define DEV_DVB_FRONTEND "frontend"

static const char *VERSION        = "0.1.0";
static const char *DESCRIPTION    = "Plugin for satelite scan";
static const char *MAINMENUENTRY  = "Channelscan";


// --- cSetupMenu ------------------------------------------------------

class cSetupMenu : public cMenuSetupPage {
private:
  int newServiceType;
  const char *ServiceTypeTexts[4];
protected:
  virtual void Store(void);
public:
  cSetupMenu(void);
};

cSetupMenu::cSetupMenu(void)
{
  newServiceType=AddServiceType;
  ServiceTypeTexts[0]=tr("Radio only");
  ServiceTypeTexts[1]=tr("TV only");
  ServiceTypeTexts[2]=tr("Radio + TV");
  ServiceTypeTexts[3]=tr("Radio + TV + NVOD");
  Add(new cMenuEditStraItem(tr("Service Type"), &newServiceType,4,ServiceTypeTexts));
}

void cSetupMenu::Store(void)
{
  SetupStore("ServiceType",AddServiceType=newServiceType); 
}

// -- cMainMenu --------------------------------------------------------
class cMainMenu : public cThread, public cOsdObject {
private:
  int HasSwitched,oldupdate,paint,Frequenz,Symbolrate,fd_frontend,cardnr;
  char *Pol;
  cChannel *OldChannel;
  cChannel Channel;
  cOsd *osd;
  tColor color;
  int iTotalTranspoders;
  int iCurrentTranspoder;
  char cSatName[255];
  char cSelectedFile[255];
  int iSelectedFile;
  int iScanActive;
  SdtFilter *SFilter;
  PatFilter *PFilter;
protected:
  virtual void Action(void);
public:
  cMainMenu(void);
  ~cMainMenu();
  virtual void Show(void);
  virtual eOSState ProcessKey(eKeys Key);
  void DisplaySignalInfoOnOsd(fe_status_t status,unsigned int ber,unsigned int ss,float ssdBm,unsigned int snr,float snrdB,unsigned int unc);
  void GetSignalInfo(         fe_status_t &status,
                             unsigned int &BERChip,
                             unsigned int &SSChip,
                             unsigned int &SNRChip,
                             unsigned int &UNCChip  );
  bool Signal(int Frequenz, char *Pol, int Symbolrate);
  void LoadSatFile(int iPosition);
  void cMainMenu::ShowSatData(char cSelectedFile[100]);
};

cMainMenu::cMainMenu(void) {
	iScanActive=0;
	iTotalTranspoders = 0;
	iCurrentTranspoder = 0;
	strcpy(cSatName,"\0");
	Frequenz=Symbolrate=0;
	Pol="H";	
        fd_frontend=0;
        cardnr=-1;
	oldupdate=Setup.UpdateChannels;
	Setup.UpdateChannels=0;
	HasSwitched=false;
	paint=0;
	osd=NULL;
	// Now search a sat files
	//LoadSatFile(1);
	const char *ConfDir = cPlugin::ConfigDirectory();	
	char cTranspoderDir[255];	
	strcpy(cTranspoderDir,ConfDir);
	strcat(cTranspoderDir,"//transponders//0130.ini");
	strcpy(cSelectedFile, cTranspoderDir);
	iSelectedFile=0;
	ShowSatData(cSelectedFile);		
        PFilter=NULL;
        SFilter=NULL;
}

cMainMenu::~cMainMenu() {
	Cancel(0);
        if (fd_frontend>0)
          close(fd_frontend);
	delete osd;
        if (PFilter)
           cDevice::GetDevice(cardnr)->Detach(PFilter);
        if (SFilter)
           cDevice::GetDevice(cardnr)->Detach(SFilter);
	//	Return to last channel
	if (HasSwitched) {
		cDevice::GetDevice(cardnr)->SwitchChannel(OldChannel,true);
	}
	Setup.UpdateChannels=oldupdate;
}

void cMainMenu::Action() {
	iScanActive=1;
	// Save the last channel
	Setup.UpdateChannels=0;
	cChannel *SChannel= new cChannel; // OldChannel;
	int iScanTime=0;
	int iElapsedTime = 0; 
	// Start scan
	cTranspoders oTransp;
	oTransp.GetTranspodersData(cSelectedFile);		
        cardnr=-1;
        for (int i=0; i<cDevice::NumDevices(); i++)
          if (cDevice::GetDevice(i)->ProvidesSource(oTransp.iSource) && !cDevice::GetDevice(i)->Receiving(true))
            cardnr=i;
        if (cardnr==-1) {
          for (int i=0; i<cDevice::NumDevices(); i++)
              if (cDevice::GetDevice(i)->ProvidesSource(oTransp.iSource) && !cDevice::GetDevice(i)->Receiving())
                cardnr=i;
        }
        if (cardnr==-1)
        {
          dsyslog("None of the devices provides this source (%x)\n",oTransp.iSource);
          return;
        }
        OldChannel=Channels.GetByNumber(cDevice::GetDevice(cardnr)->CurrentChannel());
        char buffer[PATH_MAX];
        snprintf(buffer, sizeof(buffer), "/dev/dvb/adapter%d/frontend0", cardnr);
        if (fd_frontend>0) 
        {
           close(fd_frontend);
           fd_frontend=0;
        }
        fd_frontend = open(buffer,O_RDONLY | O_NONBLOCK);
        if (cDevice::GetDevice(cardnr)==cDevice::ActualDevice())
                HasSwitched=true;
	std::cout << "*** Now start scanner *** " << "\n";
	for (int i=0; i<oTransp.iCount; i++) {
		Frequenz = oTransp.iFrequency[i];
		char cTemp = oTransp.cPolarization[i];
		Pol = &cTemp;
		Symbolrate = oTransp.iSymbolrate[i];
                *SChannel=*OldChannel;
                SChannel->cChannel::SetSatTransponderData(oTransp.iSource,Frequenz,*Pol,Symbolrate,FEC_AUTO);
                cDevice::GetDevice(cardnr)->SwitchChannel(SChannel,HasSwitched);
                PFilter = new PatFilter();
                SFilter = new SdtFilter(PFilter);
                PFilter->SetSdtFilter(SFilter);
                cDevice::GetDevice(cardnr)->AttachFilter(SFilter);
                cDevice::GetDevice(cardnr)->AttachFilter(PFilter);
		iCurrentTranspoder=i;
		ShowSatData(cSelectedFile);
		// start function show()
		fe_status_t status;
		unsigned int BERChip=0;
		unsigned int SSChip=0;
		unsigned int SNRChip=0;
		unsigned int UNCChip=0;
		GetSignalInfo(status,BERChip,SSChip,SNRChip,UNCChip);
		float snrdB = 0.0;
		float ssdBm = 0.0;
		unsigned int unc = UNCChip;
		unsigned int ber = BERChip;
		unsigned int ss = SSChip/655;
		unsigned int snr = SNRChip/655;
		ssdBm = (logf(((double)SSChip)/65535)*10.8);
		if (SNRChip>57000) {
		snrdB = (logf((double)SNRChip/6553.5)*10.0);
		} else {
		snrdB = (-3/(logf(SNRChip/65535.0)));
		}
		DisplaySignalInfoOnOsd(status,ber,ss,ssdBm,snr,snrdB,unc);		
		// Debug on console
		/*if (iElapsedTime > 60) {
			std::cout << "ElapsedTime:" << (int)iElapsedTime/60 << "Min ";
		} else {
			std::cout << "ElapsedTime:" << iElapsedTime << "Sec ";
		}
		std::cout << "Transponder:" << i << " " << oTransp.iFrequency[i] << "-" << oTransp.cPolarization[i] << "-" << oTransp.iSymbolrate[i];
		std::cout << " SNR=" << snr << " SS=" << ss;
		*/
		// end function show()
                usleep(2000000);
		if (cDevice::GetDevice(cardnr)->HasLock()) {
			iScanTime = 100;
			//std::cout << " Wait " << iScanTime << "Sec for scan...\n";
			iElapsedTime = iElapsedTime + iScanTime;
		} else {
			iScanTime = 0;
			//std::cout << " No wait no scan because no signal.\n";
			iElapsedTime = iElapsedTime + iScanTime;
		}
                time_t start=time(NULL);
                while (!PFilter->EndOfScan() && time(NULL) - start < iScanTime)
		  usleep(10000);
                cDevice::GetDevice(cardnr)->Detach(PFilter);
                cDevice::GetDevice(cardnr)->Detach(SFilter);
                PFilter=NULL;
                SFilter=NULL;
	}
	std::cout << "*** End scanner work *** " << "\n";
	iScanActive=0;
	//	Return to last channel
	std::cout << "*** Return to last channel *** " << "\n";
	if (HasSwitched) { 
		cDevice::GetDevice(cardnr)->SwitchChannel(OldChannel,true);
                HasSwitched=false;
        }
	Setup.UpdateChannels=oldupdate;
	return;
}

void cMainMenu::LoadSatFile(int iPosition) {
	// Find the conf dir
	const char *ConfDir = cPlugin::ConfigDirectory();	
	char cTranspoderDir[255];	
	strcpy(cTranspoderDir,ConfDir);
	strcat(cTranspoderDir,"//transponders//");
	cTranspoders oTransp;
	oTransp.LoadArrTranspoders(cTranspoderDir);
	// 20050920
	if ((iPosition >= 0) && (iPosition <= oTransp.iArrTranspodersFile)) {
		strcpy(cSelectedFile, oTransp.sArrTranspodersFile[iPosition].c_str());
		iSelectedFile = iPosition;
		ShowSatData(cSelectedFile);
		Show();
		std::cout << iPosition << " - " << cSelectedFile << " - " << cSatName << "\n";
	}
}

void cMainMenu::ShowSatData(char cSelectedFile[100]) {
	cTranspoders oTransp;
	oTransp.GetTranspodersData(cSelectedFile);
	iTotalTranspoders=oTransp.iCount;
	for (int i=0; i<25; i++) {
		cSatName[i] = oTransp.cName[i];
		cSatName[i+1] = '\0';
	}
	//strncpy(cSatName,oTransp.cName,24);	
}

void cMainMenu::Show(void) {
	fe_status_t status;
	unsigned int BERChip=0;
	unsigned int SSChip=0;
	unsigned int SNRChip=0;
	unsigned int UNCChip=0;
	GetSignalInfo(status,BERChip,SSChip,SNRChip,UNCChip);
	float snrdB = 0.0;
	float ssdBm = 0.0;
	unsigned int unc = UNCChip;
	unsigned int ber = BERChip;
	unsigned int ss = SSChip/655;
	unsigned int snr = SNRChip/655;
	ssdBm = (logf(((double)SSChip)/65535)*10.8);
	if (SNRChip>57000) {
		snrdB = (logf((double)SNRChip/6553.5)*10.0);
	} else {
		snrdB = (-3/(logf(SNRChip/65535.0)));
	}
	DisplaySignalInfoOnOsd(status,ber,ss,ssdBm,snr,snrdB,unc);
}

eOSState cMainMenu::ProcessKey(eKeys Key) {
	eOSState state = cOsdObject::ProcessKey(Key);
	if (state == osUnknown) {	
		switch (Key) {			
			case kBack:
				return osEnd;
				break;
			case kOk:
				if (iScanActive == 0) {
					Start();
				}
				break;
			case kLeft:
                        case kLeft|k_Repeat:
				if (iScanActive == 0) {
					LoadSatFile(iSelectedFile - 1);
				}
				break;
			case kRight:
                        case kRight|k_Repeat:
				if (iScanActive == 0) {
					LoadSatFile(iSelectedFile + 1);
				}
				break;	
                        case kUp:
                        case kUp|k_Repeat:
                                if (!iScanActive)
                                        LoadSatFile(iSelectedFile - 10);
                                break;
                        case kDown:
                        case kDown|k_Repeat:
                                if (!iScanActive) 
                                        LoadSatFile(iSelectedFile + 10);
                                break;		
			default:
				return osContinue;
		};
	}
	return osContinue;
}

void cMainMenu::DisplaySignalInfoOnOsd(fe_status_t status,unsigned int ber,unsigned int ss,float ssdBm,unsigned int snr,float snrdB,unsigned int unc) {
	int clrBackwindow = 0x800000C8;
	int width = 620;
	int height = 230;
	int iTop;
	char cTemp[100];

	int iBarGraphWidth = 400;
	int RedLimit=iBarGraphWidth*30/100;
	int YellowLimit=max(RedLimit,iBarGraphWidth*60/100);
	int ShowSNR = iBarGraphWidth*snr/100;
	int ShowSS =  iBarGraphWidth*ss/100;
	int PositionY = 0;
	
	if (paint==0) {
		paint=1;
		osd = cOsdProvider::NewOsd(50,Setup.OSDTop+Setup.OSDHeight-height);
        tArea Area = {0,0, width-1, height-1, 4};
		osd->SetAreas(&Area, 1);
		osd->Flush();
	}
    if(osd) {
		iTop=40;
        osd->DrawRectangle(0, 0, width, height,clrBackwindow);
		osd->DrawRectangle(0, 0, width, 30, clrWhite);
		for(int i=0; i<width; i++) {
			osd->DrawPixel(i, 0, clrBlack);
			osd->DrawPixel(i, 1, clrBlack);
			osd->DrawPixel(i, 30+1, clrBlack);
			osd->DrawPixel(i, 30+2, clrBlack);	
			osd->DrawPixel(i, 110+1, clrBlack);
			osd->DrawPixel(i, 110+2, clrBlack);				
			osd->DrawPixel(i, height-2, clrBlack);
			osd->DrawPixel(i, height-1, clrBlack);		
		}
		for(int i=0; i<height; i++) {
			osd->DrawPixel(0, i, clrBlack);
			osd->DrawPixel(1, i, clrBlack);
			osd->DrawPixel(width-2, i, clrBlack);
			osd->DrawPixel(width-1, i, clrBlack);
			if(i<30+2) {
				osd->DrawPixel(490, i, clrBlack);
				osd->DrawPixel(491, i, clrBlack);	
			}
		}
		strcpy(cTemp, tr(MAINMENUENTRY));
		strcat(cTemp, "-");
		strcat(cTemp, VERSION);
		osd->DrawText(10, 3, cTemp, clrBlack, clrWhite, cFont::GetFont(fontOsd));
		osd->DrawText(500, 3, "by Kikko77", clrBlack, clrWhite, cFont::GetFont(fontOsd));
		
		iTop = 40;
		// Sat name
		sprintf(cTemp, tr("Sat name:"));
		osd->DrawText(10,iTop,cTemp,clrWhite,clrBackwindow,cFont::GetFont(fontOsd));
		sprintf(cTemp, cSatName);
		osd->DrawText(130,iTop,cTemp,clrWhite,clrBackwindow,cFont::GetFont(fontOsd));
		// Transpoders state
		sprintf(cTemp, tr("Transponder:"));
		osd->DrawText(400,iTop,cTemp,clrWhite,clrBackwindow,cFont::GetFont(fontOsd));
		if (iScanActive == 1) {
			strcpy(cTemp, itoa(iCurrentTranspoder+1));
			strcat(cTemp, "/");
			strcat(cTemp, itoa(iTotalTranspoders));
		} else {
			strcpy(cTemp, itoa(iTotalTranspoders));
		}
		osd->DrawText(545,iTop,cTemp,clrWhite,clrBackwindow,cFont::GetFont(fontOsd));
		// Sat file
		sprintf(cTemp, tr("Current file:"));
		osd->DrawText(10,iTop+30,cTemp,clrWhite,clrBackwindow,cFont::GetFont(fontOsd));
		int a=0;
		for (int i=0; i<100; i++) {
			if ((cSelectedFile[i] == '/') && (cSelectedFile[i+1] == '/')) {
				i++;
			}
			cTemp[a] = cSelectedFile[i];
			cTemp[a+1] = '\0';
			a++;
		}
		osd->DrawText(150,iTop+30,cTemp,clrWhite,clrBackwindow,cFont::GetFont(fontOsd));

		iTop = 120;
		// Progress bar
		char buf[1024];
		sprintf(buf,"SNR:");
		osd->DrawText(10, iTop+3,buf,clrWhite,clrBackwindow, cFont::GetFont(fontOsd));
		osd->DrawRectangle(68, iTop+10, 68+min(RedLimit,ShowSNR), iTop+25, clrRed);
		if(ShowSNR>RedLimit) osd->DrawRectangle(68+RedLimit, iTop+10, 68+min(YellowLimit,ShowSNR), iTop+25, clrYellow);
		if(ShowSNR>YellowLimit) osd->DrawRectangle(68+YellowLimit, iTop+10, 68+ShowSNR, iTop+25, clrGreen);
		sprintf(buf,"%d%% = %.1fdB",snr,snrdB);
		osd->DrawText(440, iTop+3,buf,clrWhite,clrBackwindow, cFont::GetFont(fontOsd));
		PositionY = PositionY + 35;
		sprintf(buf,"SS:");
		osd->DrawText(10, iTop+3+PositionY,buf,clrWhite,clrBackwindow, cFont::GetFont(fontOsd));
		osd->DrawRectangle(68, iTop+10+PositionY, 68+min(RedLimit,ShowSS), iTop+25+PositionY, clrRed);
		if(ShowSS>RedLimit) osd->DrawRectangle(68+RedLimit, iTop+10+PositionY, 68+min(YellowLimit,ShowSS), iTop+25+PositionY, clrYellow);
		if(ShowSS>YellowLimit) osd->DrawRectangle(68+YellowLimit, iTop+10+PositionY, 68+ShowSS, iTop+25+PositionY, clrGreen);
		sprintf(buf,"%d%% = %.1fdBm",ss,ssdBm);
		osd->DrawText(440, iTop+3+PositionY,buf,clrWhite,clrBackwindow, cFont::GetFont(fontOsd));
		PositionY = PositionY + 35;

		iTop = 190;
		// Frequency
		sprintf(cTemp, tr("Frequency:"));
		osd->DrawText(10,iTop,cTemp,clrWhite,clrBackwindow,cFont::GetFont(fontOsd));
		sprintf(cTemp, itoa(Frequenz));
		osd->DrawText(130,iTop,cTemp,clrWhite,clrBackwindow,cFont::GetFont(fontOsd));
		// Polari
		sprintf(cTemp, tr("Polarization:"));
		osd->DrawText(215,iTop,cTemp,clrWhite,clrBackwindow,cFont::GetFont(fontOsd));
		sprintf(cTemp,"%c",*Pol);
		osd->DrawText(375,iTop,cTemp,clrWhite,clrBackwindow,cFont::GetFont(fontOsd));
		// Symbolrate
		sprintf(cTemp, tr("Symbolrate:"));
		osd->DrawText(410,iTop,cTemp,clrWhite,clrBackwindow,cFont::GetFont(fontOsd));		
		sprintf(cTemp, itoa(Symbolrate));
		osd->DrawText(540,iTop,cTemp,clrWhite,clrBackwindow,cFont::GetFont(fontOsd));		
        osd->Flush();
	} else {
		isyslog("Osd creation error...");
	}
}


void cMainMenu::GetSignalInfo(fe_status_t &status, unsigned int &BERChip, unsigned int &SSChip, unsigned int &SNRChip, unsigned int &UNCChip) {
  if (fd_frontend>0)
  {
      status = fe_status_t(0);
      usleep(15);
      CHECK(ioctl(fd_frontend, FE_READ_STATUS, &status));
      usleep(15);                                                 //µsleep necessary else returned values may be junk
      BERChip=0;                                    //set variables to zero before ioctl, else FE_call sometimes returns junk
      CHECK(ioctl(fd_frontend, FE_READ_BER, &BERChip));
      usleep(15);
      SSChip=0;
      CHECK(ioctl(fd_frontend, FE_READ_SIGNAL_STRENGTH, &SSChip));
      usleep(15);
      SNRChip=0;
      CHECK(ioctl(fd_frontend, FE_READ_SNR, &SNRChip));
      usleep(15);
      UNCChip=0;
      CHECK(ioctl(fd_frontend, FE_READ_UNCORRECTED_BLOCKS, &UNCChip));
  }
}


// --- cPluginChannelscan\ ---------------------------------------------------------
class cPluginChannelscan : public cPlugin {
private:
  // Add any member variables or functions you may need here.
public:
  cPluginChannelscan(void);
  virtual ~cPluginChannelscan();
  virtual const char *Version(void) { return VERSION; }
  virtual const char *Description(void) { return tr(DESCRIPTION); }
  virtual const char *CommandLineHelp(void);
  virtual bool ProcessArgs(int argc, char *argv[]);
  virtual bool Initialize(void);
  virtual bool Start(void);
  virtual void Housekeeping(void);
  virtual const char *MainMenuEntry(void) { return tr(MAINMENUENTRY); }
  virtual cOsdObject *MainMenuAction(void);
  virtual cMenuSetupPage *SetupMenu(void);
  virtual bool SetupParse(const char *Name, const char *Value);
};

cPluginChannelscan::cPluginChannelscan(void) {
  // Initialize any member variables here.
  // DON'T DO ANYTHING ELSE THAT MAY HAVE SIDE EFFECTS, REQUIRE GLOBAL
  // VDR OBJECTS TO EXIST OR PRODUCE ANY OUTPUT!
}

cPluginChannelscan::~cPluginChannelscan() {
  // Clean up after yourself!
}

const char *cPluginChannelscan::CommandLineHelp(void) {
  // Return a string that describes all known command line options.
  return NULL;
}

bool cPluginChannelscan::ProcessArgs(int argc, char *argv[]) {
  // Implement command line argument processing here if applicable.
  return true;
}

bool cPluginChannelscan::Initialize(void) {
  // Initialize any background activities the plugin shall perform.
  return true;
}

bool cPluginChannelscan::Start(void) {
  // Start any background activities the plugin shall perform.
  RegisterI18n(Phrases);
  return true;
}

void cPluginChannelscan::Housekeeping(void) {
  // Perform any cleanup or other regular tasks.
}

cOsdObject *cPluginChannelscan::MainMenuAction(void) {
  // Perform the action when selected from the main VDR menu.
  return new cMainMenu;
}

cMenuSetupPage *cPluginChannelscan::SetupMenu(void) {
  // Return a setup menu in case the plugin supports one.
  return new cSetupMenu;
}

bool cPluginChannelscan::SetupParse(const char *Name, const char *Value) {
  // Parse your own setup parameters and store their values.
  if (!strcasecmp(Name, "ServiceType")) AddServiceType = atoi(Value);
  else return false;
  return true;
}

VDRPLUGINCREATOR(cPluginChannelscan); // Don't touch this!

