/*
 * Softcam plugin to VDR (C++)
 *
 * This code is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This code is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 * Or, point your browser to http://www.gnu.org/copyleft/gpl.html
 */

#include <malloc.h>
#include <stdlib.h>
//#include <getopt.h>
#include <typeinfo>

#include <vdr/plugin.h>
#include <vdr/menuitems.h>
#include <vdr/status.h>
#include <vdr/dvbdevice.h>
#include <vdr/channels.h>
#include <vdr/ci.h>

#include "common.h"
#include "sc.h"
#include "filter.h"
#include "system.h"
#include "cam.h"
#include "smartcard.h"
#include "data.h"
#include "network.h"
#include "misc.h"
#include "opts.h"
#include "i18n.h"

#ifdef VDR3
#include <vdr/interface.h>
#endif

#define MIN_VERS   1 // required VDR version
#define MIN_MAJOR  1
#define MIN_MINOR 31

// some sanity checks for applied patches
#ifdef HAVE_SOFTCSA
#if !defined(SOFTCSA_VERS) | SOFTCSA_VERS<5
#error You MUST use softcsa version 0.0.5 or greater. Next time read the README first.
#endif
#endif
#if !defined(VDR_IS_SC_PATCHED) | VDR_IS_SC_PATCHED<113 | (VDR3>0 & VDR_IS_SC_PATCHED<301)
#error You MUST patch the VDR core with the supplied patch. Next time read the README first.
#endif

#define SC_MAGIC { 0,'S','C',0xc4,0x5e,0xa1 }
#define OP_PROVIDES 0
#define OP_IGNORE   1

struct ScLink {
  char magic[6];
  short op;
  const cDevice *dev;
  const unsigned short *caids;
  const cChannel *channel;
  int num;
  };

static const char *VERSION        = "0.4.0";
static const char *DESCRIPTION    = "A software emulated CAM";

cPlugin *ScPlugin;

// --- cMenuEditCapItem --------------------------------------------------------

class cMenuEditCapItem : public cMenuEditIntItem {
private:
  const cCaDefinition *ca;
protected:
  virtual void Set(void);
public:
  cMenuEditCapItem(const char *Name, int *Value);
  eOSState ProcessKey(eKeys Key);
  };

cMenuEditCapItem::cMenuEditCapItem(const char *Name, int *Value)
:cMenuEditIntItem(Name, Value, 0)
{
  ca=CaDefinitions.Get(*Value);
  Set();
}

void cMenuEditCapItem::Set(void)
{
  if(ca) SetValue(ca->Number() ? ca->Description():tr("off"));
  else   cMenuEditIntItem::Set();
}

eOSState cMenuEditCapItem::ProcessKey(eKeys Key)
{
  eOSState state = cMenuEditItem::ProcessKey(Key);
  if (state == osUnknown) {
     if(NORMALKEY(Key)==kLeft || NORMALKEY(Key)==kRight) {
       if(ca) {
         const cCaDefinition *newCa=ca;
         do {
           if(NORMALKEY(Key)==kLeft) newCa=(cCaDefinition *)newCa->Prev();
           else                      newCa=(cCaDefinition *)newCa->Next();
           if(newCa && (!newCa->Number() || 
#ifdef VDR3
                                            newCa->Number()<=MAXDVBAPI
#else
                                            newCa->Number()>=CACONFBASE
#endif
                                                                        )) {
             ca=newCa;
             *value=newCa->Number();
             break;
             }
           } while(newCa);
         }
       }
     else return cMenuEditIntItem::ProcessKey(Key);
     Set();
     state=osContinue;
     }
  return state;
}

// --- cScInfoItem -------------------------------------------------------------

class cScInfoItem : public cOsdItem {
private:
  void SetValue(const char *Name, const char *Value);
public:
  cScInfoItem(const char *Name, int Value, eOSState State=osUnknown);
  cScInfoItem(const char *Name, const char *Value=0, eOSState State=osUnknown);
  };

