/***************************************************************************
                          eit.c  -  description
                             -------------------
    begin                : Fri Aug 25 2000
    copyright            : (C) 2000 by Robert Schneider
    email                : Robert.Schneider@web.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.16 2001/05/26 10:58:01 kls Exp $
 ***************************************************************************/

#include "eit.h"
#include <ctype.h>
#include <fcntl.h>
#include <fstream.h>
#include <iomanip.h>
#include <iostream.h>
#include <limits.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 "config.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(LOG_INFO, "Time parsed = %s\n", ctime(&mjdtime));
}

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

   ptm = localtime(&mjdtime);
   loctim = time(NULL);

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

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

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

typedef struct {
   u_char   table_id                   : 8;

#if BYTE_ORDER == BIG_ENDIAN
   u_char   section_syntax_indicator   : 1;
   u_char                              : 3;
   u_char   section_length_hi          : 4;
#else
   u_char   section_length_hi          : 4;
   u_char                              : 3;
   u_char   section_syntax_indicator   : 1;
#endif

   u_char   section_length_lo          : 8;


   u_char   utc_date_hi                : 8;
   u_char   utc_date_lo                : 8;
   u_char   utc_hour                   : 4;
   u_char   utc_hour_ten               : 4;
   u_char   utc_min                    : 4;
   u_char   utc_min_ten                : 4;
   u_char   utc_sec                    : 4;
   u_char   utc_sec_ten                : 4;
} tdt_t;

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!)
};

cTDT::cTDT(tdt_t *ptdt)
:tdt(*ptdt)
,mjd(tdt.utc_date_hi, tdt.utc_date_lo, tdt.utc_hour_ten * 10 + tdt.utc_hour,
                                       tdt.utc_min_ten  * 10 + tdt.utc_min,
                                       tdt.utc_sec_ten  * 10 + tdt.utc_sec)
{
}

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;
   uEventID = eventid;
   uServiceID = serviceid;
   cExtendedDescriptorNumber = 0;
   nChannelNumber = 0;
}

cEventInfo::~cEventInfo()
{
   delete pTitle;
   delete pSubtitle;
   delete 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::SetFollowing(bool foll)
{
   bIsFollowing = foll;
}
/**  */
const char * cEventInfo::GetDate() const
{
   static char szDate[25];

   strftime(szDate, sizeof(szDate), "%d.%m.%Y", localtime(&tTime));

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

   strftime(szTime, sizeof(szTime), "%R", localtime(&tTime));

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

   strftime(szEndTime, sizeof(szEndTime), "%R", localtime(&tEndTime));

   return szEndTime;
}
/**  */
time_t cEventInfo::GetTime() const
{
   return tTime;
}
/**  */
long cEventInfo::GetDuration() const
{
   return lDuration;
}
/**  */
unsigned short cEventInfo::GetEventID() const
{
   return uEventID;
}
/**  */
bool cEventInfo::SetTitle(char *string)
{
   if (string == NULL)
      return false;

   pTitle = strdup(string);
   if (pTitle == NULL)
      return false;

   return true;
}
/**  */
bool cEventInfo::SetSubtitle(char *string)
{
   if (string == NULL)
      return false;

   pSubtitle = strdup(string);
   if (pSubtitle == NULL)
      return false;

   return true;
}
/**  */
bool cEventInfo::AddExtendedDescription(char *string)
{
   int size = 0;
   bool first = true;
   char *p;

   if (string == NULL)
      return false;

   if (pExtendedDescription)
   {
      first = false;
      size += strlen(pExtendedDescription);
   }

   size += (strlen(string) + 1);

   p = (char *)realloc(pExtendedDescription, size);
   if (p == NULL)
      return false;

   if (first)
      *p = 0;

   strcat(p, string);

   pExtendedDescription = p;

   return true;
}
/**  */
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;
}
/**  */
u_char cEventInfo::GetExtendedDescriptorNumber() const
{
   return cExtendedDescriptorNumber;
}
/**  */
void cEventInfo::IncreaseExtendedDescriptorNumber()
{
   cExtendedDescriptorNumber++;
}

