/***************************************************************************
                          eit.c  -  description
                             -------------------
    begin                : Fri Aug 25 2000
    copyright            : (C) 2000 by Robert Schneider
    email                : Robert.Schneider@web.de

    2001-08-15: Adapted to 'libdtv' by Rolf Hakenes <hakenes@hippomi.de>

 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   This program 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.                                   *
 *                                                                         *
 * $Id: eit.c 1.58 2002/11/01 10:05:56 kls Exp $
 ***************************************************************************/

#include "eit.h"
#include <ctype.h>
#include <fcntl.h>
#include <limits.h>
#include <linux/dvb/dmx.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/poll.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <time.h>
#include <unistd.h>
#include "channels.h"
#include "config.h"
#include "libdtv/libdtv.h"
#include "videodir.h"

// --- cMJD ------------------------------------------------------------------

class cMJD {
public:
   cMJD();
   cMJD(u_char date_hi, u_char date_lo);
   cMJD(u_char date_hi, u_char date_lo, u_char timehr, u_char timemi, u_char timese);
   ~cMJD();
  /**  */
  void ConvertToTime();
  /**  */
  bool SetSystemTime();
  /**  */
  time_t GetTime_t();
protected: // Protected attributes
  /**  */
  time_t mjdtime;
protected: // Protected attributes
  /**  */
  u_char time_second;
protected: // Protected attributes
  /**  */
  u_char time_minute;
protected: // Protected attributes
  /**  */
  u_char time_hour;
protected: // Protected attributes
  /**  */
  u_short mjd;
};

cMJD::cMJD()
{
}

cMJD::cMJD(u_char date_hi, u_char date_lo)
{
   mjd = date_hi << 8 | date_lo;
   time_hour = time_minute = time_second = 0;
   ConvertToTime();
}

cMJD::cMJD(u_char date_hi, u_char date_lo, u_char timehr, u_char timemi, u_char timese)
{
   mjd = date_hi << 8 | date_lo;
   time_hour = timehr;
   time_minute = timemi;
   time_second = timese;
   ConvertToTime();
}

cMJD::~cMJD()
{
}

/**  */
void cMJD::ConvertToTime()
{
   struct tm t;

   t.tm_sec = time_second;
   t.tm_min = time_minute;
   t.tm_hour = time_hour;
   int k;

   t.tm_year = (int) ((mjd - 15078.2) / 365.25);
   t.tm_mon = (int) ((mjd - 14956.1 - (int)(t.tm_year * 365.25)) / 30.6001);
   t.tm_mday = (int) (mjd - 14956 - (int)(t.tm_year * 365.25) - (int)(t.tm_mon * 30.6001));
   k = (t.tm_mon == 14 || t.tm_mon == 15) ? 1 : 0;
   t.tm_year = t.tm_year + k;
   t.tm_mon = t.tm_mon - 1 - k * 12;
   t.tm_mon--;

   t.tm_isdst = -1;
   t.tm_gmtoff = 0;

   mjdtime = timegm(&t);

   //isyslog("Time parsed = %s\n", ctime(&mjdtime));
}

/**  */
bool cMJD::SetSystemTime()
{
   struct tm *ptm;
   time_t loctim;

   struct tm tm_r;
   ptm = localtime_r(&mjdtime, &tm_r);
   loctim = time(NULL);

   if (abs(mjdtime - loctim) > 2)
   {
      isyslog("System Time = %s (%ld)\n", ctime(&loctim), loctim);
      isyslog("Local Time  = %s (%ld)\n", ctime(&mjdtime), mjdtime);
      if (stime(&mjdtime) < 0)
         esyslog("ERROR while setting system time: %m");
      return true;
   }

   return false;
}
/**  */
time_t cMJD::GetTime_t()
{
   return mjdtime;
}

// --- cTDT ------------------------------------------------------------------

class cTDT {
public:
   cTDT(tdt_t *ptdt);
   ~cTDT();
  /**  */
  bool SetSystemTime();
protected: // Protected attributes
  /**  */
  tdt_t tdt;
  /**  */
  cMJD mjd; // kls 2001-03-02: made this a member instead of a pointer (it wasn't deleted in the destructor!)
};

#define BCD2DEC(b) (((b >> 4) & 0x0F) * 10 + (b & 0x0F))

cTDT::cTDT(tdt_t *ptdt)
:tdt(*ptdt)
,mjd(tdt.utc_mjd_hi, tdt.utc_mjd_lo, BCD2DEC(tdt.utc_time_h), BCD2DEC(tdt.utc_time_m), BCD2DEC(tdt.utc_time_s))
{
}

cTDT::~cTDT()
{
}
/**  */
bool cTDT::SetSystemTime()
{
   return mjd.SetSystemTime();
}

// --- cEventInfo ------------------------------------------------------------