cScInfoItem::cScInfoItem(const char *Name, int Value, eOSState State)
:cOsdItem(State)
{
  char buf[16];
  snprintf(buf,sizeof(buf),"%d",Value);
  SetValue(Name,buf);
  if(State==osUnknown)
#ifdef VDR3SKIN
    SetSelectable(false);
#else
    SetColor(clrWhite,clrBackground);
#endif
}

cScInfoItem::cScInfoItem(const char *Name, const char *Value, eOSState State)
:cOsdItem(State)
{
  SetValue(Name,Value);
  if(State==osUnknown)
#ifdef VDR3SKIN
    SetSelectable(false);
#else
    SetColor(clrWhite,clrBackground);
#endif
}

void cScInfoItem::SetValue(const char *Name, const char *Value)
{
  char *buff;
  asprintf(&buff,Value ? "%s:\t%s":"%s",Name,Value);
  SetText(buff,false);
#ifdef VDR3
  cStatus::MsgOsdCurrentItem(buff);
#else
  Display();
#endif
}

// --- cOpt --------------------------------------------------------------------

cOpt::cOpt(const char *Name, const char *Title)
{
  name=Name; title=Title;
  fullname=0; persistant=true;
}

cOpt::~cOpt()
{
  free(fullname);
}

const char *cOpt::FullName(const char *PreStr)
{
  if(PreStr) {
    free(fullname);
    asprintf(&fullname,"%s.%s",PreStr,name);
    return fullname;
    }
  else return name;
}

// --- cOptInt -----------------------------------------------------------------

cOptInt::cOptInt(const char *Name, const char *Title, int *Storage, int Min, int Max)
:cOpt(Name,Title)
{
  storage=Storage; min=Min; max=Max;
}

void cOptInt::Parse(const char *Value)
{
  *storage=atoi(Value);
}

void cOptInt::Backup(void)
{
  value=*storage;
}

bool cOptInt::Set(void)
{
  if(value!=*storage) { *storage=value; return true; }
  return false;
}

void cOptInt::Store(const char *PreStr)
{
  ScPlugin->SetupStore(FullName(PreStr),*storage);
}

void cOptInt::Create(cOsdMenu *menu)
{
  menu->Add(new cMenuEditIntItem(tr(title),&value,min,max));
}

// --- cOptSel -----------------------------------------------------------------

cOptSel::cOptSel(const char *Name, const char *Title, int *Storage, int NumStr, const char * const *Strings)
:cOptInt(Name,Title,Storage,0,NumStr)
{
  strings=Strings;
  trStrings=0;
}

cOptSel::~cOptSel()
{
  free(trStrings);
}

void cOptSel::Create(cOsdMenu *menu)
{
  free(trStrings);
  if((trStrings=MALLOC(const char *,max))) {
    for(int i=0; i<max ; i++) trStrings[i]=tr(strings[i]);
    menu->Add(new cMenuEditStraItem(tr(title),&value,max,trStrings));
    }
}

// --- cOptBool -----------------------------------------------------------------

cOptBool::cOptBool(const char *Name, const char *Title, int *Storage)
:cOptInt(Name,Title,Storage,0,1)
{}

void cOptBool::Create(cOsdMenu *menu)
{
  menu->Add(new cMenuEditBoolItem(tr(title),&value));
}

// --- cOptStr -----------------------------------------------------------------

cOptStr::cOptStr(const char *Name, const char *Title, char *Storage, int Size, const char *Allowed)
:cOpt(Name,Title)
{
  storage=Storage; size=Size; allowed=Allowed;
  value=MALLOC(char,size);
}

cOptStr::~cOptStr()
{
  free(value);
}

void cOptStr::Parse(const char *Value)
{
  strn0cpy(storage,Value,size);
}

void cOptStr::Backup(void)
{
  strn0cpy(value,storage,size);
}

bool cOptStr::Set(void)
{
  if(strcmp(value,storage)) { strn0cpy(storage,value,size); return true; }
  return false;
}

void cOptStr::Store(const char *PreStr)
{
  ScPlugin->SetupStore(FullName(PreStr),storage);
}