/**  */
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\n", Prefix, uEventID, tTime, lDuration);
      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);
      }
}

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

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


cSchedule::~cSchedule()
{
}
/**  */
const cEventInfo * cSchedule::GetPresentEvent() const
{
   // checking temporal sanity of present event (kls 2000-11-01)
   time_t now = time(NULL);
   if (pPresent && !(pPresent->GetTime() <= now && now <= pPresent->GetTime() + pPresent->GetDuration()))
   {
      cEventInfo *pe = Events.First();
      while (pe != NULL)
      {
         if (pe->GetTime() <= now && now <= pe->GetTime() + pe->GetDuration())
            return pe;
         pe = Events.Next(pe);
      }
   }
   return pPresent;
}
/**  */
const cEventInfo * cSchedule::GetFollowingEvent() const
{
   // checking temporal sanity of following event (kls 2000-11-01)
   time_t now = time(NULL);
   const cEventInfo *pr = GetPresentEvent(); // must have it verified!
   if (pFollowing && !(pr && pr->GetTime() + pr->GetDuration() <= pFollowing->GetTime()))
   {
      int minDt = INT_MAX;
      cEventInfo *pe = Events.First(), *pf = NULL;
      while (pe != NULL)
      {
         int dt = pe->GetTime() - now;
         if (dt > 0 && dt < minDt)
         {
            minDt = dt;
            pf = pe;
         }
         pe = Events.Next(pe);
      }
      return pf;
   }
   return pFollowing;
}
/**  */
void cSchedule::SetServiceID(unsigned short servid)
{
   uServiceID = servid;
}
/**  */
unsigned short cSchedule::GetServiceID() const
{
   return uServiceID;
}
/**  */
const cEventInfo * cSchedule::GetEvent(unsigned short uEventID) const
{
   cEventInfo *pe = Events.First();
   while (pe != NULL)
   {
      if (pe->GetEventID() == uEventID)
         return pe;

      pe = Events.Next(pe);
   }

   return NULL;
}
/**  */
const cEventInfo * cSchedule::GetEvent(time_t tTime) const
{
   cEventInfo *pe = Events.First();
   while (pe != NULL)
   {
      if (pe->GetTime() == tTime)
         return pe;

      pe = Events.Next(pe);
   }

   return NULL;
}
/**  */
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() < tTime)
      {
         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);
   }
}

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

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

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

   uCurrentServiceID = servid;

   return true;
}
/**  */
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);
}

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

#define DEC(N) dec << setw(N) << setfill(int('0'))
#define HEX(N) hex << setw(N) << setfill(int('0'))

#define EIT_STUFFING_DESCRIPTOR                 0x42
#define EIT_LINKAGE_DESCRIPTOR                  0x4a
#define EIT_SHORT_EVENT_DESCRIPTOR              0x4d
#define EIT_EXTENDED_EVENT_DESCRIPTOR           0x4e
#define EIT_TIME_SHIFTED_EVENT_DESCRIPTOR       0x4f
#define EIT_COMPONENT_DESCRIPTOR                0x50
#define EIT_CA_IDENTIFIER_DESCRIPTOR            0x53
#define EIT_CONTENT_DESCRIPTOR                  0x54
#define EIT_PARENTAL_RATING_DESCRIPTOR          0x55
#define EIT_TELEPHONE_DESCRIPTOR                0x57
#define EIT_MULTILINGUAL_COMPONENT_DESCRIPTOR   0x5e
#define EIT_PRIVATE_DATE_SPECIFIER_DESCRIPTOR   0x5f
#define EIT_SHORT_SMOOTHING_BUFFER_DESCRIPTOR   0x61
#define EIT_DATA_BROADCAST_DESCRIPTOR           0x64
#define EIT_PDC_DESCRIPTOR                      0x69

