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

#include <string>
#include <getopt.h>
#include <stdlib.h>
#include <vdr/interface.h>
#include <vdr/plugin.h>
#include "i18n.h"
#include <sys/wait.h>
#include <unistd.h>
#include "ChannelMap.h"
#include <vdr/videodir.h>
#include <ctype.h>

// Socket Headers
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <unistd.h>


// VPS was introduced with VDR 1.3.5
#if VDRVERSNUM >= 10305
#define VPSAVAIL
#endif	        

#define INT_SIZEOFBUF 200 * 1024 // Buffer 200Kb

extern char **environ;

#define DEF_TVTV_SERVER "www.tvtv.de"

#define DEF_TVTV_SCHEDULE_UID	"uid"
#define DEF_TVTV_SCHEDULE_CHN	"channel"
#define DEF_TVTV_SCHEDULE_STM	"starttime"
#define DEF_TVTV_SCHEDULE_VTM	"vps"
#define DEF_TVTV_SCHEDULE_ETM	"endtime"
#define DEF_TVTV_SCHEDULE_TIT	"title"
#define DEF_TVTV_SCHEDULE_NAT	"nature"
#define DEF_TVTV_SCHEDULE_DESC	"description"
#define DEF_TVTV_SCHEDULE_PERS	"persons"
#define DEF_TVTV_SCHEDULE_FRM	"format"
#define DEF_TVTV_SCHEDULE_FSK	"pg"
#define DEF_TVTV_SCHEDULE_ACT	"action"

static const char *VERSION        = "0.2.3";
static const char *DESCRIPTION    = "TVTV Timers update";
static const char *MAINMENUENTRY  = "TVTV";

class cUpdate : public cThread {
private:
    bool active;
public:
    cUpdate();
    ~cUpdate();
    virtual void StartUpdate();
protected:
    virtual void Action(void);
    virtual int  Load_HTTP_File(char** sBuffer, const char* sHost, int iPort, const char* sFileName);
    virtual bool MakeTimerUpdate(void);
    virtual void ProcessImportedFile(const char *sBuffer);
    virtual int  FindChannelSID(const char *sChannel);
    virtual cChannel *GetChannelByServiceID(int ServiceID);

    virtual int  calc_field_cnt(std::string *s);
    virtual char *strip_str(char *s);
    virtual char **split_csv(const char *job_line, int field_cnt);
    virtual std::string *cUpdate::read_line_from_buffer(const char *buf, unsigned int *idx);
};

// Global variables that control the overall behaviour:

char sUserName[30] = "";
char sPassword[30] = "";
char sEMail[100]   = "";
int  iUpdateTime   = 0;
int  bAutoUpdate   = 0;
int  bWithFolders  = 0;
int  bShowInMainMenu = 1;

#ifdef VPSAVAIL
int  bUseVps = 1;
#endif	        

cUpdate *oUpdate;

char sPackedString[30]; // TVTV identify code

class cPluginTVTV : public cPlugin {
private:
  char *option_sUserName;
  char *option_sPassword;
  char *option_sEMail;
  char *option_sUpdateTime;
public:
  cPluginTVTV(void);
  virtual ~cPluginTVTV();
  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 Start(void);
  virtual void Housekeeping(void);
  virtual const char *MainMenuEntry(void) { return (bShowInMainMenu ? MAINMENUENTRY : NULL); }
  virtual cOsdMenu *MainMenuAction(void);
  virtual cMenuSetupPage *SetupMenu(void);
  virtual bool SetupParse(const char *Name, const char *Value);
  virtual void Get_Packed_String(char *sOut);
  };

// --- cMenuSetupTVTV -------------------------------------------------------

class cMenuSetupTVTV : public cMenuSetupPage {
private:
  char sNewUserName[30];
  char sNewPassword[30];
  char sNewEMail[100];
  int  iNewUpdateTime;
  int  bNewAutoUpdate;
  int  bNewWithFolders;
  int  bNewShowInMainMenu;
#ifdef VPSAVAIL
  int  bNewUseVps;
#endif	        
protected:
  virtual void Store(void);
public:
  cMenuSetupTVTV(void);
  };

cMenuSetupTVTV::cMenuSetupTVTV(void)
{
  strcpy(sNewUserName, sUserName);
  strcpy(sNewPassword, sPassword);
  strcpy(sNewEMail, sEMail);
  bNewAutoUpdate = bAutoUpdate;
  iNewUpdateTime = iUpdateTime;
  bNewWithFolders = bWithFolders;
  bNewShowInMainMenu = bShowInMainMenu;
  Add(new cMenuEditStrItem( tr("TVTV UserName"), sNewUserName, sizeof(sNewUserName), tr(FileNameChars)));
  Add(new cMenuEditStrItem( tr("TVTV Password"), sNewPassword, sizeof(sNewPassword), tr(FileNameChars)));
  Add(new cMenuEditStrItem( tr("Your EMail (TVTV Options)"), sNewEMail, sizeof(sNewEMail), tr(FileNameChars)));
  Add(new cMenuEditBoolItem(tr("AutoUpdate"),       &bNewAutoUpdate));
  Add(new cMenuEditIntItem( tr("Updatetime (min)"), &iNewUpdateTime));
  Add(new cMenuEditBoolItem( tr("Extended FileFormat"), &bNewWithFolders));
  Add(new cMenuEditBoolItem( tr("Show In Main Menu"), &bNewShowInMainMenu));

#ifdef VPSAVAIL
  bNewUseVps = bUseVps;
  Add(new cMenuEditBoolItem( tr("Use VPS"), &bNewUseVps));
#endif
}