void cOptStr::Create(cOsdMenu *menu)
{
  menu->Add(new cMenuEditStrItem(tr(title),value,size,allowed));
}

// --- cOptCap -----------------------------------------------------------------

class cOptCap : public cOpt {
protected:
  int *storage, *value;
  int size, disp, len;
public:
  cOptCap(const char *Name, const char *Title, int *Storage, int Size, int Disp);
  virtual ~cOptCap();
  virtual void Parse(const char *Value);
  virtual void Backup(void);
  virtual bool Set(void);
  virtual void Store(const char *PreStr);
  virtual void Create(cOsdMenu *menu);
  };

cOptCap::cOptCap(const char *Name, const char *Title, int *Storage, int Size, int Disp)
:cOpt(Name,Title)
{
  storage=Storage; size=Size; disp=Disp; len=sizeof(int)*size;
  value=MALLOC(int,size);
}

cOptCap::~cOptCap()
{
  free(value);
}

void cOptCap::Parse(const char *Value)
{
  memset(storage,0,len);
  int i=0;
  while(1) {
    char *p;
    const int c=strtol(Value,&p,10);
    if(c<=0 || p==Value || i>=size) return;
    storage[i++]=c;
    Value=p;
    }
}

void cOptCap::Backup(void)
{
  memcpy(value,storage,len);
}

bool cOptCap::Set(void)
{
  if(memcmp(value,storage,len)) { memcpy(storage,value,len); return true; }
  return false;
}

void cOptCap::Store(const char *PreStr)
{
  char b[256];
  for(int i=0,p=0; i<size; i++)
    if(storage[i]) p+=snprintf(b+p,sizeof(b)-p,"%d ",storage[i]);
  ScPlugin->SetupStore(FullName(PreStr),b);
}

void cOptCap::Create(cOsdMenu *menu)
{
  for(int i=0; i<disp; i++) {
    char buff[32];
    snprintf(buff,sizeof(buff),"%s %d",tr(title),i+1);
    menu->Add(new cMenuEditCapItem(buff,&value[i]));
    }
}

// --- cOpts -------------------------------------------------------------------

cOpts::cOpts(const char *PreStr, int NumOpts)
{
  preStr=PreStr;
  numOpts=NumOpts; numAdd=0;
  if((opts=MALLOC(cOpt *,numOpts))) memset(opts,0,sizeof(cOpt *)*numOpts);
}

cOpts::~cOpts()
{
  if(opts) {
    for(int i=0; i<numOpts; i++) delete opts[i];
    free(opts);
    }
}

void cOpts::Add(cOpt *opt)
{
  if(opts && numAdd<numOpts) opts[numAdd++]=opt;
}

bool cOpts::Parse(const char *Name, const char *Value)
{
  if(opts) {
    for(int i=0; i<numAdd; i++)
      if(opts[i] && opts[i]->Persistant() && !strcasecmp(Name,opts[i]->Name())) {
        opts[i]->Parse(Value);
        return true;
        }
    }
  return false;
}

bool cOpts::Store(void)
{
  bool res=false;
  if(opts) {
    for(int i=0; i<numAdd; i++)
      if(opts[i]) {
        if(opts[i]->Set()) res=true;
        if(opts[i]->Persistant()) opts[i]->Store(preStr);
        }
    }
  return res;
}

void cOpts::Backup(void)
{
  if(opts) {
    for(int i=0; i<numAdd; i++)
      if(opts[i]) opts[i]->Backup();
    }
}

void cOpts::Create(cOsdMenu *menu)
{
  if(opts) {
    for(int i=0; i<numAdd; i++)
      if(opts[i]) opts[i]->Create(menu);
    }
}

// --- cMenuInfoSc -------------------------------------------------------------

class cMenuInfoSc : public cOsdMenu {
public:
  cMenuInfoSc(void);
  virtual eOSState ProcessKey(eKeys Key);
  };