cEventInfo::cEventInfo(unsigned short serviceid, unsigned short eventid)
{
   pTitle = NULL;
   pSubtitle = NULL;
   pExtendedDescription = NULL;
   bIsPresent = bIsFollowing = false;
   lDuration = 0;
   tTime = 0;
   uTableID = 0;
   uEventID = eventid;
   uServiceID = serviceid;
   nChannelNumber = 0;
}

cEventInfo::~cEventInfo()
{
   free(pTitle);
   free(pSubtitle);
   free(pExtendedDescription);
}

/**  */
const char * cEventInfo::GetTitle() const
{
   return pTitle;
}
/**  */
const char * cEventInfo::GetSubtitle() const
{
   return pSubtitle;
}
/**  */
const char * cEventInfo::GetExtendedDescription() const
{
   return pExtendedDescription;
}
/**  */
bool cEventInfo::IsPresent() const
{
   return bIsPresent;
}
/**  */
void cEventInfo::SetPresent(bool pres)
{
   bIsPresent = pres;
}
/**  */
bool cEventInfo::IsFollowing() const
{
   return bIsFollowing;
}

void cEventInfo::SetTableID(unsigned char tableid)
{
   uTableID = tableid;
}

/**  */
void cEventInfo::SetFollowing(bool foll)
{
   bIsFollowing = foll;
}
/**  */
const char * cEventInfo::GetDate() const
{
   static char szDate[25];

   struct tm tm_r;
   strftime(szDate, sizeof(szDate), "%d.%m.%Y", localtime_r(&tTime, &tm_r));

   return szDate;
}

const unsigned char cEventInfo::GetTableID(void) const
{
   return uTableID;
}

/**  */
const char * cEventInfo::GetTimeString() const
{
   static char szTime[25];

   struct tm tm_r;
   strftime(szTime, sizeof(szTime), "%R", localtime_r(&tTime, &tm_r));

   return szTime;
}
/**  */
const char * cEventInfo::GetEndTimeString() const
{
   static char szEndTime[25];
   time_t tEndTime = tTime + lDuration;

   struct tm tm_r;
   strftime(szEndTime, sizeof(szEndTime), "%R", localtime_r(&tEndTime, &tm_r));

   return szEndTime;
}
/**  */
time_t cEventInfo::GetTime() const
{
   return tTime;
}
/**  */
long cEventInfo::GetDuration() const
{
   return lDuration;
}
/**  */
unsigned short cEventInfo::GetEventID() const
{
   return uEventID;
}
/**  */
void cEventInfo::SetTitle(const char *string)
{
   pTitle = strcpyrealloc(pTitle, string);
}
/**  */
void cEventInfo::SetSubtitle(const char *string)
{
   pSubtitle = strcpyrealloc(pSubtitle, string);
}
/**  */
void cEventInfo::SetExtendedDescription(const char *string)
{
   pExtendedDescription = strcpyrealloc(pExtendedDescription, string);
}
/**  */
void cEventInfo::SetTime(time_t t)
{
   tTime = t;
}
/**  */
void cEventInfo::SetDuration(long l)
{
   lDuration = l;
}
/**  */
void cEventInfo::SetEventID(unsigned short evid)
{
   uEventID = evid;
}
/**  */
void cEventInfo::SetServiceID(unsigned short servid)
{
   uServiceID = servid;
}

/**  */
unsigned short cEventInfo::GetServiceID() const
{
   return uServiceID;
}

/**  */
void cEventInfo::Dump(FILE *f, const char *Prefix) const
{
   if (tTime + lDuration >= time(NULL)) {
      fprintf(f, "%sE %u %ld %ld %X\n", Prefix, uEventID, tTime, lDuration, uTableID);
      if (!isempty(pTitle))
         fprintf(f, "%sT %s\n", Prefix, pTitle);
      if (!isempty(pSubtitle))
         fprintf(f, "%sS %s\n", Prefix, pSubtitle);
      if (!isempty(pExtendedDescription))
         fprintf(f, "%sD %s\n", Prefix, pExtendedDescription);
      fprintf(f, "%se\n", Prefix);
      }
}