void cMenuSetupTVTV::Store(void)
{
  strcpy(sUserName, sNewUserName);
  strcpy(sPassword, sNewPassword);
  strcpy(sEMail, sNewEMail);
  bAutoUpdate = bNewAutoUpdate;
  iUpdateTime = iNewUpdateTime;
  bWithFolders = bNewWithFolders;
  bShowInMainMenu = bNewShowInMainMenu;
  SetupStore("UserName", sUserName);
  SetupStore("Password", sPassword);
  SetupStore("EMail", sEMail);
  SetupStore("AutoUpdate", bAutoUpdate);
  SetupStore("UpdateTime", iUpdateTime);
  SetupStore("ExtFileFormat", bWithFolders);
  SetupStore("ShowInMainMenu", bNewShowInMainMenu);

#ifdef VPSAVAIL
  bUseVps = bNewUseVps;
  SetupStore("UseVps", bNewUseVps);
#endif	        
  
  if (bAutoUpdate)
    if (oUpdate)
	oUpdate->StartUpdate();
}

// --- cPluginTVTV ----------------------------------------------------------

cPluginTVTV::cPluginTVTV(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!
    option_sUserName   = NULL;
    option_sPassword   = NULL;
    option_sEMail      = NULL;
    option_sUpdateTime = NULL;
    oUpdate = NULL;
}

cPluginTVTV::~cPluginTVTV()
{
  // Clean up after yourself!
    if (oUpdate)
	delete oUpdate;
}

const char *cPluginTVTV::CommandLineHelp(void)
{
  // Return a string that describes all known command line options.
  return "  -u UserName,   --user =UserName TVTV UserName\n"
         "  -p Password,   --pwd  =Pasword  TVTV User Password\n"
         "  -e E-Mail,     --email=E-Mail   TVTV User E-Mail\n"
         "  -t Time (s),   --time =Time (min) Update Time (0 - Autoupdate OFF)\n";
}

bool cPluginTVTV::ProcessArgs(int argc, char *argv[])
{
  // Implement command line argument processing here if applicable.
  static struct option long_options[] = {
       { "user",     required_argument, NULL, 'u' },
       { "pwd",      required_argument, NULL, 'p' },
       { "email",    required_argument, NULL, 'e' },
       { "time",     required_argument, NULL, 't' },
       { NULL }
     };

  int c;
  while ((c = getopt_long(argc, argv, "u:p:e:t", long_options, NULL)) != -1) {
        switch (c) {
          case 'u': option_sUserName = optarg;
                    break;
          case 'p': option_sPassword = optarg;
                    break;
          case 'e': option_sEMail = optarg;
                    break;
          case 't': option_sUpdateTime = optarg;
                    break;
          default:  return false;
          }
        }
  return true;
}

/* ---- MD5 ---- */
/* The four core functions - F1 is optimized somewhat */

/* #define F1(x, y, z) (x & y | ~x & z) */
#define F1(x, y, z) (z ^ (x & (y ^ z)))
#define F2(x, y, z) F1(z, x, y)
#define F3(x, y, z) (x ^ y ^ z)
#define F4(x, y, z) (y ^ (x | ~z))

/* This is the central step in the MD5 algorithm. */
#define MD5STEP(f, w, x, y, z, data, s) \
    ( w += f(x, y, z) + data,  w = w<<s | w>>(32-s),  w += x )

// MD5

typedef unsigned long uint32;

struct MD5Context {
    uint32_t buf[4];
    uint32_t bits[2];
    unsigned char in[64];
};
			
typedef struct MD5Context MD5_CTX;
#define byteReverse(buf, len)	/* Nothing */