cMenuInfoSc::cMenuInfoSc(void)
:cOsdMenu(tr("SoftCAM"),25)
{
  Add(new cScInfoItem(tr("Current keys:")));
  for(int d=0; d<MAXDVBAPI; d++) {
    char *ks=cSoftCAM::CurrKeyStr(d);
    if(ks) {
      char buffer[32];
      snprintf(buffer,sizeof(buffer),"  [%s %d]","DVB",d+1);
      Add(new cScInfoItem(buffer,ks));
      free(ks);
      }
    }
  Add(new cScInfoItem(tr("Logger (AutoUpdate):")));
  int fk, nk;
  cSoftCAM::KeyStats(fk,nk);
  Add(new cScInfoItem(tr("  [Seen keys]"),fk));
  Add(new cScInfoItem(tr("  [New keys]"), nk));
#ifdef VDR3
  Display();
#endif
}

eOSState cMenuInfoSc::ProcessKey(eKeys Key)
{
  eOSState state=cOsdMenu::ProcessKey(Key);
  if(state==osUnknown && Key==kOk) state=osBack;
  return state;
}

// --- cMenuInfoCard -----------------------------------------------------------

#ifdef SMARTCARD

#ifdef VDR3SKIN
#if VDRVERSNUM > 10307
#include <vdr/menu.h>
#else
class cMenuText : public cOsdMenu {
private:
  char *text;
public:
  cMenuText(const char *Title, const char *Text, eDvbFont Font = fontOsd);
  virtual ~cMenuText();
  void SetText(const char *Text);
  virtual void Display(void);
  virtual eOSState ProcessKey(eKeys Key);
  };

cMenuText::cMenuText(const char *Title, const char *Text, eDvbFont Font)
:cOsdMenu(Title)
{
  text = NULL;
  SetText(Text);
}

cMenuText::~cMenuText()
{
  free(text);
}

void cMenuText::SetText(const char *Text)
{
  free(text);
  text = strdup(Text);
}

void cMenuText::Display(void)
{
  cOsdMenu::Display();
  DisplayMenu()->SetText(text, true);//XXX define control character in text to choose the font???
}

eOSState cMenuText::ProcessKey(eKeys Key)
{
  switch (Key) {
    case kUp|k_Repeat:
    case kUp:
    case kDown|k_Repeat:
    case kDown:
    case kLeft|k_Repeat:
    case kLeft:
    case kRight|k_Repeat:
    case kRight:
                  DisplayMenu()->Scroll(NORMALKEY(Key) == kUp || NORMALKEY(Key) == kLeft, NORMALKEY(Key) == kLeft || NORMALKEY(Key) == kRight);
                  return osContinue;
    default: break;
    }

  eOSState state = cOsdMenu::ProcessKey(Key);

  if (state == osUnknown) {
     switch (Key) {
       case kOk: return osBack;
       default:  state = osContinue;
       }
     }
  return state;
}
#endif //VDRVERSNUM > 10307

class cMenuInfoCard : public cMenuText {
private:
  int port;
  char infoStr[1024];
public:
  cMenuInfoCard(int Port);
  virtual eOSState ProcessKey(eKeys Key);
  };

cMenuInfoCard::cMenuInfoCard(int Port)
:cMenuText(tr("Smartcard"),0)
{
  port=Port;
  smartcards.CardInfo(port,infoStr,sizeof(infoStr));
  SetText(infoStr);
  SetHelp(tr("Reset card"));
  Display();
}

eOSState cMenuInfoCard::ProcessKey(eKeys Key)
{
  if(Key==kRed && Interface->Confirm(tr("Really reset card?"))) {
    smartcards.CardReset(port);
    return osEnd;
    }
  eOSState state=cMenuText::ProcessKey(Key);
  if(state==osUnknown) state=osContinue;
  return state;
}

#else //VDR3SKIN

// --- cMenuInfoCard -----------------------------------------------------------

class cMenuInfoCard : public cOsdMenu {
private:
  int port;
  char infoStr[1024];
public:
  cMenuInfoCard(int Port);
  virtual eOSState ProcessKey(eKeys Key);
  };