typedef struct eit_struct {
   u_char   table_id                   : 8;

#if BYTE_ORDER == BIG_ENDIAN
   u_char   section_syntax_indicator   : 1;
   u_char                              : 3;
   u_char   section_length_hi          : 4;
#else
   u_char   section_length_hi          : 4;
   u_char                              : 3;
   u_char   section_syntax_indicator   : 1;
#endif

   u_char   section_length_lo          : 8;

   u_char   service_id_hi              : 8;
   u_char   service_id_lo              : 8;

#if BYTE_ORDER == BIG_ENDIAN
   u_char                              : 2;
   u_char   version_number             : 5;
   u_char   current_next_indicator     : 1;
#else
   u_char   current_next_indicator     : 1;
   u_char   version_number             : 5;
   u_char                              : 2;
#endif

   u_char   section_number             : 8;
   u_char   last_section_number        : 8;
   u_char   transport_stream_id_hi     : 8;
   u_char   transport_stream_id_lo     : 8;
   u_char   original_network_id_hi     : 8;
   u_char   original_network_id_lo     : 8;
   u_char   segment_last_section_number   : 8;
   u_char   segment_last_table_id      : 8;
} eit_t;

typedef struct eit_loop_struct {
   u_char   event_id_hi                : 8;
   u_char   event_id_lo                : 8;

   u_char   date_hi                    : 8;
   u_char   date_lo                    : 8;
   u_char   time_hour                  : 4;
   u_char   time_hour_ten              : 4;
   u_char   time_minute                : 4;
   u_char   time_minute_ten            : 4;
   u_char   time_second                : 4;
   u_char   time_second_ten            : 4;

   u_char   dur_hour                   : 4;
   u_char   dur_hour_ten               : 4;
   u_char   dur_minute                 : 4;
   u_char   dur_minute_ten             : 4;
   u_char   dur_second                 : 4;
   u_char   dur_second_ten             : 4;

#if BYTE_ORDER == BIG_ENDIAN
   u_char   running_status             : 3;
   u_char   free_ca_mode               : 1;
   u_char   descriptors_loop_length_hi : 4;
#else
   u_char   descriptors_loop_length_hi : 4;
   u_char   free_ca_mode               : 1;
   u_char   running_status             : 3;
#endif

   u_char   descriptors_loop_length_lo : 8;
} eit_loop_t;

typedef struct eit_short_event_struct {
   u_char   descriptor_tag             : 8;
   u_char   descriptor_length          : 8;

   u_char   language_code_1            : 8;
   u_char   language_code_2            : 8;
   u_char   language_code_3            : 8;

   u_char   event_name_length          : 8;
} eit_short_event_t;

typedef struct eit_extended_event_struct {
   u_char   descriptor_tag             : 8;
   u_char   descriptor_length          : 8;

   u_char   last_descriptor_number     : 4;
   u_char   descriptor_number          : 4;

   u_char   language_code_1            : 8;
   u_char   language_code_2            : 8;
   u_char   language_code_3            : 8;

   u_char   length_of_items            : 8;
} eit_extended_event_t;

typedef struct eit_content_descriptor {
   u_char   descriptor_tag             : 8;
   u_char   descriptor_length          : 8;
} eit_content_descriptor_t;

typedef struct eit_content_loop {
   u_char   content_nibble_level_2     : 4;
   u_char   content_nibble_level_1     : 4;
   u_char   user_nibble_2              : 4;
   u_char   user_nibble_1              : 4;
} eit_content_loop_t;

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

protected: // Protected methods
  /**  */
  int strdvbcpy(unsigned char *dst, unsigned char *src, int max);
  /** returns true if this EIT covers a
present/following information, false if it's
schedule information */
  bool IsPresentFollowing();
  /**  */
  bool WriteShortEventDescriptor(unsigned short service, eit_loop_t *eitloop, u_char *buf);
  /**  */
  bool WriteExtEventDescriptor(unsigned short service, eit_loop_t *eitloop, u_char *buf);
protected: // Protected attributes
  int buflen;
protected: // Protected attributes
  /**  */
  u_char buffer[4097];
  /** Table ID of this EIT struct */
  u_char tid;
  /** EITs service id (program number) */
  u_short pid;
};

cEIT::cEIT(void * buf, int length, cSchedules *Schedules)
{
   buflen = length < int(sizeof(buffer)) ? length : sizeof(buffer);
   memset(buffer, 0, sizeof(buffer));
   memcpy(buffer, buf, buflen);
   tid = buffer[0];
   schedules = Schedules;
}