bool cEventInfo::Read(FILE *f, cSchedule *Schedule)
{
  if (Schedule) {
     cEventInfo *pEvent = NULL;
     char *s;
     while ((s = readline(f)) != NULL) {
           char *t = skipspace(s + 1);
           switch (*s) {
             case 'E': if (!pEvent) {
                          unsigned int uEventID;
                          time_t tTime;
                          long lDuration;
                          unsigned int uTableID = 0;
                          int n = sscanf(t, "%u %ld %ld %X", &uEventID, &tTime, &lDuration, &uTableID);
                          if (n == 3 || n == 4) {
                             pEvent = (cEventInfo *)Schedule->GetEvent(uEventID, tTime);
                             if (!pEvent)
                                pEvent = Schedule->AddEvent(new cEventInfo(Schedule->GetServiceID(), uEventID));
                             if (pEvent) {
                                pEvent->SetTableID(uTableID);
                                pEvent->SetTime(tTime);
                                pEvent->SetDuration(lDuration);
                                }
                             }
                          }
                       break;
             case 'T': if (pEvent)
                          pEvent->SetTitle(t);
                       break;
             case 'S': if (pEvent)
                          pEvent->SetSubtitle(t);
                       break;
             case 'D': if (pEvent)
                          pEvent->SetExtendedDescription(t);
                       break;
             case 'e': pEvent = NULL;
                       break;
             case 'c': // to keep things simple we react on 'c' here
                       return true;
             default:  esyslog("ERROR: unexpected tag while reading EPG data: %s", s);
                       return false;
             }
           }
     esyslog("ERROR: unexpected end of file while reading EPG data");
     }
  return false;
}

#define MAXEPGBUGFIXSTATS 6
#define MAXEPGBUGFIXCHANS 50
struct tEpgBugFixStats {
  int hits;
  int n;
  unsigned short serviceIDs[MAXEPGBUGFIXCHANS];
  tEpgBugFixStats(void) { hits = n = 0; }
  };

tEpgBugFixStats EpgBugFixStats[MAXEPGBUGFIXSTATS];

static void EpgBugFixStat(int Number, unsigned int ServiceID)
{
  if (0 <= Number && Number < MAXEPGBUGFIXSTATS) {
     tEpgBugFixStats *p = &EpgBugFixStats[Number];
     p->hits++;
     int i = 0;
     for (; i < p->n; i++) {
         if (p->serviceIDs[i] == ServiceID)
            break;
         }
     if (i == p->n && p->n < MAXEPGBUGFIXCHANS)
        p->serviceIDs[p->n++] = ServiceID;
     }
}

static void ReportEpgBugFixStats(bool Reset = false)
{
  if (Setup.EPGBugfixLevel > 0) {
     bool GotHits = false;
     char buffer[1024];
     for (int i = 0; i < MAXEPGBUGFIXSTATS; i++) {
         const char *delim = "\t";
         tEpgBugFixStats *p = &EpgBugFixStats[i];
         if (p->hits) {
            if (!GotHits) {
               dsyslog("=====================");
               dsyslog("EPG bugfix statistics");
               dsyslog("=====================");
               dsyslog("IF SOMEBODY WHO IS IN CHARGE OF THE EPG DATA FOR ONE OF THE LISTED");
               dsyslog("CHANNELS READS THIS: PLEASE TAKE A LOOK AT THE FUNCTION cEventInfo::FixEpgBugs()");
               dsyslog("IN VDR/eit.c TO LEARN WHAT'S WRONG WITH YOUR DATA, AND FIX IT!");
               dsyslog("=====================");
               dsyslog("Fix\tHits\tChannels");
               GotHits = true;
               }
            char *q = buffer;
            q += snprintf(q, sizeof(buffer) - (q - buffer), "%d\t%d", i, p->hits);
            for (int c = 0; c < p->n; c++) {
                cChannel *channel = Channels.GetByServiceID(p->serviceIDs[c]);
                if (channel) {
                   q += snprintf(q, sizeof(buffer) - (q - buffer), "%s%s", delim, channel->Name());
                   delim = ", ";
                   }
                }
            dsyslog("%s", buffer);
            }
         if (Reset)
            p->hits = p->n = 0;
         }
     if (GotHits)
        dsyslog("=====================");
     }
}