/*
 * The core of the MD5 algorithm, this alters an existing MD5 hash to
 * reflect the addition of 16 longwords of new data.  MD5Update blocks
 * the data and converts bytes into longwords for this routine.
*/
void MD5Transform(uint32_t buf[4], uint32_t in[16])
{
    register uint32_t a, b, c, d;
    
    a = buf[0];
    b = buf[1];
    c = buf[2];
    d = buf[3];
			    
    MD5STEP(F1, a, b, c, d, in[0] + 0xd76aa478, 7);
    MD5STEP(F1, d, a, b, c, in[1] + 0xe8c7b756, 12);
    MD5STEP(F1, c, d, a, b, in[2] + 0x242070db, 17);
    MD5STEP(F1, b, c, d, a, in[3] + 0xc1bdceee, 22);
    MD5STEP(F1, a, b, c, d, in[4] + 0xf57c0faf, 7);
    MD5STEP(F1, d, a, b, c, in[5] + 0x4787c62a, 12);
    MD5STEP(F1, c, d, a, b, in[6] + 0xa8304613, 17);
    MD5STEP(F1, b, c, d, a, in[7] + 0xfd469501, 22);
    MD5STEP(F1, a, b, c, d, in[8] + 0x698098d8, 7);
    MD5STEP(F1, d, a, b, c, in[9] + 0x8b44f7af, 12);
    MD5STEP(F1, c, d, a, b, in[10] + 0xffff5bb1, 17);
    MD5STEP(F1, b, c, d, a, in[11] + 0x895cd7be, 22);
    MD5STEP(F1, a, b, c, d, in[12] + 0x6b901122, 7);
    MD5STEP(F1, d, a, b, c, in[13] + 0xfd987193, 12);
    MD5STEP(F1, c, d, a, b, in[14] + 0xa679438e, 17);
    MD5STEP(F1, b, c, d, a, in[15] + 0x49b40821, 22);

    MD5STEP(F2, a, b, c, d, in[1] + 0xf61e2562, 5);
    MD5STEP(F2, d, a, b, c, in[6] + 0xc040b340, 9);
    MD5STEP(F2, c, d, a, b, in[11] + 0x265e5a51, 14);
    MD5STEP(F2, b, c, d, a, in[0] + 0xe9b6c7aa, 20);
    MD5STEP(F2, a, b, c, d, in[5] + 0xd62f105d, 5);
    MD5STEP(F2, d, a, b, c, in[10] + 0x02441453, 9);
    MD5STEP(F2, c, d, a, b, in[15] + 0xd8a1e681, 14);
    MD5STEP(F2, b, c, d, a, in[4] + 0xe7d3fbc8, 20);
    MD5STEP(F2, a, b, c, d, in[9] + 0x21e1cde6, 5);
    MD5STEP(F2, d, a, b, c, in[14] + 0xc33707d6, 9);
    MD5STEP(F2, c, d, a, b, in[3] + 0xf4d50d87, 14);
    MD5STEP(F2, b, c, d, a, in[8] + 0x455a14ed, 20);
    MD5STEP(F2, a, b, c, d, in[13] + 0xa9e3e905, 5);
    MD5STEP(F2, d, a, b, c, in[2] + 0xfcefa3f8, 9);
    MD5STEP(F2, c, d, a, b, in[7] + 0x676f02d9, 14);
    MD5STEP(F2, b, c, d, a, in[12] + 0x8d2a4c8a, 20);

    MD5STEP(F3, a, b, c, d, in[5] + 0xfffa3942, 4);
    MD5STEP(F3, d, a, b, c, in[8] + 0x8771f681, 11);
    MD5STEP(F3, c, d, a, b, in[11] + 0x6d9d6122, 16);
    MD5STEP(F3, b, c, d, a, in[14] + 0xfde5380c, 23);
    MD5STEP(F3, a, b, c, d, in[1] + 0xa4beea44, 4);
    MD5STEP(F3, d, a, b, c, in[4] + 0x4bdecfa9, 11);
    MD5STEP(F3, c, d, a, b, in[7] + 0xf6bb4b60, 16);
    MD5STEP(F3, b, c, d, a, in[10] + 0xbebfbc70, 23);
    MD5STEP(F3, a, b, c, d, in[13] + 0x289b7ec6, 4);
    MD5STEP(F3, d, a, b, c, in[0] + 0xeaa127fa, 11);
    MD5STEP(F3, c, d, a, b, in[3] + 0xd4ef3085, 16);
    MD5STEP(F3, b, c, d, a, in[6] + 0x04881d05, 23);
    MD5STEP(F3, a, b, c, d, in[9] + 0xd9d4d039, 4);
    MD5STEP(F3, d, a, b, c, in[12] + 0xe6db99e5, 11);
    MD5STEP(F3, c, d, a, b, in[15] + 0x1fa27cf8, 16);
    MD5STEP(F3, b, c, d, a, in[2] + 0xc4ac5665, 23);

    MD5STEP(F4, a, b, c, d, in[0] + 0xf4292244, 6);
    MD5STEP(F4, d, a, b, c, in[7] + 0x432aff97, 10);
    MD5STEP(F4, c, d, a, b, in[14] + 0xab9423a7, 15);
    MD5STEP(F4, b, c, d, a, in[5] + 0xfc93a039, 21);
    MD5STEP(F4, a, b, c, d, in[12] + 0x655b59c3, 6);
    MD5STEP(F4, d, a, b, c, in[3] + 0x8f0ccc92, 10);
    MD5STEP(F4, c, d, a, b, in[10] + 0xffeff47d, 15);
    MD5STEP(F4, b, c, d, a, in[1] + 0x85845dd1, 21);
    MD5STEP(F4, a, b, c, d, in[8] + 0x6fa87e4f, 6);
    MD5STEP(F4, d, a, b, c, in[15] + 0xfe2ce6e0, 10);
    MD5STEP(F4, c, d, a, b, in[6] + 0xa3014314, 15);
    MD5STEP(F4, b, c, d, a, in[13] + 0x4e0811a1, 21);
    MD5STEP(F4, a, b, c, d, in[4] + 0xf7537e82, 6);
    MD5STEP(F4, d, a, b, c, in[11] + 0xbd3af235, 10);
    MD5STEP(F4, c, d, a, b, in[2] + 0x2ad7d2bb, 15);
    MD5STEP(F4, b, c, d, a, in[9] + 0xeb86d391, 21);

    buf[0] += a;
    buf[1] += b;
    buf[2] += c;
    buf[3] += d;
}