cEIT::~cEIT()
{
}

/**  */
int cEIT::ProcessEIT()
{
   int bufact = 0;
   eit_t *eit;
   eit_loop_t *eitloop;
   u_char tmp[256];

   if (bufact + (int)sizeof(eit_t) > buflen)
      return 0;
   eit = (eit_t *)buffer;
   bufact += sizeof(eit_t);

   unsigned int service = (eit->service_id_hi << 8) | eit->service_id_lo;

   while(bufact + (int)sizeof(eit_loop_t) <= buflen)
   {
      eitloop = (eit_loop_t *)&buffer[bufact];
      bufact += sizeof(eit_loop_t);

      int descdatalen = (eitloop->descriptors_loop_length_hi << 8) + eitloop->descriptors_loop_length_lo;
      int descdataact = 0;

      while (descdataact < descdatalen && bufact < buflen)
      {
         switch (buffer[bufact])
         {
            eit_content_descriptor_t *cont;
            eit_content_loop_t *contloop;

            case EIT_STUFFING_DESCRIPTOR  :
               //dsyslog(LOG_INFO, "Found EIT_STUFFING_DESCRIPTOR");
               break;

            case EIT_LINKAGE_DESCRIPTOR   :
               //dsyslog(LOG_INFO, "Found EIT_LINKAGE_DESCRIPTOR");
               break;

            case EIT_SHORT_EVENT_DESCRIPTOR:
               WriteShortEventDescriptor(service, eitloop, &buffer[bufact]);
               break;

            case EIT_EXTENDED_EVENT_DESCRIPTOR:
               WriteExtEventDescriptor(service, eitloop, &buffer[bufact]);
               break;

            case EIT_TIME_SHIFTED_EVENT_DESCRIPTOR :
               //dsyslog(LOG_INFO, "Found EIT_TIME_SHIFTED_EVENT_DESCRIPTOR");
               break;

            case EIT_COMPONENT_DESCRIPTOR :
               if (buffer[bufact + 1] > 6) // kls 2001-02-24: otherwise strncpy() causes a segfault in strdvbcpy()
                  strdvbcpy(tmp, &buffer[bufact + 8], buffer[bufact + 1] - 6);
               //dsyslog(LOG_INFO, "Found EIT_COMPONENT_DESCRIPTOR %c%c%c 0x%02x/0x%02x/0x%02x '%s'\n", buffer[bufact + 5], buffer[bufact + 6], buffer[bufact + 7], buffer[2], buffer[3], buffer[4], tmp);
               break;

            case EIT_CA_IDENTIFIER_DESCRIPTOR   :
               //dsyslog(LOG_INFO, "Found EIT_CA_IDENTIFIER_DESCRIPTOR");
               break;

            case EIT_CONTENT_DESCRIPTOR   :
               cont = (eit_content_descriptor_t *)buffer;
               contloop = (eit_content_loop_t *)&buffer[sizeof(eit_content_descriptor_t)];
               //dsyslog(LOG_INFO, "Found EIT_CONTENT_DESCRIPTOR 0x%02x/0x%02x\n", contloop->content_nibble_level_1, contloop->content_nibble_level_2);
               break;

            case EIT_PARENTAL_RATING_DESCRIPTOR :
               //dsyslog(LOG_INFO, "Found EIT_PARENTAL_RATING_DESCRIPTOR");
               break;

            case EIT_TELEPHONE_DESCRIPTOR :
               //dsyslog(LOG_INFO, "Found EIT_TELEPHONE_DESCRIPTOR");
               break;

            case EIT_MULTILINGUAL_COMPONENT_DESCRIPTOR   :
               //dsyslog(LOG_INFO, "Found EIT_MULTILINGUAL_COMPONENT_DESCRIPTOR");
               break;

            case EIT_PRIVATE_DATE_SPECIFIER_DESCRIPTOR   :
               //dsyslog(LOG_INFO, "Found EIT_PRIVATE_DATE_SPECIFIER_DESCRIPTOR");
               break;

            case EIT_SHORT_SMOOTHING_BUFFER_DESCRIPTOR   :
               //dsyslog(LOG_INFO, "Found EIT_SHORT_SMOOTHING_BUFFER_DESCRIPTOR");
               break;

            case EIT_DATA_BROADCAST_DESCRIPTOR  :
               //dsyslog(LOG_INFO, "Found EIT_DATA_BROADCAST_DESCRIPTOR");
               break;

            case EIT_PDC_DESCRIPTOR :
               //dsyslog(LOG_INFO, "Found EIT_PDC_DESCRIPTOR");
               break;

            default:
               //dsyslog(LOG_INFO, "Found unhandled descriptor 0x%02x with length of %04d\n", (int)buffer[bufact], (int)buffer[bufact + 1]);
               break;
         }
         descdataact += (buffer[bufact + 1] + 2);
         bufact += (buffer[bufact + 1] + 2);
      }
   }

   return 0;
}