void cEventInfo::FixEpgBugs(void)
{
  // VDR can't usefully handle newline characters in the EPG data, so let's
  // always convert them to blanks (independent of the setting of EPGBugfixLevel):
  strreplace(pTitle, '\n', ' ');
  strreplace(pSubtitle, '\n', ' ');
  strreplace(pExtendedDescription, '\n', ' ');

  if (Setup.EPGBugfixLevel == 0)
     return;

  // Some TV stations apparently have their own idea about how to fill in the
  // EPG data. Let's fix their bugs as good as we can:
  if (pTitle) {

     // VOX puts too much information into the Subtitle and leaves the Extended
     // Description empty:
     //
     // Title
     // (NAT, Year Min')[ ["Subtitle". ]Extended Description]
     //
     if (pSubtitle && !pExtendedDescription) {
        if (*pSubtitle == '(') {
           char *e = strchr(pSubtitle + 1, ')');
           if (e) {
              if (*(e + 1)) {
                 if (*++e == ' ')
                    if (*(e + 1) == '"')
                       e++;
                 }
              else
                 e = NULL;
              char *s = e ? strdup(e) : NULL;
              free(pSubtitle);
              pSubtitle = s;
              EpgBugFixStat(0, GetServiceID());
              // now the fixes #1 and #2 below will handle the rest
              }
           }
        }

     // VOX and VIVA put the Subtitle in quotes and use either the Subtitle
     // or the Extended Description field, depending on how long the string is:
     //
     // Title
     // "Subtitle". Extended Description
     //
     if ((pSubtitle == NULL) != (pExtendedDescription == NULL)) {
        char *p = pSubtitle ? pSubtitle : pExtendedDescription;
        if (*p == '"') {
           const char *delim = "\".";
           char *e = strstr(p + 1, delim);
           if (e) {
              *e = 0;
              char *s = strdup(p + 1);
              char *d = strdup(e + strlen(delim));
              free(pSubtitle);
              free(pExtendedDescription);
              pSubtitle = s;
              pExtendedDescription = d;
              EpgBugFixStat(1, GetServiceID());
              }
           }
        }

     // VOX and VIVA put the Extended Description into the Subtitle (preceeded
     // by a blank) if there is no actual Subtitle and the Extended Description
     // is short enough:
     //
     // Title
     //  Extended Description
     //
     if (pSubtitle && !pExtendedDescription) {
        if (*pSubtitle == ' ') {
           memmove(pSubtitle, pSubtitle + 1, strlen(pSubtitle));
           pExtendedDescription = pSubtitle;
           pSubtitle = NULL;
           EpgBugFixStat(2, GetServiceID());
           }
        }

     // Pro7 sometimes repeats the Title in the Subtitle:
     //
     // Title
     // Title
     //
     if (pSubtitle && strcmp(pTitle, pSubtitle) == 0) {
        free(pSubtitle);
        pSubtitle = NULL;
        EpgBugFixStat(3, GetServiceID());
        }

     // ZDF.info puts the Subtitle between double quotes, which is nothing
     // but annoying (some even put a '.' after the closing '"'):
     //
     // Title
     // "Subtitle"[.]
     //
     if (pSubtitle && *pSubtitle == '"') {
        int l = strlen(pSubtitle);
        if (l > 2 && (pSubtitle[l - 1] == '"' || (pSubtitle[l - 1] == '.' && pSubtitle[l - 2] == '"'))) {
           memmove(pSubtitle, pSubtitle + 1, l);
           char *p = strrchr(pSubtitle, '"');
           if (p)
              *p = 0;
           EpgBugFixStat(4, GetServiceID());
           }
        }

     if (Setup.EPGBugfixLevel <= 1)
        return;

     // Some channels apparently try to do some formatting in the texts,
     // which is a bad idea because they have no way of knowing the width
     // of the window that will actually display the text.
     // Remove excess whitespace:
     pTitle = compactspace(pTitle);
     pSubtitle = compactspace(pSubtitle);
     pExtendedDescription = compactspace(pExtendedDescription);
     // Remove superfluous hyphens:
     if (pExtendedDescription) {
        char *p = pExtendedDescription;
        while (*p && *(p + 1) && *(p + 2)) {
              if (*p == '-' && *(p + 1) == ' ' && *(p + 2) && islower(*(p - 1)) && islower(*(p + 2))) {
                 if (!startswith(p + 2, "und ")) { // special case in German, as in "Lach- und Sachgeschichten"
                    memmove(p, p + 2, strlen(p + 2) + 1);
                    EpgBugFixStat(5, GetServiceID());
                    }
                 }
              p++;
              }
        }

     // Some channels use the ` ("backtick") character, where a ' (single quote)
     // would be normally used. Actually, "backticks" in normal text don't make
     // much sense, so let's replace them:
     strreplace(pTitle, '`', '\'');
     strreplace(pSubtitle, '`', '\'');
     strreplace(pExtendedDescription, '`', '\'');
     }
}

// --- cSchedule -------------------------------------------------------------

cSchedule::cSchedule(unsigned short servid)
{
   pPresent = pFollowing = NULL;
   uServiceID = servid;
}


cSchedule::~cSchedule()
{
}

cEventInfo *cSchedule::AddEvent(cEventInfo *EventInfo)
{
  Events.Add(EventInfo);
  return EventInfo;
}

const cEventInfo *cSchedule::GetPresentEvent(void) const
{
  return GetEventAround(time(NULL));
}

const cEventInfo *cSchedule::GetFollowingEvent(void) const
{
  const cEventInfo *pe = NULL;
  time_t now = time(NULL);
  time_t delta = INT_MAX;
  for (cEventInfo *p = Events.First(); p; p = Events.Next(p)) {
      time_t dt = p->GetTime() - now;
      if (dt > 0 && dt < delta) {
         delta = dt;
         pe = p;
         }
      }
  return pe;
}