cMenuInfoCard::cMenuInfoCard(int Port)
:cOsdMenu(tr("Smartcard"),25)
{
  port=Port;
  Add(new cScInfoItem(tr("Reset card"),(const char *)0,osUser1));
  smartcards.CardInfo(port,infoStr,sizeof(infoStr));
  cOsdItem *item=new cMenuTextItem(infoStr,0,4,Setup.OSDwidth,Setup.OSDheight-4);
  item->SetColor(clrWhite,clrBackground);
  Add(item);
}

eOSState cMenuInfoCard::ProcessKey(eKeys Key)
{
  eOSState state=cOsdMenu::ProcessKey(Key);
  if(state==osUser1 && Interface->Confirm(tr("Really reset card?"))) {
    smartcards.CardReset(port);
    state=osEnd;
    }
  else if(state!=osBack) state=osContinue;
  return state;
}

#endif //VDR3SKIN
#endif //SMARTCARD

// --- cMenuSetupSc ------------------------------------------------------------

static cOpts *ScOpts;

class cMenuSetupSc : public cMenuSetupPage {
private:
  char *cfgdir;
  //
  void SystemOpts(void);
protected:
  virtual void Store(void);
public:
  cMenuSetupSc(const char *CfgDir);
  virtual ~cMenuSetupSc();
  virtual eOSState ProcessKey(eKeys Key);
  };

#ifdef SMARTCARD
static eOSState portStates[] = { osUser1,osUser2,osUser3,osUser4 };
#if MAX_PORTS!=4
#error Update portStates[]
#endif
#endif

cMenuSetupSc::cMenuSetupSc(const char *CfgDir)
{
  cfgdir=strdup(CfgDir);
  SetSection(tr("SoftCAM"));
  ScOpts->Backup(); ScOpts->Create(this);
#ifdef SMARTCARD
  char id[IDSTR_LEN];
  for(int i=0; smartcards.ListCard(i,id,sizeof(id)); i++) {
    char buff[32];
    snprintf(buff,sizeof(buff),"%s %d",tr("Smartcard interface"),i);
    if(id[0]) Add(new cScInfoItem(buff,id,portStates[i]));
    else      Add(new cScInfoItem(buff,tr("(empty)")));
    }
#endif
  SystemOpts();
  Add(new cOsdItem(tr("Status information..."),osUser8));
  Add(new cOsdItem(tr("Reload files"),osUser9));
}

cMenuSetupSc::~cMenuSetupSc()
{
  free(cfgdir);
}

void cMenuSetupSc::SystemOpts(void)
{
  bool start=true;
  cOpts *opts;
  while((opts=cSystems::GetSystemOpts(start))) {
    opts->Create(this);
    start=false;
    }
}

void cMenuSetupSc::Store(void)
{
  ScOpts->Store();
  cSystems::ConfigStore();
}

eOSState cMenuSetupSc::ProcessKey(eKeys Key)
{
  eOSState state = cOsdMenu::ProcessKey(Key);

  switch(state) {
#ifdef SMARTCARD
    case osUser1...osUser7:
      for(unsigned int i=0; i<sizeof(portStates)/sizeof(eOSState); i++)
        if(portStates[i]==state) return(AddSubMenu(new cMenuInfoCard(i)));
      state=osContinue;
      break;
#endif

    case osUser8:
      return(AddSubMenu(new cMenuInfoSc));

    case osUser9:
      state=osContinue;
      if(!cSoftCAM::Active()) {
        if(Interface->Confirm(tr("Really reload files?"))) {
          Store();
          cSoftCAM::Load(cfgdir);
          state=osEnd;
          }
        }
      else 
#ifdef VDR3SKIN
        Skins.Message(mtError,tr("Active! Can't reload files now"));
#else
        Interface->Error(tr("Active! Can't reload files now"));
#endif
      break;

    case osUnknown:
      if(Key==kOk) { Store(); state=osBack; }
      break;

    default:
      break;
    }
  return state;
}

// -- cScStatus ----------------------------------------------------------------

class cScStatus : public cStatus {
protected:
  virtual void ChannelSwitch(const cDevice *device, int chNumber);
  };

