/*
 * 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 <string.h>
#include <malloc.h>

#include "common.h"
#include "system.h"
#include "data.h"
#include "opts.h"
FILE* camlog;
// -- cKeySnoop ----------------------------------------------------------------

cKeySnoop::cKeySnoop(cSystem *Sys, int Type, int Id, int Keynr)
{
  ok=false;
  sys=Sys; type=Type; id=Id; keynr=Keynr;
  sys->FirstKey();
}

cKeySnoop::~cKeySnoop()
{
  if(!ok) 
    d(sys->KeyFail(type,id,keynr))
}

void cKeySnoop::OK(cPlainKey *pk)
{
  sys->KeyOK(pk);
  ok=true;
}

// -- cSystem ------------------------------------------------------------------

cSystem::cSystem(const char *Name, int Pri)
{
  name=Name; pri=Pri;
  lastType=0; currentKey=0; currentKeyStr[0]=0; doLog=true; cardNum=-1;
  // default config
  maxEcmTry=6; // try to get a key X times from the same ECM pid (default)
  hasLogger=false;
  needsLogger=false;
  expensive=false;
}

void cSystem::ParseCADescriptor(unsigned short sysId, const unsigned char *data, int len)
{
  AddEcm(new cEcmInfo(name,WORD(data,2,0x1FFF),sysId,0));
}

void cSystem::GetECMs(unsigned short sysId, const unsigned char *buff, int n, struct Ecms *Ecms)
{
  ecms=Ecms; ecms->num=0;
  ParseCADescriptor(sysId,buff,n);
}

bool cSystem::AddEcm(cEcmInfo *ecm)
{
  if(ecms->num<MAX_ECMS) {
    ecms->ecm[ecms->num]=ecm;
    ecms->num++;
    return true;
    }
  delete ecm;
  return false;
}

void cSystem::ParseCAT(const cEcmInfo *ecm, const unsigned char *buffer, int n)
{
  for(int index=8 ; index<n ;) {
    if(buffer[index]==0x09) {
      if(WORD(buffer,index+2,0xFFFF)==ecm->caId) {
        AddPid(WORD(buffer,index+4,0x1FFF),0x82,0xFF);
        break;
        }
      }
    index+=buffer[index+1]+2;
    }
}

void cSystem::GetEMMPids(const cEcmInfo *ecm, const unsigned char *buff, int n, struct SctPids *Pids)
{
  pids=Pids; pids->num=0;
  ParseCAT(ecm,buff,n);
}

bool cSystem::AddPid(int Pid, int Section, int Mask)
{
  if(pids->num<MAX_PIDS) {
    pids->pid[pids->num].pid=Pid;
    pids->pid[pids->num].sct=Section;
    pids->pid[pids->num].mask=Mask;
    pids->num++;
    return true;
    }
  return false;
}

void cSystem::FirstKey(void)
{
  firstKey=-1;
}

cPlainKey *cSystem::FastKey(int type, int id, int keynr)
{
  if(currentKey && currentKey->type==type && currentKey->id==id && currentKey->keynr==keynr) {
    if(firstKey<=0) {
      if(firstKey<0) { firstKey=0; return currentKey; }
      else currentKey=0;
      }
    }
  else currentKey=0;

  currentKey=keys.FindKey(type,id,keynr,currentKey);
  firstKey=1;
  return currentKey;
}

void cSystem::KeyOK(cPlainKey *pk)
{
  if(pk->type!=lastType || pk->id!=lastId || pk->keynr!=lastKeynr) {
    char *str=pk->Print();
    lastType=pk->type; lastId=pk->id; lastKeynr=pk->keynr;
    snprintf(currentKeyStr,sizeof(currentKeyStr),"%c %.2x %02x %s\n",lastType,lastId,lastKeynr,str);
#ifdef DEBUG
    //d(fprintf(camlog,"system: using key %c %.2x %02x %s\n",lastType,lastId,lastKeynr,str))
    doLog=true;
#endif
    free(str);
    }
}

void cSystem::KeyFail(int type, int id, int keynr)
{
#ifdef DEBUG
  if(type!=lastType || id!=lastId || keynr!=lastKeynr) {
    lastType=type; lastId=id; lastKeynr=keynr;
    if(doLog)
      printf("system: no key found for %c %.2x %02x\n",lastType,lastId,lastKeynr);
    }
#endif
}

// -- cSystemLink --------------------------------------------------------------

cSystemLink::cSystemLink(const char *Name, int Pri)
{
  name=Name; pri=Pri;
  opts=0; noFF=false;
  cSystems::Register(this);
}

cSystemLink::~cSystemLink()
{
  delete opts;
}

// -- cSystems -----------------------------------------------------------------

cSystemLink *cSystems::first=0;

void cSystems::Register(cSystemLink *sysLink)
{
  d(printf("systems: registering CA system %s, pri %d\n",sysLink->name,sysLink->pri))
  sysLink->next=first;
  first=sysLink;
}

int cSystems::Provides(const unsigned short *SysIds, bool ff)
{
  int n=0;
  while(*SysIds) {
    cSystemLink *sl=first;
    while(sl) {
      if(sl->CanHandle(*SysIds)) {
        if(sl->noFF && ff) return 0;
        n++;
        break;
        }
      sl=sl->next;
      }
    SysIds++;
    }
  return n;
}

cSystemLink *cSystems::FindByName(const char *Name)
{
  cSystemLink *sl=first;
  while(sl) {
    if(!strcasecmp(sl->name,Name)) return sl; 
    sl=sl->next;
    }
  return 0;
}

cSystem *cSystems::FindBySysName(unsigned short SysId, bool ff, const char *Name)
{
  cSystemLink *sl=FindByName(Name);
  if(sl && (!ff || !sl->noFF) && sl->CanHandle(SysId)) return sl->Create();
  return 0;
}

cSystem *cSystems::FindBySysId(unsigned short SysId, bool ff, int oldPri)
{
  // all pri's are negative!
  // oldPri = 0 -> get highest pri system
  // oldPri < 0 -> get highest pri system with pri<oldPri
  // oldPri > 0 -> get lowest pri system

  cSystemLink *sl=first, *csl=0;
  while(sl) {
    if((!ff || !sl->noFF) && sl->CanHandle(SysId)
       && sl->pri<oldPri
       && (!csl 
           || (oldPri<=0 && sl->pri>csl->pri)
           || (oldPri>0 && sl->pri<csl->pri)
          )
       ) csl=sl;
    sl=sl->next;
    }
  if(csl) return csl->Create();
  return 0;
}

bool cSystems::Init(const char *cfgdir)
{
  cSystemLink *sl=first;
  while(sl) {
    if(!sl->Init(cfgdir)) return false;
    sl=sl->next;
    }
  return true;
}

void cSystems::Clean(void)
{
  cSystemLink *sl=first;
  while(sl) {
    sl->Clean();
    sl=sl->next;
    }
}

bool cSystems::ConfigParse(const char *Name, const char *Value)
{
  char sysName[32];
  unsigned int i;
  for(i=0; Name[i] && Name[i]!='.' && i<sizeof(sysName); i++)
    sysName[i]=Name[i];
  if(Name[i]) {
    sysName[i]=0; i++;
    cSystemLink *sl=FindByName(sysName);
    if(sl && sl->opts) return sl->opts->Parse(&Name[i],Value);
    }
  return false;
}

void cSystems::ConfigStore(void)
{
  cSystemLink *sl=first;
  while(sl) {
    if(sl->opts) {
      if(sl->opts->Store()) sl->NewConfig();
      }
    sl=sl->next;
    }
}

cOpts *cSystems::GetSystemOpts(bool start)
{
  static cSystemLink *sl=0;
  sl=(start || !sl) ? first : sl->next;
  while(sl) {
    if(sl->opts) {
      sl->opts->Backup();
      return sl->opts;
      }
    sl=sl->next;
    }
  return 0;
}