void cSchedule::SetServiceID(unsigned short servid)
{
   uServiceID = servid;
}
/**  */
unsigned short cSchedule::GetServiceID() const
{
   return uServiceID;
}
/**  */
const cEventInfo * cSchedule::GetEvent(unsigned short uEventID, time_t tTime) const
{
   // Returns either the event info with the given uEventID or, if that one can't
   // be found, the one with the given tTime (or NULL if neither can be found)
   cEventInfo *pe = Events.First();
   cEventInfo *pt = NULL;
   while (pe != NULL)
   {
      if (pe->GetEventID() == uEventID)
         return pe;
      if (tTime > 0 && pe->GetTime() == tTime) // 'tTime < 0' is apparently used with NVOD channels
         pt = pe;

      pe = Events.Next(pe);
   }

   return pt;
}

const cEventInfo *cSchedule::GetEventAround(time_t Time) const
{
  const cEventInfo *pe = NULL;
  time_t delta = INT_MAX;
  for (cEventInfo *p = Events.First(); p; p = Events.Next(p)) {
      time_t dt = Time - p->GetTime();
      if (dt >= 0 && dt < delta && p->GetTime() + p->GetDuration() >= Time) {
         delta = dt;
         pe = p;
         }
      }
  return pe;
}

bool cSchedule::SetPresentEvent(cEventInfo *pEvent)
{
   if (pPresent != NULL)
      pPresent->SetPresent(false);
   pPresent = pEvent;
   pPresent->SetPresent(true);

   return true;
}

/**  */
bool cSchedule::SetFollowingEvent(cEventInfo *pEvent)
{
   if (pFollowing != NULL)
      pFollowing->SetFollowing(false);
   pFollowing = pEvent;
   pFollowing->SetFollowing(true);

   return true;
}

/**  */
void cSchedule::Cleanup()
{
   Cleanup(time(NULL));
}

/**  */
void cSchedule::Cleanup(time_t tTime)
{
   cEventInfo *pEvent;
   for (int a = 0; true ; a++)
   {
      pEvent = Events.Get(a);
      if (pEvent == NULL)
         break;
      if (pEvent->GetTime() + pEvent->GetDuration() + 3600 < tTime) // adding one hour for safety
      {
         Events.Del(pEvent);
         a--;
      }
   }
}

/**  */
void cSchedule::Dump(FILE *f, const char *Prefix) const
{
   cChannel *channel = Channels.GetByServiceID(uServiceID);
   if (channel)
   {
      fprintf(f, "%sC %u %s\n", Prefix, uServiceID, channel->Name());
      for (cEventInfo *p = Events.First(); p; p = Events.Next(p))
         p->Dump(f, Prefix);
      fprintf(f, "%sc\n", Prefix);
   }
}

bool cSchedule::Read(FILE *f, cSchedules *Schedules)
{
  if (Schedules) {
     char *s;
     while ((s = readline(f)) != NULL) {
           if (*s == 'C') {
              unsigned int uServiceID;
              if (1 == sscanf(s + 1, "%u", &uServiceID)) {
                 cSchedule *p = (cSchedule *)Schedules->SetCurrentServiceID(uServiceID);
                 if (p) {
                    if (!cEventInfo::Read(f, p))
                       return false;
                    }
                 }
              }
           else {
              esyslog("ERROR: unexpected tag while reading EPG data: %s", s);
              return false;
              }
           }
     return true;
     }
  return false;
}

// --- cSchedules ------------------------------------------------------------

cSchedules::cSchedules()
{
   pCurrentSchedule = NULL;
   uCurrentServiceID = 0;
}

cSchedules::~cSchedules()
{
}
/**  */
const cSchedule *cSchedules::SetCurrentServiceID(unsigned short servid)
{
   pCurrentSchedule = GetSchedule(servid);
   if (pCurrentSchedule == NULL)
   {
      Add(new cSchedule(servid));
      pCurrentSchedule = GetSchedule(servid);
      if (pCurrentSchedule == NULL)
         return NULL;
   }

   uCurrentServiceID = servid;

   return pCurrentSchedule;
}
/**  */
const cSchedule * cSchedules::GetSchedule() const
{
   return pCurrentSchedule;
}
/**  */
const cSchedule * cSchedules::GetSchedule(unsigned short servid) const
{
   cSchedule *p;

   p = First();
   while (p != NULL)
   {
      if (p->GetServiceID() == servid)
         return p;
      p = Next(p);
   }

   return NULL;
}

/**  */
void cSchedules::Cleanup()
{
   cSchedule *p;

   p = First();
   while (p != NULL)
   {
      p->Cleanup(time(NULL));
      p = Next(p);
   }
}

/**  */
void cSchedules::Dump(FILE *f, const char *Prefix) const
{
   for (cSchedule *p = First(); p; p = Next(p))
      p->Dump(f, Prefix);
}

/**  */
bool cSchedules::Read(FILE *f)
{
   cMutexLock MutexLock;
   return cSchedule::Read(f, (cSchedules *)cSIProcessor::Schedules(MutexLock));
}

// --- cEIT ------------------------------------------------------------------