void cScStatus::ChannelSwitch(const cDevice *device, int chNumber)
{
  if(chNumber>0) {
    //d(printf("status: card=%d primary=%d dev=%p actDev=%p\n",
    //  device->CardIndex(),device->IsPrimaryDevice(),device,cDevice::ActualDevice()))
    cChannel *channel=Channels.GetByNumber(chNumber);
    if(!device->IsPrimaryDevice() || cDevice::ActualDevice()==device) {
      int ca;
      if(channel && (ca=channel->Ca())>=CACONFBASE) {
#ifndef VDR3
        for(int i=0 ; i<MAXSCCAPS ; i++) {
          if(ScSetup.ScCaps[i]==ca) {
            cCam *cam=cSoftCAM::FindCAM(device);
            if(!cam) cam=cSoftCAM::Init(device);
            if(cam) cam->SetChannel(channel->Sid(),channel->Source(),channel->Frequency());
            return;
            }
          }
#else
        cCam *cam=cSoftCAM::FindCAM(device);
        if(!cam) cam=cSoftCAM::Init(device);
        if(cam) cam->SetChannel(channel->Sid(),channel->Source(),channel->Transponder());
        return;
#endif
        }
      cCam *cam=cSoftCAM::FindCAM(device);
      if(cam) cam->SetChannel(0);
      }
    }
}

// --- cScSetup ---------------------------------------------------------------

cScSetup ScSetup;

cScSetup::cScSetup(void)
{
  LoggerActive = 0;
  LoggerTimeout = 0;
  memset(ScCaps,0,sizeof(ScCaps));
  ScCaps[0] = 101;
  ScCaps[1] = 102;
}

// --- cSoftCAM ---------------------------------------------------------------

struct CamInfo cSoftCAM::camInfo[MAXDVBAPI] = {};
cMutex cSoftCAM::camLock;
int cSoftCAM::foundKeys=0;
int cSoftCAM::newKeys=0;

bool cSoftCAM::Load(const char *cfgdir)
{
  ecmcache.Load();
  if(!keys.Load(cfgdir)) esyslog("ERROR: no keys loaded for softcam!");
  if(!cSystems::Init(cfgdir)) return false;
  cLoaders::LoadCache(cfgdir);
  Save();
#ifdef DEBUG
  printf("softcam: ScCaps are");
  for(int i=0; i<MAXSCCAPS ; i++) printf(" %d",ScSetup.ScCaps[i]);
  printf("\n");
#endif
  return true;
}

void cSoftCAM::Save(void)
{
  cLoaders::SaveCache();
}

void cSoftCAM::ShutDown(void)
{
  for(int n=0 ; n<MAXDVBAPI ; n++) {
    cCam *cam=camInfo[n].cam;
    if(cam) delete cam;
    }
  cSystems::Clean();
}

bool cSoftCAM::CapCheck(int n)
{
  bool capMatch=false;
#ifdef VDR3
  for(int j=0; j<MAXSCCAPS; j++) {
    if(ScSetup.ScCaps[j] && ScSetup.ScCaps[j]==n+1) { capMatch=true; break; }
    }
#else
  for(int i=0; i<MAXCACAPS; i++) {
    const int ca=Setup.CaCaps[n][i];
    for(int j=0; j<MAXSCCAPS; j++) {
      if(ca>=CACONFBASE && ScSetup.ScCaps[j]==ca) { capMatch=true; break; }
      }
    }
#endif
  return capMatch;
}

cCam *cSoftCAM::Init(const cDevice *dev)
{
  cCam *cam=0;
  int n=dev->CardIndex();

  if(n<MAXDVBAPI) {
    cMutexLock lock(&camLock);
    if(camInfo[n].shutUp) return 0;
  
    if(CapCheck(n)) {
      if(typeid(*dev)==typeid(cDvbDevice)) {
        int camfd=-1;
        cCiHandler *ci=((cDevice *)dev)->CiHandler();
        if(ci) { // get camDev from ciHandler
          camfd=ci->GetCaFd();
          d(printf("softcam %d: got ca device from ciHandler\n",n))
          }
        if(camfd<0) { // last resort, try to open myself
          d(printf("softcam %d: try to open ca device myself\n",n))
          camfd=DvbOpen(DEV_DVB_CA,n,O_RDWR|O_NONBLOCK);
          }
#ifdef HAVE_SOFTCSA
        if(camfd<0) d(printf("softcam %d: no ca device, assuming budget card & SoftCSA\n",n))
        cam=new cCam(dev,n,camfd);
#else
        if(camfd>=0) cam=new cCam(dev,n,camfd);
        else esyslog("ERROR: can't open ca device for softcam on card %d",n);
#endif
        }
      else esyslog("ERROR: softcam doesn't support card %d!",n);
      }

    if(cam) camInfo[n].cam=cam;
    else camInfo[n].shutUp=true;
    }
  return cam;
}