/**  */
int cEIT::strdvbcpy(unsigned char *dst, unsigned char *src, int max)
{
   int a = 0;

   // kls 2001-02-24: if we come in with negative values, the caller must
   // have done something wrong and the strncpy() below will cause a segfault
   if (max <= 0)
   {
      *dst = 0;
      return 0;
   }

   if (*src == 0x05 || (*src >= 0x20 && *src <= 0xff))
   {
      for (a = 0; a < max; a++)
      {
         if (*src == 0)
            break;

         if ((*src >= ' ' && *src <= '~') || (*src >= 0xa0 && *src <= 0xff))
            *dst++ = *src++;
         else
         {
            // if ((*src > '~' && *src < 0xa0) || *src == 0xff)
            // cerr << "found special character 0x" << HEX(2) << (int)*src << endl;
            src++;
         }
      }
      *dst = 0;
   }
   else
   {
      const char *ret;

      switch (*src)
      {
         case 0x01: ret = "Coding according to character table 1"; break;
         case 0x02: ret = "Coding according to character table 2"; break;
         case 0x03: ret = "Coding according to character table 3"; break;
         case 0x04: ret = "Coding according to character table 4"; break;
         case 0x10: ret = "Coding according to ISO/IEC 8859"; break;
         case 0x11: ret = "Coding according to ISO/IEC 10646"; break;
         case 0x12: ret = "Coding according to KSC 5601"; break;
         default: ret = "Unknown coding"; break;
      }
      strncpy((char *)dst, ret, max);
   }
   return a;
}