class cEIT {
private:
   cSchedules *schedules;
public:
   cEIT(unsigned char *buf, int length, cSchedules *Schedules);
   ~cEIT();
  /**  */
  int ProcessEIT(unsigned char *buffer);

protected: // Protected methods
  /** returns true if this EIT covers a
present/following information, false if it's
schedule information */
  bool IsPresentFollowing();
protected: // Protected attributes
  /** Table ID of this EIT struct */
  u_char tid;
};

cEIT::cEIT(unsigned char * buf, int length, cSchedules *Schedules)
{
   tid = buf[0];
   schedules = Schedules;
}

cEIT::~cEIT()
{
}

/**  */
int cEIT::ProcessEIT(unsigned char *buffer)
{
   cEventInfo *pEvent, *rEvent = NULL;
   cSchedule *pSchedule, *rSchedule = NULL;
   struct LIST *VdrProgramInfos;
   struct VdrProgramInfo *VdrProgramInfo;

   if (!buffer)
      return -1;

   VdrProgramInfos = createVdrProgramInfos(buffer);

   if (VdrProgramInfos) {
      for (VdrProgramInfo = (struct VdrProgramInfo *) VdrProgramInfos->Head; VdrProgramInfo; VdrProgramInfo = (struct VdrProgramInfo *) xSucc (VdrProgramInfo)) {
          // Drop events that belong to an "other TS" and are very long (some stations broadcast bogus data for "other" channels):
          if (VdrProgramInfo->Duration >= 86400 && (tid == 0x4F || tid == 0x60 || tid == 0x61))
             continue;
          pSchedule = (cSchedule *)schedules->GetSchedule(VdrProgramInfo->ServiceID);
          if (!pSchedule) {
             schedules->Add(new cSchedule(VdrProgramInfo->ServiceID));
             pSchedule = (cSchedule *)schedules->GetSchedule(VdrProgramInfo->ServiceID);
             if (!pSchedule)
                break;
             }
          if (VdrProgramInfo->ReferenceServiceID) {
             rSchedule = (cSchedule *)schedules->GetSchedule(VdrProgramInfo->ReferenceServiceID);
             if (!rSchedule)
                break;
             rEvent = (cEventInfo *)rSchedule->GetEvent((unsigned short)VdrProgramInfo->ReferenceEventID);
             if (!rEvent)
                break;
             }
          pEvent = (cEventInfo *)pSchedule->GetEvent((unsigned short)VdrProgramInfo->EventID, VdrProgramInfo->StartTime);
          if (!pEvent) {
             // If we don't have that event ID yet, we create a new one.
             // Otherwise we copy the information into the existing event anyway, because the data might have changed.
             pEvent = pSchedule->AddEvent(new cEventInfo(VdrProgramInfo->ServiceID, VdrProgramInfo->EventID));
             if (!pEvent)
                break;
             pEvent->SetTableID(tid);
             }
          else {
             // We have found an existing event, either through its event ID or its start time.
             // If the existing event has a zero table ID it was defined externally and shall
             // not be overwritten.
             if (pEvent->GetTableID() == 0x00)
                continue;
             // If the new event comes from a table that belongs to an "other TS" and the existing
             // one comes from an "actual TS" table, lets skip it.
             if ((tid == 0x4F || tid == 0x60 || tid == 0x61) && (pEvent->GetTableID() == 0x4E || pEvent->GetTableID() == 0x50 || pEvent->GetTableID() == 0x51))
                continue;
             }
          if (rEvent) {
             pEvent->SetTitle(rEvent->GetTitle());
             pEvent->SetSubtitle(rEvent->GetSubtitle());
             pEvent->SetExtendedDescription(rEvent->GetExtendedDescription());
             }
          else {
             pEvent->SetTableID(tid);
             pEvent->SetTitle(VdrProgramInfo->ShortName);
             pEvent->SetSubtitle(VdrProgramInfo->ShortText);
             pEvent->SetExtendedDescription(VdrProgramInfo->ExtendedName);
             //XXX kls 2001-09-22:
             //XXX apparently this never occurred, so I have simpified ExtendedDescription handling
             //XXX pEvent->AddExtendedDescription(VdrProgramInfo->ExtendedText);
             }
          pEvent->SetTime(VdrProgramInfo->StartTime);
          pEvent->SetDuration(VdrProgramInfo->Duration);
          pEvent->FixEpgBugs();
          if (IsPresentFollowing()) {
             if ((GetRunningStatus(VdrProgramInfo->Status) == RUNNING_STATUS_PAUSING) || (GetRunningStatus(VdrProgramInfo->Status) == RUNNING_STATUS_RUNNING))
                pSchedule->SetPresentEvent(pEvent);
             else if (GetRunningStatus(VdrProgramInfo->Status) == RUNNING_STATUS_AWAITING)
                pSchedule->SetFollowingEvent(pEvent);
             }
          }
      }

   xMemFreeAll(NULL);
   return 0;
}