cCam *cSoftCAM::FindCAM(const cDevice *dev)
{
  cMutexLock lock(&camLock);
  const int n=dev->CardIndex();
  return n<MAXDVBAPI ? camInfo[n].cam : 0;
}

char *cSoftCAM::CurrKeyStr(int CardNum)
{
  cMutexLock lock(&camLock);
  cCam *cam=camInfo[CardNum].cam;
  char *str=0;
  if(cam) str=cam->CurrentKeyStr();
  if(!str && CapCheck(CardNum)) str=strdup(tr("(none)"));
  return str;
}

bool cSoftCAM::Active(void)
{
  cMutexLock lock(&camLock);
  for(int n=0 ; n<MAXDVBAPI ; n++) {
    cCam *cam=camInfo[n].cam;
    if(cam && cam->CurrentPrg()) return true;
    }
  return false;
}

// --- cScPlugin ---------------------------------------------------------------

class cScPlugin : public cPlugin {
private:
  cScStatus *status;
public:
  cScPlugin(void);
  virtual ~cScPlugin();
  virtual const char *Version(void);
  virtual const char *Description(void);
  virtual const char *CommandLineHelp(void);
  virtual bool ProcessArgs(int argc, char *argv[]);
  virtual bool Initialize(void);
  virtual cMenuSetupPage *SetupMenu(void);
  virtual bool SetupParse(const char *Name, const char *Value);
  };

cScPlugin::cScPlugin(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!
  status=0;
  static const char *logg[] = { "off","always on","non-recording on" };
  ScOpts=new cOpts(0,3);
  ScOpts->Add(new cOptSel("LoggerActive" ,"Key logger"        ,&ScSetup.LoggerActive ,3,logg));
  ScOpts->Add(new cOptInt("LoggerTimeout","Key logger timeout",&ScSetup.LoggerTimeout,0,9999));
  ScOpts->Add(new cOptCap("ScCaps"       ,"CICAM Caps"        , ScSetup.ScCaps       ,MAXSCCAPS,2));
}

cScPlugin::~cScPlugin()
{
  cSoftCAM::ShutDown();
  delete status;
  delete ScOpts;
}

bool cScPlugin::Initialize(void)
{
  const int version = VDRVERSNUM / 10000;
  const int major   = (VDRVERSNUM / 100) % 100;
  const int minor   = VDRVERSNUM % 100;
  if( version<MIN_VERS ||
     (version==MIN_VERS && major<MIN_MAJOR) ||
     (version==MIN_VERS && major==MIN_MAJOR && minor<MIN_MINOR)) {
    esyslog("ERROR: SC plugin needs at least VDR version %d.%d.%d",MIN_VERS,MIN_MAJOR,MIN_MINOR);
    fprintf(stderr,"SC plugin needs at least VDR version %d.%d.%d\n",MIN_VERS,MIN_MAJOR,MIN_MINOR);
    return false;
    }
  if(sizeof(int)!=4) {
    esyslog("ERROR: compiled with 'int' as %d bit. Only supporting 32 bit.",sizeof(int)*8);
    fprintf(stderr,"compiled with 'int' as %d bit. Only supporting 32 bit.\n",sizeof(int)*8);
    return false;
    }
  if(sizeof(long long)!=8) {
    esyslog("ERROR: compiled with 'long long' as %d bit. Only supporting 64 bit.",sizeof(long long)*8);
    fprintf(stderr,"compiled with 'long long' as %d bit. Only supporting 64 bit.\n",sizeof(long long)*8);
    return false;
    }
    
  ScPlugin=this;
  RegisterI18n(Phrases);
  filemaps.SetCfgDir(ConfigDirectory());
  if(!cSoftCAM::Load(ConfigDirectory())) return false;
  status=new cScStatus;
#ifdef SMARTCARD
#ifdef DEFAULT_PORT
  smartcards.AddPort(DEFAULT_PORT);
#endif
  smartcards.LaunchWatcher();
#endif
  return true;
}