/*
 * Start MD5 accumulation.  Set bit count to 0 and buffer to mysterious
 * initialization constants.
*/
void MD5Init(MD5_CTX *ctx)
{
   ctx->buf[0] = 0x67452301;
   ctx->buf[1] = 0xefcdab89;
   ctx->buf[2] = 0x98badcfe;
   ctx->buf[3] = 0x10325476;
	   
   ctx->bits[0] = 0;
   ctx->bits[1] = 0;
}
			   

/*
 * Update context to reflect the concatenation of another buffer full
 * of bytes.
*/
void MD5Update(MD5_CTX *ctx, unsigned char *buf, unsigned len)
{
    uint32_t t;
       
    /* Update bitcount */
	   
    t = ctx->bits[0];
    if ((ctx->bits[0] = t + ((uint32_t) len << 3)) < t)
    ctx->bits[1]++; 	/* Carry from low to high */
    ctx->bits[1] += len >> 29;
			
    t = (t >> 3) & 0x3f;	/* Bytes already in shsInfo->data */

    /* Handle any leading odd-sized chunks */
    
    if (t) {
        unsigned char *p = (unsigned char *) ctx->in + t;
	    
	t = 64 - t;
        if (len < t) {
	    memcpy(p, buf, len);
	    return;
	}
        memcpy(p, buf, t);
	byteReverse(ctx->in, 16);
        MD5Transform(ctx->buf, (uint32_t *) ctx->in);
	buf += t;
        len -= t;
    }
											    
    /* Process data in 64-byte chunks */
    
    while (len >= 64) {
	memcpy(ctx->in, buf, 64);
	byteReverse(ctx->in, 16);
	MD5Transform(ctx->buf, (uint32_t *) ctx->in);
	buf += 64;
	len -= 64;
    }
				
    /* Handle any remaining bytes of data. */
		    
    memcpy(ctx->in, buf, len);
}

/*
 * Final wrapup - pad to 64-byte boundary with the bit pattern 
 * 1 0* (64-bit count of bits processed, MSB-first)
*/
void MD5Final(unsigned char digest[16], MD5_CTX *ctx)
{
    unsigned count;
    unsigned char *p;
	   
    /* Compute number of bytes mod 64 */
    count = (ctx->bits[0] >> 3) & 0x3F;
		   
   /* Set the first char of padding to 0x80.  This is safe since there is
      always at least one byte free */
    p = ctx->in + count;
    *p++ = 0x80;
				      
    /* Bytes of padding needed to make 64 bytes */
    count = 64 - 1 - count;
	
    /* Pad out to 56 mod 64 */
    if (count < 8) {
        /* Two lots of padding:  Pad the first block to 64 bytes */
	memset(p, 0, count);
        byteReverse(ctx->in, 16);
	MD5Transform(ctx->buf, (uint32_t *) ctx->in);
				
        /* Now fill the next block with 56 bytes */
	memset(ctx->in, 0, 56);
    } else {
	/* Pad block to 56 bytes */
        memset(p, 0, count - 8);
    }
    byteReverse(ctx->in, 14);
					    				      
    /* Append length in bits and transform */
    ((uint32_t *) ctx->in)[14] = ctx->bits[0];
    ((uint32_t *) ctx->in)[15] = ctx->bits[1];
	    
    MD5Transform(ctx->buf, (uint32_t *) ctx->in);
    byteReverse((unsigned char *) ctx->buf, 4);
    memcpy(digest, ctx->buf, 16);
    memset(ctx, 0, sizeof(ctx));        /* In case it's sensitive */
}

uint8_t toHex(const uint8_t &x)
{
    return x > 9 ? x + 55: x + 48;
}
    
void URLEncode(const char *sIn, char *sOut)
{
    register char *pOutTmp = NULL;
    char *pOutBuf = NULL;
    register char *pInTmp = NULL;
    char *pInBuf = (char *)sIn;
    
    //alloc out buffer
    pOutBuf = sOut;
	
    if(pOutBuf)
    {
        pInTmp	= pInBuf;
        pOutTmp = pOutBuf;
				    
	// do encoding
	while (*pInTmp)
	{
	    if(isalnum(*pInTmp))
	        *pOutTmp++ = *pInTmp;
	    else
	        if(isspace(*pInTmp))
		    *pOutTmp++ = '+';
	        else
	        {
	            *pOutTmp++ = '%';
		    *pOutTmp++ = toHex(*pInTmp>>4);
	            *pOutTmp++ = toHex(*pInTmp%16);
		}
	    pInTmp++;
	}
	*pOutTmp = '\0';
    }
}