/** returns true if this EIT covers a
present/following information, false if it's
schedule information */
bool cEIT::IsPresentFollowing()
{
   if (tid == 0x4e || tid == 0x4f)
      return true;

   return false;
}

// --- cSIProcessor ----------------------------------------------------------

#define MAX_FILTERS 20
#define EPGDATAFILENAME "epg.data"

int cSIProcessor::numSIProcessors = 0;
cSchedules *cSIProcessor::schedules = NULL;
cMutex cSIProcessor::schedulesMutex;
const char *cSIProcessor::epgDataFileName = EPGDATAFILENAME;
time_t cSIProcessor::lastDump = time(NULL);

/**  */
cSIProcessor::cSIProcessor(const char *FileName)
{
   fileName = strdup(FileName);
   masterSIProcessor = numSIProcessors == 0; // the first one becomes the 'master'
   filters = NULL;
   if (!numSIProcessors++) // the first one creates it
      schedules = new cSchedules;
   filters = (SIP_FILTER *)calloc(MAX_FILTERS, sizeof(SIP_FILTER));
   SetStatus(true);
   Start();
}

cSIProcessor::~cSIProcessor()
{
   if (masterSIProcessor)
      ReportEpgBugFixStats();
   active = false;
   Cancel(3);
   ShutDownFilters();
   free(filters);
   if (!--numSIProcessors) // the last one deletes it
      delete schedules;
   free(fileName);
}

const cSchedules *cSIProcessor::Schedules(cMutexLock &MutexLock)
{
  if (MutexLock.Lock(&schedulesMutex))
     return schedules;
  return NULL;
}

bool cSIProcessor::Read(FILE *f)
{
  bool OwnFile = f == NULL;
  if (OwnFile) {
     const char *FileName = GetEpgDataFileName();
     if (access(FileName, R_OK) == 0) {
        dsyslog("reading EPG data from %s", FileName);
        if ((f = fopen(FileName, "r")) == NULL) {
           LOG_ERROR;
           return false;
           }
        }
     else
        return false;
     }
  bool result = cSchedules::Read(f);
  if (OwnFile)
     fclose(f);
  return result;
}

void cSIProcessor::Clear(void)
{
  cMutexLock MutexLock(&schedulesMutex);
  delete schedules;
  schedules = new cSchedules;
}

void cSIProcessor::SetEpgDataFileName(const char *FileName)
{
  epgDataFileName = NULL;
  if (FileName)
     epgDataFileName = strdup(DirectoryOk(FileName) ? AddDirectory(FileName, EPGDATAFILENAME) : FileName);
}

const char *cSIProcessor::GetEpgDataFileName(void)
{
  if (epgDataFileName)
     return *epgDataFileName == '/' ? epgDataFileName : AddDirectory(VideoDirectory, epgDataFileName);
  return NULL;
}

void cSIProcessor::SetStatus(bool On)
{
   ShutDownFilters();
   if (On)
   {
      AddFilter(0x14, 0x70);  // TDT
      AddFilter(0x14, 0x73);  // TOT
      AddFilter(0x12, 0x4e);  // event info, actual TS, present/following
      AddFilter(0x12, 0x4f);  // event info, other TS, present/following
      AddFilter(0x12, 0x50);  // event info, actual TS, schedule
      AddFilter(0x12, 0x60);  // event info, other TS, schedule
      AddFilter(0x12, 0x51);  // event info, actual TS, schedule for another 4 days
      AddFilter(0x12, 0x61);  // event info, other TS, schedule for another 4 days
   }
}