const char *cScPlugin::Version(void)
{
  return VERSION;
}

const char *cScPlugin::Description(void)
{
  return tr(DESCRIPTION);
}

const char *cScPlugin::CommandLineHelp(void)
{
  static char *help_str=0;
  
  free(help_str);    //                                     for easier orientation, this is column 80|
  asprintf(&help_str,"  -I        --inverse-cd   use inverse CD detection for the next serial device\n"
                     "  -R        --inverse-rst  use inverse RESET for the next serial device\n"
                     "  -s DEV,   --serial=DEV   activate Phoenix ISO interface on serial device DEV\n"
                     "                           (default: %s)\n"
                     "  -d CMD,   --dialup=CMD   call CMD to start/stop dialup-network\n"
                     "                           (default: %s)\n"
                     "  -t SECS,  --timeout=SECS shutdown timeout for dialup-network\n"
                     "                           (default: %d secs)\n",
                     "none","none",
#ifdef NETWORK
                     netTimeout
#else
                     60
#endif
                     );
  return help_str;
}

bool cScPlugin::ProcessArgs(int argc, char *argv[])
{
  static struct option long_options[] = {
      { "serial",      required_argument, NULL, 's' },
      { "inverse-cd",  no_argument,       NULL, 'I' },
      { "inverse-rst", no_argument,       NULL, 'R' },
      { "dialup",      required_argument, NULL, 'd' },
      { NULL }
    };

  int c, option_index=0;
#ifdef SMARTCARD
  bool invCD=false, invRST=false;
#endif
  while((c=getopt_long(argc,argv,"d:s:t:IR",long_options,&option_index))!=-1) {
    switch (c) {
#ifdef SMARTCARD
      case 'I': invCD=true; break;
      case 'R': invRST=true; break;
      case 's': smartcards.AddPort(optarg,invCD,invRST); invCD=false; invRST=false; break;
#else
      case 'I':
      case 'R':
      case 's': fprintf(stderr, "sc: smartcard support has not been compiled in!\n"); return false;
#endif
#ifdef NETWORK
      case 'd': netscript=optarg; break;
      case 't': netTimeout=atoi(optarg)*1000; break;
#else
      case 'd':
      case 't': fprintf(stderr, "sc: network support has not been compiled in!\n"); return false;
#endif
      default:  return false;
      }
    }
  return true;
}

cMenuSetupPage *cScPlugin::SetupMenu(void)
{
  return new cMenuSetupSc(ConfigDirectory());
}

bool cScPlugin::SetupParse(const char *Name, const char *Value)
{
#ifdef VDR3
  if(!*Name) {
    static const char magic[] = SC_MAGIC;
    struct ScLink *link=(struct ScLink *)Name;
    if(!memcmp(link->magic,magic,sizeof(magic))) {
      cCam *cam=cSoftCAM::FindCAM(link->dev);
      if(!cam) cam=cSoftCAM::Init(link->dev);
      if(cam) {
        switch(link->op) {
          case OP_PROVIDES:
            link->num=cam->ProvidesCa(link->caids);
            break;
          case OP_IGNORE:
            {
            const cChannel *channel=link->channel;
            cam->IgnoreChannel(channel->Sid(),channel->Source(),channel->Transponder());
            }
            break;
          }
        }
      }
    return false;
    }
  else
#endif
  if(!ScOpts->Parse(Name,Value) && !cSystems::ConfigParse(Name,Value))
    return false;
  return true;
}

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