void cPluginTVTV::Get_Packed_String(char *sOut)
{
    char *s;
    unsigned char signature[17];
    
    asprintf(&s, "EPGSync%s%s", sUserName, sPassword);

    MD5_CTX ctx;
    MD5Init(&ctx);
    MD5Update(&ctx, (unsigned char *)s, strlen(s));
    MD5Final(signature, &ctx);
    signature[16] = 0;

    char aStr[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
    
    char aOut[30];
    int j = 0;
	    
    aOut[16] = '\0';
		
    for (int i = 0; i < 16; i+= 3)
    {
	uint8_t x, y;
		
	x = signature[i];
	x >>= 2;
	x = x & 0x3f;
	aOut[j++] = aStr[x];
								
	y = signature[i];
	y = y & 0x3;
	y <<= 4;
									
	x = signature[i + 1];
	x >>= 4;
	x = x & 0x0f;
	x = x ^ y;
	aOut[j++] = aStr[x];
																
	if (i + 1 < 16)
	{
	    y = signature[i + 1];
	    y = y & 0x0f;
	    y <<= 2;
						    
	    // 3
	    x = signature[i + 2];
	    x = x & 0xff;
	    x >>= 6;
	    x = x ^ y;
	    aOut[j++] = aStr[x];
															    
	    // 4
	    x = signature[i + 2];
	    x &= 0x3f;
	    aOut[j++] = aStr[x];
	}
	else
	{
	    aOut[j++] = (char) 0x3d;
	    aOut[j++] = (char) 0x3d;
	}
			
	aOut[j] = '\0';
    }

    strcpy(sOut, aOut);							    
		    
    free(s);
}
				
cUpdate::cUpdate()
{
    active = false;
}

cUpdate::~cUpdate()
{
    if (active)
    {
	active = false;
	Cancel(3);
    }
}

void cUpdate::StartUpdate()
{
    if (!active)
	Start();
    else
    {
	active = false;
	Cancel(3);
	usleep(250);
	Start();
    }
}

void cUpdate::Action(void)
{
    dsyslog("TVTV Timer Thread started (pid=%d)", getpid());
    
    active = true;

    do    
    {
	MakeTimerUpdate();

	if (bAutoUpdate && iUpdateTime > 0)
	    for (long i = 0; active && i < iUpdateTime * 60 * 2; i++)
		usleep(500000);
    }
    while (active && bAutoUpdate && iUpdateTime > 0);
    
    active = false;
    
    dsyslog("TVTV Timer Thread ended (pid=%d)", getpid());
    
    Cancel(0);
}

int cUpdate::Load_HTTP_File(char** sBuffer, const char* sHost, int iPort, const char* sFileName)
{
    int nReturn = -1;
    *sBuffer = (char *) calloc(INT_SIZEOFBUF+1, sizeof(char));
    int sock = socket(AF_INET, SOCK_STREAM, 0);
    
    if (sock != -1)
    {
	struct sockaddr_in server_addr;
	server_addr.sin_family = AF_INET;
	
	struct hostent *host;
	
	host = gethostbyname(sHost);
	if (host != NULL)
	{
	    server_addr.sin_addr.s_addr = *(long *)host->h_addr;
	    server_addr.sin_port = htons(iPort);

	    char *sHTMLReq = NULL;
	    asprintf(&sHTMLReq, "GET %s HTTP/1.0\n"
				"User-Agent: VDRAgent\n"
				"Host: %s:%d\n\n",
				sFileName, sHost, iPort);

	    if (sHTMLReq)
	    {
		if (connect( sock, (struct sockaddr *)&server_addr, sizeof(server_addr)) == 0)
		{
		    write(sock, sHTMLReq, strlen(sHTMLReq));
		    
		    int iBytesRead = 0;
		    int iBytes = 1;
		    
		    while ( iBytes > 0 )
		    {
			fd_set rfds;
			struct timeval tv;
			int retval;
			
			iBytes = 0;
			
			FD_ZERO(&rfds);
			FD_SET(sock, &rfds);
			tv.tv_sec = 10;
			tv.tv_usec = 0;
			
			retval = select(sock + 1, &rfds, NULL, NULL, &tv);
			
			if (retval)
			{
			    iBytes = read(sock, *sBuffer + iBytesRead, INT_SIZEOFBUF - iBytesRead);
			    
			    if (iBytes > 0)
				iBytesRead += iBytes;
			}
		    }
		    
		    (*sBuffer)[iBytesRead] = '\0';
		    
		    nReturn = iBytesRead;
		}
	    
		free(sHTMLReq);
	    }
	}
	close(sock);
    }
    
    return nReturn;
}

bool cUpdate::MakeTimerUpdate(void)
{
    char *sEnEMail = NULL;
    char *sEnCryptS = NULL;
    char *s = NULL;
    char *sBuf = NULL;
    
    sEnEMail = (char *)calloc(100, sizeof (char));
    sEnCryptS = (char *)calloc(100, sizeof (char));
    URLEncode(sEMail, sEnEMail);    
    URLEncode(sPackedString, sEnCryptS);    

    asprintf(&s, "/cgi-bin/WebObjects/TVSync.woa/wa/getjobs?&serial=0&account=%s&product=35&target=%s&doctype=csv&access=%s", sUserName, DEF_TVTV_SERVER, sEnCryptS);
    isyslog("TVTV Timer Make Update");
    
    int iFileSize = Load_HTTP_File(&sBuf, DEF_TVTV_SERVER, 80, s);
    
    if (iFileSize > 0)
    {
	dsyslog("TVTV Receive %d Bytes", iFileSize);
	/* process CSV file */
	ProcessImportedFile(sBuf);
    
	isyslog("TVTV Timer File processed");
    }
    else
	isyslog("TVTV Download Error");
    
    if (sBuf)
	free(sBuf);
    if (s)
	free(s);
    if (sEnCryptS)
        free(sEnCryptS);
    if (sEnEMail)
	free(sEnEMail);
    
    return true;
}

int cUpdate::FindChannelSID(const char *sChannel)
{
    int iReturn = -1;
    
    int iMaxChannel = sizeof(channel_map) / sizeof(channel_map_entry);
    for (int i = 0; iReturn == -1 && i < iMaxChannel; i++)
	if (strcmp(sChannel, channel_map[i].tvtv_name) == 0)
	    iReturn = channel_map[i].pnr;
	    
    return iReturn;
}

cChannel *cUpdate::GetChannelByServiceID(int ServiceID)
{
    for (cChannel *channel = Channels.First(); channel; channel = Channels.Next(channel))
    {
	if (!channel->GroupSep() && channel->Sid() == ServiceID)
	    return channel;
    }
    
    return NULL;
}
			 
int cUpdate::calc_field_cnt(std::string *s) {
  int n=0;
  unsigned int p = 0;

  if ((s == NULL) || (s->empty())) return 0;

  p = s->find(',', 0);
  while (p != std::string::npos) {
    n++;
    p = s->find(',', p+1);
  }
  n++;
  return n;
}

char *cUpdate::strip_str(char *s) {
  char *m=NULL;
  unsigned int i=0;

  if (s==NULL) return NULL;

  m=(char *)calloc(strlen(s)+1, sizeof(char));

  for (i=0; i<strlen(s); i++) {
    if (s[i] == '"') {
      if (s[i+1] == '"') {
	strncat(m, s+i, 1);
	i++;
      }
    } else if ((s[i] == '\\')&&(s[i+1] == 'n')) {
      strncat(m, "\n", 1);
      i++;
    } else {
      strncat(m, s+i, 1);
    }
  }
  return m;
}


char **cUpdate::split_csv(const char *job_line, int field_cnt) {

  char **arr=NULL;
  char *sp=NULL, *ep=NULL;
  char *s=NULL;
  int n=0;

  if ((job_line == NULL)||(field_cnt == 0)) { return NULL; }

  arr=(char **)calloc(field_cnt, sizeof(char *));

  sp=(char *)job_line;
  while (n < field_cnt) {
    if (*sp == '"') {
      sp++;
      ep=index(sp, '"');
      while ( *(ep+1) == '"' ) { ep=index(ep+2, '"'); }
      ep++;
    } else {
      ep=index(sp, ',');
      if (ep == NULL) {
	ep=(char *)job_line + strlen(job_line);
      }
    }
    s=(char *)calloc(ep-sp+1, sizeof(char));
    strncpy(s, sp, ep-sp);
    arr[n]=strip_str(s);
    free(s);
    
    sp=ep+1;
    n++;

  }
  return arr;
}


std::string *cUpdate::read_line_from_buffer(const char *buf, unsigned int *idx) {
  std::string s;

  if (buf == NULL) return NULL;

  while ((buf[*idx] != 0)&&(buf[*idx] != '\n')) {
    if (!iscntrl(buf[*idx])) s.append(1, buf[*idx]);
    (*idx)++;
  }
  while ( (buf[*idx] != 0) && (iscntrl(buf[*idx]) )) (*idx)++;
  return new std::string(s);
}

void cUpdate::ProcessImportedFile(const char *sBuffer)
{
    char **fields=NULL;
    char **tvtvjob=NULL;
    int field_cnt=0;
    unsigned int p=0;
    bool vps = false;
    int iSID=0;

    time_t tStartTime, tVpsTime, tEndTime;
    struct tm tStart, tVps, tEnd;

    int  StartTZ_h, StartTZ_m;
    char StartTZ_s;
    int  VpsTZ_h, VpsTZ_m;
    char VpsTZ_s;
    int  EndTZ_h, EndTZ_m;
    char EndTZ_s;
		
    char *sDescBegin=NULL;
    char *sTimer = NULL;
    cTimer *oTimer = NULL;
    cChannel *oTVTVChannel = NULL;

    std::string *sLine=NULL;
    std::string *sField=NULL;
  
    std::string sUID;
    std::string sChannel;
    std::string sStartTime;
    std::string sVpsTime;
    std::string sEndTime;
    std::string sTitle;
    std::string sDescription;
    std::string sPersons;
    std::string sNature;
    std::string sFormat;
    std::string sFSK;
    std::string sAction;
    std::string s;


    
    dsyslog("TVTV: Processing CSV data");

    // skip header
    do {
      sLine = read_line_from_buffer(sBuffer, &p);
      field_cnt = calc_field_cnt(sLine);
    } while (( field_cnt < 10 ) && ( !sLine->empty() ));
    fields=split_csv(sLine->c_str(), field_cnt);
    dsyslog("TVTV: Found %d fields in CSV data", field_cnt);

    // read timer jobs and create timers
    dsyslog("TVTV: Start reading timer jobs");
    sLine = read_line_from_buffer(sBuffer, &p);
    while (!sLine->empty()) {

      tvtvjob=split_csv(sLine->c_str(), field_cnt);

      for (int i=0; i<field_cnt; i++) {
	sField = new std::string(fields[i]);
	if (*sField == DEF_TVTV_SCHEDULE_UID)         { sUID = tvtvjob[i];
	} else if (*sField == DEF_TVTV_SCHEDULE_CHN)  { sChannel = tvtvjob[i];
	} else if (*sField == DEF_TVTV_SCHEDULE_STM)  { sStartTime = tvtvjob[i];
	} else if (*sField == DEF_TVTV_SCHEDULE_ETM)  { sEndTime = tvtvjob[i];
	} else if (*sField == DEF_TVTV_SCHEDULE_VTM)  { sVpsTime = tvtvjob[i];
	} else if (*sField == DEF_TVTV_SCHEDULE_TIT)  { sTitle = tvtvjob[i];
	} else if (*sField == DEF_TVTV_SCHEDULE_DESC) { sDescription = tvtvjob[i];
	} else if (*sField == DEF_TVTV_SCHEDULE_PERS) { sPersons = tvtvjob[i];
	} else if (*sField == DEF_TVTV_SCHEDULE_NAT)  { sNature = tvtvjob[i];
	} else if (*sField == DEF_TVTV_SCHEDULE_FRM)  { sFormat = tvtvjob[i];
	} else if (*sField == DEF_TVTV_SCHEDULE_FSK)  { sFSK = tvtvjob[i];
	} else if (*sField == DEF_TVTV_SCHEDULE_ACT)  { sAction = tvtvjob[i];
	}
	delete sField;
	free(tvtvjob[i]);
      }
      free(tvtvjob);
      
      
      iSID = FindChannelSID(sChannel.c_str());
      if (iSID > 0) {

	oTVTVChannel = GetChannelByServiceID(iSID);
	if (oTVTVChannel) {

	  oTimer = new cTimer;
			
	  // Calc Start and Stop Time
	  sscanf(sStartTime.c_str(), "%04d-%02d-%02d %02d:%02d:%02d %c%02d%02d",
		 &tStart.tm_year, &tStart.tm_mon, &tStart.tm_mday,
		 &tStart.tm_hour, &tStart.tm_min, &tStart.tm_sec,
		 &StartTZ_s, &StartTZ_h, &StartTZ_m);
	      
	  sscanf(sVpsTime.c_str(), "%04d-%02d-%02d %02d:%02d:%02d %c%02d%02d",
		 &tVps.tm_year, &tVps.tm_mon, &tVps.tm_mday,
		 &tVps.tm_hour, &tVps.tm_min, &tVps.tm_sec,
		 &VpsTZ_s, &VpsTZ_h, &VpsTZ_m);

	  sscanf(sEndTime.c_str(), "%04d-%02d-%02d %02d:%02d:%02d %c%02d%02d",
		 &tEnd.tm_year, &tEnd.tm_mon, &tEnd.tm_mday,
		 &tEnd.tm_hour, &tEnd.tm_min, &tEnd.tm_sec,
		 &EndTZ_s, &EndTZ_h, &EndTZ_m);
		  
	  tStart.tm_year -= 1900;
	  tStart.tm_mon  -= 1;
	  tStart.tm_isdst = -1;
	    
	  tVps.tm_year -= 1900;
	  tVps.tm_mon  -= 1;
	  tVps.tm_isdst = -1;
			
	  tEnd.tm_year -= 1900;
	  tEnd.tm_mon  -= 1;
	  tEnd.tm_isdst = -1;
			
	  tStartTime = timegm(&tStart);
	  tVpsTime   = timegm(&tVps);
	  tEndTime   = timegm(&tEnd);

#ifdef VPSAVAIL
	  vps = Setup.UseVps && (tVpsTime != -1) && bUseVps;
#endif	        

	  tStartTime -= (StartTZ_h*60 + StartTZ_m)*60*((StartTZ_s == '-')?(-1):(1));
	  tEndTime   -= (EndTZ_h*60 + EndTZ_m)*60*((EndTZ_s == '-')?(-1):(1));
	  if (vps) tVpsTime -= (VpsTZ_h*60 + VpsTZ_m)*60*((VpsTZ_s == '-')?(-1):(1));
			
	  tStartTime -= Setup.MarginStart * 60;
	  if (!vps) tEndTime += Setup.MarginStop * 60;
	  localtime_r(&tStartTime, &tStart);
	  localtime_r(&tVpsTime, &tVps);
	  localtime_r(&tEndTime, &tEnd);
			
	  sTimer = (char *) calloc(10240, sizeof(char));
			
	  if (bWithFolders && ! sNature.empty())
	    s = sNature + "~" + sTitle;
	  else
	    s = sTitle;
	      
	  sprintf(sTimer, "%d:%s:%d:%04d:%04d:%d:%d:%s:",
		  vps ? 5 : 1,
// type of return value changed in VDR 1.3.18
#if VDRVERSNUM >= 10318
		  *(oTVTVChannel->GetChannelID().ToString()),
#else
		  oTVTVChannel->GetChannelID().ToString(),
#endif	        
		  tStart.tm_mday,
		  vps ? tVps.tm_hour   * 100 + tVps.tm_min :
		  tStart.tm_hour * 100 + tStart.tm_min,
		  tEnd.tm_hour   * 100 + tEnd.tm_min,
		  Setup.DefaultPriority,
		  Setup.DefaultLifetime,
		  s.c_str());
			
	  sDescBegin = (char *) (sTimer + strlen(sTimer));
			
	  s = sTitle;
	  if (sNature.length() > 0)  s += "||Nature: " + sNature;
	  if (sFormat.length() > 0)  s += "|Format: " + sFormat;
	  if (sFSK.length() > 0)     s += "|FSK: " + sFSK;
	  if (sPersons.length() > 0) s += "||Darsteller: " + sPersons;
	  s += "||" + sDescription;
			
	  strcat(sTimer, s.c_str());
			
	  while (*sDescBegin != 0 && (sDescBegin - sTimer < 10240)) {
	    if ( (*sDescBegin) == '\n') *sDescBegin = '|';
	    sDescBegin++;
	  }

	  if (oTimer->Parse(sTimer)) {
	    cTimer *t = Timers.GetTimer(oTimer);
	    if (t) { // Timer exists
	      delete oTimer;
	      if (sAction == "delete") {
		if (t->Recording()) {
		  isyslog("timer %d recording, will be deleted after recording has ended (%s)", t->Index() + 1, t->File());
		} else {
		  isyslog("timer %d deleted (%s)", t->Index() + 1, t->File());
		  Timers.Del(t);
		}
	      } else {
		isyslog("timer %d exist (%s)", t->Index() + 1, t->File());
	      }
	    } else { // if (t)
	      if (sAction == "rec") {
		Timers.Add(oTimer);
		isyslog("timer %d added (%s)", oTimer->Index() + 1, oTimer->File());
	      }
	    }
	  } else {
	    isyslog("TVTV Timer Error: %s", sTimer);
	  }
	  free(sTimer);

	} else {
	  isyslog("TVTV ChannelsMap.h ist not up-to-date ,-)");
	}

      } else { // if (iSID > 0)
	isyslog("TVTV Channel <%s> is not found!", sChannel.c_str());
      }

      // read next line
      sLine = read_line_from_buffer(sBuffer, &p);

    } // while (!sLine->empty())

    Timers.Save();
}


bool cPluginTVTV::Start(void)
{
    // Start any background activities the plugin shall perform.
    RegisterI18n(Phrases);
    Get_Packed_String(sPackedString);
    isyslog("TVTV Packed String: %s", sPackedString);
    
    if (strlen(sUserName) == 0 && option_sUserName)
	strcpy(sUserName, option_sUserName);

    if (strlen(sPassword) == 0 && option_sPassword)
	strcpy(sPassword, option_sPassword);

    if (strlen(sEMail) == 0 && option_sEMail)
	strcpy(sEMail, option_sEMail);

    if (iUpdateTime == 0 && option_sUpdateTime)
    {
	char s[10];
	
	strncpy(s, option_sUpdateTime, 10);
	iUpdateTime = atoi(s);
    }

    oUpdate = new cUpdate();
    
    if (oUpdate && bAutoUpdate)
	oUpdate->StartUpdate();
    
    return true;
}

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

cOsdMenu *cPluginTVTV::MainMenuAction(void)
{
  // Perform the action when selected from the main VDR menu.
  if (oUpdate) oUpdate->StartUpdate();

  Interface->Confirm(tr("TVTV update will be started"), 1);
  
  return NULL;
}

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

bool cPluginTVTV::SetupParse(const char *Name, const char *Value)
{
  // Parse your own setup parameters and store their values.

  if      (!strcasecmp(Name, "UserName"))    strcpy(sUserName, Value);
  else if (!strcasecmp(Name, "Password"))    strcpy(sPassword, Value);
  else if (!strcasecmp(Name, "EMail"))       strcpy(sEMail, Value);
  else if (!strcasecmp(Name, "AutoUpdate"))  bAutoUpdate = atoi(Value);
  else if (!strcasecmp(Name, "UpdateTime"))  iUpdateTime = atoi(Value);
  else if (!strcasecmp(Name, "ExtFileFormat")) bWithFolders = atoi(Value);
  else if (!strcasecmp(Name, "ShowInMainMenu")) bShowInMainMenu = atoi(Value);
#ifdef VPSAVAIL
  else if (!strcasecmp(Name, "UseVps"))      bUseVps = atoi(Value);
#endif	        
  else
     return false;
  return true;
}

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