/** use the vbi device to parse all relevant SI
information and let the classes corresponding
to the tables write their information to the disk */
void cSIProcessor::Action()
{
   dsyslog("EIT processing thread started (pid=%d)%s", getpid(), masterSIProcessor ? " - master" : "");

   time_t lastCleanup = time(NULL);

   active = true;

   while(active)
   {
      if (masterSIProcessor)
      {
         time_t now = time(NULL);
         struct tm tm_r;
         struct tm *ptm = localtime_r(&now, &tm_r);
         if (now - lastCleanup > 3600 && ptm->tm_hour == 5)
         {
            cMutexLock MutexLock(&schedulesMutex);
            isyslog("cleaning up schedules data");
            schedules->Cleanup();
            lastCleanup = now;
            ReportEpgBugFixStats(true);
         }
         if (epgDataFileName && now - lastDump > 600)
         {
            cMutexLock MutexLock(&schedulesMutex);
            cSafeFile f(GetEpgDataFileName());
            if (f.Open()) {
               schedules->Dump(f);
               f.Close();
               }
            else
               LOG_ERROR;
            lastDump = now;
         }
      }

      // set up pfd structures for all active filter
      pollfd pfd[MAX_FILTERS];
      int NumUsedFilters = 0;
      for (int a = 0; a < MAX_FILTERS ; a++)
      {
         if (filters[a].inuse)
         {
            pfd[NumUsedFilters].fd = filters[a].handle;
            pfd[NumUsedFilters].events = POLLIN;
            NumUsedFilters++;
         }
      }

      // wait until data becomes ready from the bitfilter
      if (poll(pfd, NumUsedFilters, 1000) != 0)
      {
         for (int a = 0; a < NumUsedFilters ; a++)
         {
            if (pfd[a].revents & POLLIN)
            {
               /* read section */
               unsigned char buf[4096+1]; // max. allowed size for any EIT section (+1 for safety ;-)
               if (safe_read(filters[a].handle, buf, 3) == 3)
               {
                  int seclen = ((buf[1] & 0x0F) << 8) | (buf[2] & 0xFF);
                  int pid = filters[a].pid;
                  int n = safe_read(filters[a].handle, buf + 3, seclen);
                  if (n == seclen)
                  {
                     seclen += 3;
                     //dsyslog("Received pid 0x%02x with table ID 0x%02x and length of %04d\n", pid, buf[0], seclen);
                     switch (pid)
                     {
                        case 0x14:
                           if (buf[0] == 0x70)
                           {
                              if (Setup.SetSystemTime && Setup.TimeTransponder && ISTRANSPONDER(currentTransponder, Setup.TimeTransponder))
                              {
                                 cTDT ctdt((tdt_t *)buf);
                                 ctdt.SetSystemTime();
                              }
                           }
                              /*XXX this comes pretty often:
                           else
                              dsyslog("Time packet was not 0x70 but 0x%02x\n", (int)buf[0]);
                              XXX*/
                           break;

                        case 0x12:
                           if (buf[0] != 0x72)
                           {
                              cMutexLock MutexLock(&schedulesMutex);
                              cEIT ceit(buf, seclen, schedules);
                              ceit.ProcessEIT(buf);
                           }
                           else
                              dsyslog("Received stuffing section in EIT\n");
                           break;

                        default:
                           break;
                     }
                  }
                  /*XXX this just fills up the log file - shouldn't we rather try to re-sync?
                  else
                     dsyslog("read incomplete section - seclen = %d, n = %d", seclen, n);
                  XXX*/
               }
            }
         }
      }
   }

   dsyslog("EIT processing thread ended (pid=%d)%s", getpid(), masterSIProcessor ? " - master" : "");
}

/** Add a filter with packet identifier pid and
table identifer tid */
bool cSIProcessor::AddFilter(u_char pid, u_char tid)
{
   dmx_sct_filter_params sctFilterParams;
   memset(&sctFilterParams, 0, sizeof(sctFilterParams));
   sctFilterParams.pid = pid;
   sctFilterParams.timeout = 0;
   sctFilterParams.flags = DMX_IMMEDIATE_START;
   sctFilterParams.filter.filter[0] = tid;
   sctFilterParams.filter.mask[0] = 0xFF;

   for (int a = 0; a < MAX_FILTERS; a++)
   {
      if (!filters[a].inuse)
      {
         filters[a].pid = pid;
         filters[a].tid = tid;
         if ((filters[a].handle = open(fileName, O_RDWR | O_NONBLOCK)) >= 0)
         {
            if (ioctl(filters[a].handle, DMX_SET_FILTER, &sctFilterParams) >= 0)
               filters[a].inuse = true;
            else
            {
               esyslog("ERROR: can't set filter");
               close(filters[a].handle);
               return false;
            }
            // dsyslog("Registered filter handle %04x, pid = %02d, tid = %02d", filters[a].handle, filters[a].pid, filters[a].tid);
         }
         else
         {
            esyslog("ERROR: can't open filter handle");
            return false;
         }
         return true;
      }
   }
   esyslog("ERROR: too many filters");

   return false;
}

/**  */
bool cSIProcessor::ShutDownFilters(void)
{
   for (int a = 0; a < MAX_FILTERS; a++)
   {
      if (filters[a].inuse)
      {
         close(filters[a].handle);
         // dsyslog("Deregistered filter handle %04x, pid = %02d, tid = %02d", filters[a].handle, filters[a].pid, filters[a].tid);
         filters[a].inuse = false;
      }
   }

   return true; // there's no real 'boolean' to return here...
}

/** */
void cSIProcessor::SetCurrentTransponder(int CurrentTransponder)
{
  currentTransponder = CurrentTransponder;
}

/** */
bool cSIProcessor::SetCurrentServiceID(unsigned short servid)
{
  cMutexLock MutexLock(&schedulesMutex);
  return schedules ? schedules->SetCurrentServiceID(servid) : false;
}

void cSIProcessor::TriggerDump(void)
{
  cMutexLock MutexLock(&schedulesMutex);
  lastDump = 0;
}