/*
 * 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 "stdafx.h"

#include <string.h>

#include "vdr/tools.h"

#include "common.h"
#include "system.h"
#include "cc.h"
#include "network.h"
#include "misc.h"
#include "opts.h"

#define SYSTEM_NAME			"Cardclient"
#define SYSTEM_PRI			-15

#define CONF_FILE			"cardclient.conf"

#define strcasecmp			_stricmp

static int immediate=true;

// -- cCardClient --------------------------------------------------------------

cCardClient::cCardClient(const char *Name)
:msECM(32,16)
,msEMM(32,0)
{
	name=Name;
	emmAllowed=false; emmCaid[0]=0x1700; emmMask[0]=0xFF00; numCaid=1;
}

bool cCardClient::Immediate(void)
{
	return immediate;
}

bool cCardClient::ParseStdConfig(const char *config, int *num)
{
	int n, emm=0;
	if(!num) num=&n;
	if(sscanf(config,"%63[^:]:%d:%d%n",hostname,&port,&emm,num)<3) return false;
	if(emm>0) emmAllowed=true;
	if(config[*num]=='/') {
		numCaid=0;
		do {
			emmMask[numCaid]=0xFFFF;
			int start=(*num)+1;
			if(sscanf(&config[start],"%x%n/%x%n",&emmCaid[numCaid],num,&emmMask[numCaid],num)<1)
				return false;
			*num+=start;
			numCaid++;
		} while(numCaid<MAX_CC_CAID && config[*num]==',');
	}
	dc(printf("cc: hostname=%s port=%d emm=%d emmCaids",hostname,port,emmAllowed))
	for(int i=0; i<numCaid; i++) dc(printf(" %04x/%04x",emmCaid[i],emmMask[i]))
	dc(printf("\n"))
	return true;
}

bool cCardClient::CanHandle(unsigned short SysId)
{
	for(int i=0; i<numCaid; i++)
		if((SysId&emmMask[i])==emmCaid[i]) return true;
	return false;
}

bool cCardClient::SendMsg(cNetSocket *so, const unsigned char *data, int len)
{
	if(!so->Connected() && !Login()) return false;
	if(so->Write(data,len)<0) {
		dc(printf("cc: send error. reconnecting...\n"));
		so->Disconnect();
		return false;
	}
	return true;
}

int cCardClient::RecvMsg(cNetSocket *so, unsigned char *data, int len, int to)
{
	if(!so->Connected() && !Login()) return -1;
	int n=so->Read(data,len,to);
	if(n<0) {
		dc(printf("cc: recv error. reconnecting...\n"));
		so->Disconnect();
		return -1;
	}
	return n;
}

// -- cCardClients -------------------------------------------------------------

class cCardClients {
friend class cCardClientLink;
private:
	static cCardClientLink *first;
	//
	static void Register(cCardClientLink *ccl);
public:
	cCardClientLink *FindByName(const char *name);
};

cCardClientLink *cCardClients::first=0;

static cCardClients cardclients;

void cCardClients::Register(cCardClientLink *ccl)
{
	dyn(printf("cardclients: registering cardclient %s\n",ccl->name))
	ccl->next=first;
	first=ccl;
}

cCardClientLink *cCardClients::FindByName(const char *name)
{
	cCardClientLink *ccl=first;
	while(ccl) {
		if(!strcasecmp(ccl->name,name)) break;
		ccl=ccl->next;
	}
	return ccl;
}

// -- cCardClientLink ----------------------------------------------------------

cCardClientLink::cCardClientLink(const char *Name)
{
	name=Name;
	cCardClients::Register(this);
}

// -- cSystemLinkCardClient -----------------------------------------------------------

class cSystemLinkCardClient : public cSystemLink, private cConfRead, cSimpleList<cCardClient> {
protected:
	virtual bool ParseLine(const char *line, bool fromCache);
public:
	cSystemLinkCardClient(void);
	virtual bool CanHandle(unsigned short SysId);
	virtual cSystem *Create(void);
	virtual bool Init(const char *cfgdir);
	virtual void Clean(void);
	cCardClient *FindBySysId(unsigned short id, cCardClient *cc);
};

static cSystemLinkCardClient staticCcl;

// -- cSystemCardClient ---------------------------------------------------------------

class cSystemCardClient : public cSystem {
private:
	cCardClient *cc;
public:
	cSystemCardClient(void);
	virtual bool ProcessECM(const cEcmInfo *ecm, unsigned char *data);
};

cSystemCardClient::cSystemCardClient(void)
:cSystem(SYSTEM_NAME,SYSTEM_PRI)
{
	cc=0;
	expensive=true; hasLogger=true;
}

bool cSystemCardClient::ProcessECM(const cEcmInfo *ecm, unsigned char *data)
{
	cCardClient *startCc=cc;
	do {
		if(cc) {
#ifdef DEBUG_CC
			cTimeMs start;
#endif
			int id=cc->msECM.Get(data,SCT_LEN(data),cw);
			if(id==0 || (id>0 && cc->ProcessECM(ecm,data,cw))) {
#ifdef DEBUG_CC
				int dur=start.Elapsed();
				if(dur>2000) {
					char bb[32];
					time_t now=time(0);
					ctime_r(&now,bb); stripspace(bb);
					printf("cc: %s: lagged cw %d ms (%s)\n",bb,dur,cc->Name());
				}
#endif
				char buff[32];
				_snprintf(buff,sizeof(buff),"CC %s",cc->Name());
				KeyOK(buff);
				if(id>0) cc->msECM.Cache(id,true,cw);
				return true;
			}
			if(id>0) {
				dc(printf("cc: client %s (%s:%d) ECM failed (%d ms)\n",cc->Name(),cc->hostname,cc->port,(int)start.Elapsed()))
				cc->msECM.Cache(id,false,cw);
			}
		}
		if(!cc) dc(printf("cc: cc-loop\n"))
		cc=staticCcl.FindBySysId(ecm->caId,cc);
		if(cc && cc!=startCc) dc(printf("cc: now trying client %s (%s:%d)\n",cc->Name(),cc->hostname,cc->port))
	} while(cc!=startCc);
	return false;
}

// -- cSystemLinkCardClient -----------------------------------------------------------

cSystemLinkCardClient::cSystemLinkCardClient(void)
:cSystemLink(SYSTEM_NAME,SYSTEM_PRI)
{
	opts=new cOpts(SYSTEM_NAME,1);
	opts->Add(new cOptBool("Immediate","Cardclient: connect immediately",&immediate));
}

cCardClient *cSystemLinkCardClient::FindBySysId(unsigned short id, cCardClient *cc)
{
	if(cc) cc=Next(cc); else cc=First();
	while(cc) {
		if(cc->CanHandle(id)) return cc;
		cc=Next(cc);
	}
	return 0;
}

bool cSystemLinkCardClient::CanHandle(unsigned short SysId)
{
	return FindBySysId(SysId,0)!=0;
}

cSystem *cSystemLinkCardClient::Create(void)
{
	return new cSystemCardClient();
}

bool cSystemLinkCardClient::Init(const char *cfgdir)
{
	Clear();
	ConfRead("cardclient config",AddDirectory(cfgdir,CONF_FILE));
	d(printf("cc-link: created %d client(s)\n",Count()))
	return true;
}

bool cSystemLinkCardClient::ParseLine(const char *line, bool fromCache)
{
	char name[32];
	int num;
	if(sscanf(line,"%31[^:]:%n",name,&num)==1) {
		cCardClientLink *ccl=cardclients.FindByName(name);
		if(ccl) {
			cCardClient *cc=ccl->Create();
			if(cc) {
				if(cc->Init(&line[num])) {
					Add(cc);
					dc(printf("cc-link: client '%s' ready\n",cc->Name()))
					return true;
				}
				else {
					delete cc;
					d(printf("cc-link: init of client '%s' failed\n",name))
				}
			}
			else d(printf("cc-link: failed to create client '%s'\n",name))
		}
		else d(printf("cc-link: no client found for server type '%s'\n",name))
	}
	return false;
}

void cSystemLinkCardClient::Clean(void)
{
  Clear();
}