/** 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;
}

/**  */
bool cEIT::WriteShortEventDescriptor(unsigned short service, eit_loop_t *eitloop, u_char *buf)
{
   u_char tmp[256];
   eit_short_event_t *evt = (eit_short_event_t *)buf;
   unsigned short eventid = (unsigned short)((eitloop->event_id_hi << 8) | eitloop->event_id_lo);
   cEventInfo *pEvent;

   //isyslog(LOG_INFO, "Found Short Event Descriptor");

   cSchedule *pSchedule = (cSchedule *)schedules->GetSchedule(service);
   if (pSchedule == NULL)
   {
      schedules->Add(new cSchedule(service));
      pSchedule = (cSchedule *)schedules->GetSchedule(service);
      if (pSchedule == NULL)
         return false;
   }

   /* cSchedule::GetPresentEvent() and cSchedule::GetFollowingEvent() verify
      the temporal sanity of these events, so calling them here appears to
      be a bad idea... (kls 2000-11-01)
   //
   // if we are working on a present/following info, let's see whether
   // we already have present/following info for this service and if yes
   // check whether it's the same eventid, if yes, just return, nothing
   // left to do.
   //
   if (IsPresentFollowing())
   {
      if (eitloop->running_status == 4 || eitloop->running_status == 3)
         pEvent = (cEventInfo *)pSchedule->GetPresentEvent();
      else
         pEvent = (cEventInfo *)pSchedule->GetFollowingEvent();

      if (pEvent != NULL)
         if (pEvent->GetEventID() == eventid)
            return true;
   }
   */

   //
   // let's see whether we have that eventid already
   // in case not, we have to create a new cEventInfo for it
   //
   pEvent = (cEventInfo *)pSchedule->GetEvent(eventid);
   if (pEvent == NULL)
   {
      pSchedule->Events.Add(new cEventInfo(service, eventid));
      pEvent = (cEventInfo *)pSchedule->GetEvent(eventid);
      if (pEvent == NULL)
         return false;

      strdvbcpy(tmp, &buf[sizeof(eit_short_event_t)], evt->event_name_length);
      pEvent->SetTitle((char *)tmp);
      strdvbcpy(tmp, &buf[sizeof(eit_short_event_t) + evt->event_name_length + 1],
                (int)buf[sizeof(eit_short_event_t) + evt->event_name_length]);
      pEvent->SetSubtitle((char *)tmp);
      cMJD mjd(eitloop->date_hi, eitloop->date_lo,
               eitloop->time_hour_ten * 10 + eitloop->time_hour,
               eitloop->time_minute_ten * 10 + eitloop->time_minute,
               eitloop->time_second_ten * 10 + eitloop->time_second);
      pEvent->SetTime(mjd.GetTime_t());
      pEvent->SetDuration((long)((long)((eitloop->dur_hour_ten * 10 + eitloop->dur_hour) * 60l * 60l) +
                                 (long)((eitloop->dur_minute_ten * 10 + eitloop->dur_minute) * 60l) +
                                 (long)(eitloop->dur_second_ten * 10 + eitloop->dur_second)));
   }

   if (IsPresentFollowing())
   {
      if (eitloop->running_status == 4 || eitloop->running_status == 3)
         pSchedule->SetPresentEvent(pEvent);
      else if (eitloop->running_status == 1 || eitloop->running_status == 2 || eitloop->running_status == 0)
         pSchedule->SetFollowingEvent(pEvent);
   }

   return true;
}

/**  */
bool cEIT::WriteExtEventDescriptor(unsigned short service, eit_loop_t *eitloop, u_char *buf)
{
   u_char tmp[256];
   eit_extended_event_t *evt = (eit_extended_event_t *)buf;
   int bufact, buflen;
   unsigned short eventid = (unsigned short)((eitloop->event_id_hi << 8) | eitloop->event_id_lo);
   cEventInfo *pEvent;

   //isyslog(LOG_INFO, "Found Extended Event Descriptor");

   cSchedule *pSchedule = (cSchedule *)schedules->GetSchedule(service);
   if (pSchedule == NULL)
   {
      schedules->Add(new cSchedule(service));
      pSchedule = (cSchedule *)schedules->GetSchedule(service);
      if (pSchedule == NULL)
         return false;
   }

   pEvent = (cEventInfo *)pSchedule->GetEvent(eventid);
   if (pEvent == NULL)
      return false;

   if (evt->descriptor_number != pEvent->GetExtendedDescriptorNumber())
      return false;

   bufact = sizeof(eit_extended_event_t);
   buflen = buf[1] + 2;

   if (evt->length_of_items > 0)
   {
      while (bufact - sizeof(eit_extended_event_t) < evt->length_of_items)
      {
         strdvbcpy(tmp, &buf[bufact + 1], (int)buf[bufact]);
         // could use value in tmp now to do something,
         // haven't seen any items as of yet transmitted from satellite
         bufact += (buf[bufact] + 1);
      }
   }

   strdvbcpy(tmp, &buf[bufact + 1], (int)buf[bufact]);
   if (pEvent->AddExtendedDescription((char *)tmp))
   {
      pEvent->IncreaseExtendedDescriptorNumber();
      return true;
   }

   return false;
}

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

#define MAX_FILTERS 20

int cSIProcessor::numSIProcessors = 0;
cSchedules *cSIProcessor::schedules = NULL;
cMutex cSIProcessor::schedulesMutex;

/**  */
cSIProcessor::cSIProcessor(const char *FileName)
{
   fileName = strdup(FileName);
   masterSIProcessor = numSIProcessors == 0; // the first one becomes the 'master'
   useTStime = false;
   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()
{
   active = false;
   Cancel(3);
   ShutDownFilters();
   delete filters;
   if (!--numSIProcessors) // the last one deletes it
      delete schedules;
   delete fileName;
}

void cSIProcessor::SetStatus(bool On)
{
   LOCK_THREAD;
   schedulesMutex.Lock();
   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
   }
   schedulesMutex.Unlock();
}

/** 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(LOG_INFO, "EIT processing thread started (pid=%d)%s", getpid(), masterSIProcessor ? " - master" : "");

   time_t lastCleanup = time(NULL);
   time_t lastDump = time(NULL);

   active = true;

   while(active)
   {
      if (masterSIProcessor)
      {
         time_t now = time(NULL);
         struct tm *ptm = localtime(&now);
         if (now - lastCleanup > 3600 && ptm->tm_hour == 5)
         {
            LOCK_THREAD;

            schedulesMutex.Lock();
            isyslog(LOG_INFO, "cleaning up schedules data");
            schedules->Cleanup();
            schedulesMutex.Unlock();
            lastCleanup = now;
         }
         if (now - lastDump > 600)
         {
            LOCK_THREAD;

            schedulesMutex.Lock();
            FILE *f = fopen(AddDirectory(VideoDirectory, "epg.data"), "w");
            if (f) {
               schedules->Dump(f);
               fclose(f);
               }
            lastDump = now;
            schedulesMutex.Unlock();
         }
      }

      // 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 (read(filters[a].handle, buf, 3) == 3)
               {
                  int seclen = ((buf[1] & 0x0F) << 8) | (buf[2] & 0xFF);
                  int pid = filters[a].pid;
                  int n = read(filters[a].handle, buf + 3, seclen);
                  if (n == seclen)
                  {
                     seclen += 3;
                     //dsyslog(LOG_INFO, "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 (useTStime)
                              {
                                 cTDT ctdt((tdt_t *)buf);
                                 ctdt.SetSystemTime();
                              }
                           }
                              /*XXX this comes pretty often:
                           else
                              dsyslog(LOG_INFO, "Time packet was not 0x70 but 0x%02x\n", (int)buf[0]);
                              XXX*/
                           break;

                        case 0x12:
                           if (buf[0] != 0x72)
                           {
                              LOCK_THREAD;

                              schedulesMutex.Lock();
                              cEIT ceit(buf, seclen, schedules);
                              ceit.ProcessEIT();
                              schedulesMutex.Unlock();
                           }
                           else
                              dsyslog(LOG_INFO, "Received stuffing section in EIT\n");
                           break;

                        default:
                           break;
                     }
                  }
                  else
                     dsyslog(LOG_INFO, "read incomplete section - seclen = %d, n = %d", seclen, n);
               }
            }
         }
      }
   }

   dsyslog(LOG_INFO, "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)
{
   dmxSctFilterParams sctFilterParams;
   sctFilterParams.pid = pid;
   memset(&sctFilterParams.filter.filter, 0, DMX_FILTER_SIZE);
   memset(&sctFilterParams.filter.mask, 0, DMX_FILTER_SIZE);
   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(LOG_ERR, "ERROR: can't set filter");
               close(filters[a].handle);
               return false;
            }
            // dsyslog(LOG_INFO, "  Registered filter handle %04x, pid = %02d, tid = %02d", filters[a].handle, filters[a].pid, filters[a].tid);
         }
         else
         {
            esyslog(LOG_ERR, "ERROR: can't open filter handle");
            return false;
         }
         return true;
      }
   }
   esyslog(LOG_ERR, "ERROR: too many filters");

   return false;
}

/** set whether local systems time should be
set by the received TDT or TOT packets */
bool cSIProcessor::SetUseTSTime(bool use)
{
   useTStime = use;
   return useTStime;
}

/**  */
bool cSIProcessor::ShutDownFilters(void)
{
   for (int a = 0; a < MAX_FILTERS; a++)
   {
      if (filters[a].inuse)
      {
         close(filters[a].handle);
         // dsyslog(LOG_INFO, "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...
}

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