/*******************************************************************************
 * psi.c: PSI management 
 * (c)1999 VideoLAN
 *******************************************************************************
 * Manages structures containing PSI information, and affiliated decoders.
 * TO DO: Fonctions d'init des structures
 *******************************************************************************/

/*******************************************************************************
 * Preamble
 *******************************************************************************/
#include <errno.h>
#include <pthread.h>
#include <stdio.h>
#include <sys/uio.h>                                                 /* iovec */
#include <stdlib.h>                               /* atoi(), malloc(), free() */
#include <string.h>
#include <netinet/in.h>                                              /* ntohs */

#include "common.h"
#include "config.h"
#include "mtime.h"
#include "intf_msg.h"
#include "debug.h"

#include "input.h"
#include "input_ctrl.h"
#include "input_psi.h"

/*
 * Precalculated 32-bits CRC table, shared by all instances of the PSI decoder
 */
boolean_t b_crc_initialised = 0;
u32 i_crc_32_table[256];

/*
 * Locale type definitions
 */
#define PSI_VIDEO_STREAM_DESCRIPTOR                 0x02
#define PSI_AUDIO_STREAM_DESCRIPTOR                 0x03
#define PSI_TARGET_BACKGROUND_GRID_DESCRIPTOR       0x07
#define PSI_VIDEO_WINDOW_DESCRIPTOR                 0x08

#ifdef DVB_EXTENSIONS
#define PSI_SERVICE_DESCRIPTOR                      0x48
#endif

/* That info must be stored in the version field since it contains
   unused bits */
#define PSI_UNINITIALISED                           0xFF

/*
 * Local prototypes
 */
static int input_AddPsiPID( input_thread_t *p_input, int i_pid );
static int input_DelPsiPID( input_thread_t *p_input, int i_pid );
static void DecodePgrmAssocSection( byte_t* p_pas, input_thread_t *p_input );
static void DecodePgrmMapSection( byte_t* p_pms, input_thread_t *p_input );
static void DecodeSrvDescrSection( byte_t* p_sdt, input_thread_t *p_input );
static void DecodePgrmDescriptor( byte_t* p_descriptor, pgrm_descriptor_t* p_pgrm );
static void DecodeESDescriptor( byte_t* p_descriptor, es_descriptor_t* p_es );

static stream_descriptor_t* AddStreamDescr(input_thread_t* p_input,
                                           u16 i_stream_id);
static void DestroyStreamDescr(input_thread_t* p_input, u16 i_stream_id);
static pgrm_descriptor_t* AddPgrmDescr(stream_descriptor_t* p_stream,
                                       u16 i_pgrm_id);
static void DestroyPgrmDescr(input_thread_t* p_input, stream_descriptor_t* p_stream, u16 i_pgrm_id);
static es_descriptor_t* AddESDescr(input_thread_t* p_input,
                                   pgrm_descriptor_t* p_pgrm, u16 i_es_pid);
static void DestroyESDescr(input_thread_t* p_input, pgrm_descriptor_t* p_pgrm,
                           u16 i_es_pid);

static void BuildCrc32Table();
static int CheckCRC32( u8* p_pms, int i_size);
static boolean_t Is_known( byte_t* a_known_section, u8 i_section );
static void Set_known( byte_t* a_known_section, u8 i_section );
static void Unset_known( byte_t* a_known_section, u8 i_section );


/******************************************************************************
 * input_PsiInit: Initialize PSI decoder
 ******************************************************************************
 * Init the structures in which the PSI decoder will put the informations it
 * got from PSI tables and request for the reception of the PAT.
 ******************************************************************************/
int input_PsiInit( input_thread_t *p_input )
{
  ASSERT(p_input);
  
  /* Precalculate the 32-bit CRC table if not already done ???
     TO DO -> Put a lock or do that at pgrm init */
  if( !b_crc_initialised )
  {
    BuildCrc32Table();
    b_crc_initialised = 1;
  }
  
  /* Init the structure that describes the stream we are receiving */
  AddStreamDescr( p_input, PSI_UNINITIALISED );

  /* Request for reception of the program association table */
  input_AddPsiPID( p_input, 0 );

#ifdef DVB_EXTENSIONS
  /* Request for reception of the service description table */
  input_AddPsiPID( p_input, 17 );
#endif

  return( 0 );
}


/******************************************************************************
 * input_PsiClean: Clean PSI structures before dying
 ******************************************************************************/
int input_PsiClean( input_thread_t *p_input )
{
  ASSERT(p_input);

  /* Stop to receive all the PSI tables associated with that program */
  /* TO DO ??? -> Not really usefull */

  /* Clean also descriptors for programs associated with that stream */
  /* TO DO ??? -> Not really usefull and maybe buggy */

  /* Destroy the stream description */
  DestroyStreamDescr( p_input, p_input->p_stream->i_stream_id );

  return( 0 );
}



/******************************************************************************
 * input_PsiRead: Read the table of programs
 ******************************************************************************
 * Ugly debugging function at that time ???????
 ******************************************************************************/
void input_PsiRead( input_thread_t *p_input /* ??? */ )
{
  int i_index;
  int i_index2;
  pgrm_descriptor_t* p_pgrm;
  
  ASSERT( p_input );
 
  /* Lock the tables, since this method can be called from any thread */
  //pthread_mutex_lock()

  /* Check if the table is complete or not */
  if( !p_input->p_stream->b_is_PMT_complete )
  {
    intf_IntfMsg( "Warning: PMT not yet complete\n" );
  }
  
  /* Read the table */
  for( i_index = 0; i_index < p_input->p_stream->i_pgrm_number; i_index++ )
  {
    p_pgrm = p_input->p_stream->ap_programs[i_index];
    intf_Msg("Printing info for program %d\n", p_pgrm->i_number );
    intf_IntfMsg("Printing info for program %d\n", p_pgrm->i_number );

    for( i_index2 = 0; i_index2 < p_pgrm->i_es_number; i_index2++ )
    {
      intf_Msg( " ->Pid %d: type %d, PCR: %d, PSI: %d\n", p_pgrm->ap_es[i_index2]->i_id,
                    p_pgrm->ap_es[i_index2]->i_type, p_pgrm->ap_es[i_index2]->b_pcr,
                    p_pgrm->ap_es[i_index2]->b_psi);

      intf_IntfMsg( " ->Pid %d: type %d, PCR: %d, PSI: %d\n", p_pgrm->ap_es[i_index2]->i_id,
                    p_pgrm->ap_es[i_index2]->i_type, p_pgrm->ap_es[i_index2]->b_pcr,
                    p_pgrm->ap_es[i_index2]->b_psi);
    }
  }

  /* Unock the tables */
  //pthread_mutex_unlock()
}


/******************************************************************************
 * input_PsiDecode: Decode a PSI section
 ******************************************************************************
 * This funtion is essentially a wrapper that will  perform basic checks on
 * the section and then call the right function according to its type.
 ******************************************************************************/
void input_PsiDecode( input_thread_t *p_input, psi_section_t* p_psi_section )
{
  ASSERT(p_input);
  ASSERT(p_psi_section);

  /* Hexa dump of the beginning of the section (for real men) */
  //intf_DbgMsg( "Section: %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x\n", (u8)p_psi_section->buffer[0], (u8)p_psi_section->buffer[1], (u8)p_psi_section->buffer[2], (u8)p_psi_section->buffer[3], (u8)p_psi_section->buffer[4], (u8)p_psi_section->buffer[5], (u8)p_psi_section->buffer[6], (u8)p_psi_section->buffer[7], (u8)p_psi_section->buffer[8], (u8)p_psi_section->buffer[9], (u8)p_psi_section->buffer[10], (u8)p_psi_section->buffer[11], (u8)p_psi_section->buffer[12], (u8)p_psi_section->buffer[13], (u8)p_psi_section->buffer[14], (u8)p_psi_section->buffer[15], (u8)p_psi_section->buffer[16], (u8)p_psi_section->buffer[17], (u8)p_psi_section->buffer[18], (u8)p_psi_section->buffer[19] );

  /* Check the CRC validity if any CRC is carried */
  if( p_psi_section->buffer[1] & 0x80 )
  {
    if( CheckCRC32 (p_psi_section->buffer, p_psi_section->i_length) )
    {
      intf_DbgMsg("iSize: %d, CRC: %d\n", p_psi_section->i_length,
                  U32_AT(&p_psi_section->buffer[p_psi_section->i_length-4]));
      intf_DbgMsg( "Invalid CRC for PSI\n" );
      return;
    }
  }
  
  /* If the section is not immediatly applicable, trash it (DVB drafts disallow
     transmission of such sections, so we didn't implement it) */
  if( !p_psi_section->buffer[5] & 0x01 )
  {
    intf_DbgMsg( "PSI not yet applicable: trash it\n" );
    return;
  }
  
  /* Handle the packet according to it's type given it the table_id  */
  switch ( p_psi_section->buffer[0] )
  {
    case 0x00:
      //intf_DbgMsg("Program association section received\n");
      DecodePgrmAssocSection(p_psi_section->buffer, p_input);
      break;
    case 0x01:
      //intf_DbgMsg("Conditional access section received\n");
      break;
    case 0x02:
      //intf_DbgMsg("Program map section received\n");
      DecodePgrmMapSection(p_psi_section->buffer, p_input);
      break;
    case 0x42:
      //intf_DbgMsg("Service description section received\n");
      DecodeSrvDescrSection(p_psi_section->buffer, p_input);
      break;
    default:
      //intf_DbgMsg("Private PSI data received (type %x), ignoring it\n",
      //            p_psi_section->buffer[0]);
  }
}


/******************************************************************************
 * DecodeAssocSection: Decode a PAS
 ******************************************************************************
 * No check is made to known if the table is currently applicable or not, so
 * that unapplicable sections must be filtered before calling this function
 * The Program Association Table can be segmented to occupy multiple sections
 * so that we have to know which sections we have already received (IsKnown() /
 * SetKnown() calls)
 ******************************************************************************/
static void DecodePgrmAssocSection(u8* p_pas, input_thread_t *p_input )
{
  u8 i_stream_id;               /* Id of the stream described in that section */
    u8 i_version;              /* Version of the table carried in the section */

    u16 i_pgrm_id;                       /* Id of the current described  pgrm */
    u16 i_pgrm_map_pid;            /* PID of the associated program map table */
    int i_pgrm_number;         /* Number of programs described in the section */

    boolean_t b_is_invalid = 0;
    
    u8 i_current_section;
    u8 i_last_section;

    int i_pgrm_index;
    int i_es_index;

    ASSERT(p_pas);
    ASSERT(p_input);

#define p_descr (p_input->p_stream)

   /* Read stream id and version number immediately, to be sure they will be
      initialised in all cases we will need it */
   i_stream_id = U16_AT(&p_pas[3]);
   i_version = (p_pas[5] >> 1) & 0x1F;
   //intf_DbgMsg("TS Id: %d, version: %d\n", U16_AT(&p_pas[3]),(p_pas[5] >> 1) & 0x1F);

    /* Test if the stream has not changed by looking at the stream_id */
    if( p_descr->i_stream_id != i_stream_id )
    {
      /* This can either mean that the PSI decoder has just started or that
         the stream has changed */
      if( p_descr->i_PAT_version== PSI_UNINITIALISED )
        intf_Msg("Building Program Association table\n");
      else
        intf_ErrMsg( "Stream Id has suddently changed ! Rebuilding PAT\n" );

      /* Whatever it is, ask the PSI decoder to rebuild the table */
      b_is_invalid = 1;
    }
    else
    {
      /* Stream has not changed, test if the PMT is up to date */
      if( p_descr->i_PAT_version != i_version )
      {
        intf_Msg("PAT has been updated, rebuilding it\n");
        /* Ask the PSI decoder to rebuild the table */
        b_is_invalid = 1;
      }
    }
    
    /* Clear the table if needed */
    if( b_is_invalid )
    {
        intf_DbgMsg("Updating PAT table\n");

        /* Any program in the stream may have disapeared, or a new one
           can have been added. The good way to handle such a case would be
           to build a temporary table and to make a diff */

        /* Stop the reception of all programs and PSI informations
           associated with this stream, excepted the PAT on PID 0 and the SDT
           on PID 17 */
        for( i_es_index = 0; i_es_index < INPUT_MAX_SELECTED_ES && 
             p_input->pp_selected_es[i_es_index]; i_es_index++ )
        {
          if( p_input->pp_selected_es[i_es_index]->b_psi )
          {
            if( p_input->pp_selected_es[i_es_index]->i_id != 0
#ifdef DVB_EXTENSIONS
                && p_input->pp_selected_es[i_es_index]->i_id != 17
#endif
            )
              input_DelPsiPID( p_input, p_input->pp_selected_es[i_es_index]->i_id );
          }
          else
            input_DelPgrmElem( p_input, p_input->pp_selected_es[i_es_index]->i_id );
        }

        /* Recreate a new stream description. Since it is virgin, the decoder
           will on is own rebuild it entirely */
        DestroyStreamDescr(p_input, p_descr->i_stream_id);
        AddStreamDescr(p_input, i_stream_id);

        /* Record the new version for that table */
        p_descr->i_PAT_version = i_version;
    }

    /* Build the table if not already complete or if it was cleared */
    if( p_descr->b_is_PAT_complete )
    {
      /* Nothing to do */
      //intf_DbgMsg("Table already complete\n");
    }
    else
    {
        /* Check if we already heard about that section */
        i_last_section = p_pas[7];
        i_current_section = p_pas[6];

//        intf_DbgMsg( "Section %d (last section %d)\n",
//                     i_current_section, i_last_section );

        if( Is_known(p_descr->a_known_PAT_sections, i_current_section) )
        {
          /* Nothing to do */
//          intf_DbgMsg("Section already received, skipping\n");
        }
        else
        {
          /* Compute the number of program_map PID carried in this section */
          i_pgrm_number = ((U16_AT(&p_pas[1]) & 0xFFF) - 9) / 4;
          intf_DbgMsg("Number of Pgrm in that section: %d\n", i_pgrm_number);

          /* Start the reception of those program map PID */
          for( i_pgrm_index = 0; i_pgrm_index < i_pgrm_number; i_pgrm_index++ )
          {
            i_pgrm_id = U16_AT(&p_pas[8+4*i_pgrm_index]);
            i_pgrm_map_pid = U16_AT(&p_pas[8+4*i_pgrm_index+2]) & 0x1FFF;
            intf_DbgMsg("Pgrm %d described on pid %d\n", i_pgrm_id, i_pgrm_map_pid);

            /* Check we are not already receiving that pid because it carries info
               for another program */
            for( i_es_index = 0; i_es_index < INPUT_MAX_ES; i_es_index++ )
            {
              if( p_input->p_es[i_es_index].i_id == i_pgrm_map_pid)
              {
                intf_DbgMsg("Already receiving pid %d", i_pgrm_map_pid);
                i_es_index = INPUT_MAX_ES+1;
                break;
              } 
            }
            /* Start to receive that PID if we're not already doing it */
            if( i_es_index <= INPUT_MAX_ES )
              input_AddPsiPID( p_input, i_pgrm_map_pid );

            /* Append an entry to the pgrm_descriptor table to later store
               the description of this program, unless program number is 0
               (Network information table) */
            if( i_pgrm_id != 0 )
            {
              intf_Msg("Adding program %d to the Program Map Table\n", i_pgrm_id);
              AddPgrmDescr(p_descr, i_pgrm_id);
            }
          }
          
          /* We now know the info carried in this section */
          Set_known(p_descr->a_known_PAT_sections, i_current_section);
        
          /* Check if the table is now complete */
          p_descr->i_known_PAT_sections++;
          if( p_descr->i_known_PAT_sections >= i_last_section)
            p_descr->b_is_PAT_complete = 1;
        }
    }

#undef p_descr
}


/******************************************************************************
 * DecodePgrmMapSection: Decode a PMS
 ******************************************************************************
 * No check is made to known if the table is currently applicable or not, so
 * that unapplicable sections must be filtered before calling this function
 * The Program Map Table can be segmented to occupy multiple sections so that
 * we have to know which sections we have already received (IsKnown() /
 * SetKnown() calls)
 * Note that the processing of those sections is different from the one of the
 * others since here a section refers to a single program, and a program cannot
 * be segmented into multiple sections
 ******************************************************************************/
static void DecodePgrmMapSection( u8* p_pms, input_thread_t* p_input )
{
  u16 i_pgrm_number;         /* Id of the program described in that section */
  u8 i_version;              /* Version of the description for that program */
  
  u16 i_offset;
  u16 i_section_length;
  u16 i_descr_end;
  
  u8 i_last_section;
  u8 i_current_section;

  u16 i_es_pid;

  int i_index = 0;
#ifdef AUTO_SPAWN
  int i_es_loop;
#endif
  pgrm_descriptor_t* p_pgrm;
  es_descriptor_t* p_es;

#define p_descr (p_input->p_stream)

  /* Read the id of the program described in that section */
  i_pgrm_number = U16_AT(&p_pms[3]);
//  intf_DbgMsg( "PMT section received for program %d\n", i_pgrm_number );

  /* Find where is stored the description of this program */
  for( i_index = 0; i_index < p_descr->i_pgrm_number &&
       i_pgrm_number != p_descr->ap_programs[i_index]->i_number; i_index++ );

  if( i_index >= p_descr->i_pgrm_number )
  {
    /* If none entry exists, this simply means that the PAT is not complete,
       so skip this section since it is the responsability of the PAT decoder
       to add pgrm_descriptor slots to the table of known pgrms */
    intf_DbgMsg( "Pgrm %d is unknown: skipping its description\n", i_pgrm_number );
    return;
  }

  /* We now have the slot which is the program described: we can begin with
     the decoding of its description */
  p_pgrm = p_descr->ap_programs[i_index];

  /* Which section of the description of that program did we receive ? */
  i_last_section = p_pms[7];
  i_current_section = p_pms[6];
//  intf_DbgMsg("Section %d (last section %d)\n", i_current_section, i_last_section);

  /* Is this an update of the description for this program ? */
  i_version = (p_pms[5] >> 1) && 0x1F;
    if( p_pgrm->i_version != i_version )
    {
        intf_Msg("Updating PMT for program %d\n", i_pgrm_number);

        for( i_index = 0; i_index < p_pgrm->i_es_number; i_index++ )
        {
          /* Stop the reception of the ES if needed by calling the function
             normally used by the interface to manage this */
          if( input_IsElemRecv(p_input, p_pgrm->ap_es[i_index]->i_id) )
          {
            intf_Msg( "PID %d is no more valid: stopping its reception\n",
                      p_pgrm->ap_es[i_index]->i_id );
            input_DelPgrmElem( p_input, p_pgrm->ap_es[i_index]->i_id );
          }

          /* Remove the descriptor associated to the es of this programs */
          intf_DbgMsg( "Invalidating PID %d\n", p_pgrm->ap_es[i_index]->i_id );
          DestroyESDescr(p_input, p_pgrm, p_pgrm->ap_es[i_index]->i_id);
        }
        
       /* Update version number */
        p_pgrm->i_version = i_version;

        /* Ask the decoder to update the description of that program */
        p_descr->i_known_PMT_sections--;
        Unset_known( p_descr->a_known_PMT_sections, i_current_section );
        p_pgrm->b_is_ok = 0;
    }

    /* Read the info for that pgrm is the one we have is not up to date or
       if we don't have any */
    if( p_pgrm->b_is_ok )
    {
      /* Nothing to do */
//      intf_DbgMsg("Program description OK, nothing to do\n");
    }
    else
    {
        /* Check if we already heard about that section */
        if( Is_known(p_descr->a_known_PMT_sections, i_current_section) )
        {
          /* Nothing to do */
//          intf_DbgMsg("Section already received, skipping\n");
        }
        else
        {
          /* Read the corresponding PCR */
          p_pgrm->i_pcr_pid = U16_AT(&p_pms[8]) & 0x1fff;
          intf_DbgMsg("PCR at PID: %d\n", p_pgrm->i_pcr_pid);

          /* Compute the length of the section minus the final CRC */
          i_section_length = (U16_AT(&p_pms[1]) & 0xFFF) + 3 - 4;
          intf_DbgMsg("Section length (CRC not included): %d\n", i_section_length);

          /* Read additional info stored in the descriptors if any */
          intf_DbgMsg("description length for program %d: %d\n", p_pgrm->i_number,
                   (U16_AT(&p_pms[10]) & 0x0FFF));
          i_descr_end = (U16_AT(&p_pms[10]) & 0x0FFF) + 12;
          intf_DbgMsg("description ends at offset: %d\n",  i_descr_end);

          i_offset = 12;
          while( i_offset < i_descr_end )
          {
            DecodePgrmDescriptor(&p_pms[i_offset], p_pgrm);
            i_offset += p_pms[i_offset+1] + 2;
          }

          /* Read all the ES descriptions */
          while( i_offset < i_section_length )
          {
            /* Read type of that ES */
            intf_DbgMsg("ES Type: %d\n", p_pms[i_offset]);

            /* Read PID of that ES */
            i_es_pid = U16_AT(&p_pms[i_offset+1]) & 0x1FF;
            intf_DbgMsg("ES PID: %d\n", i_es_pid);

            /* Add the ES to the program description and reserve a slot in the
               table of ES to store its description */
            p_es = AddESDescr(p_input, p_pgrm, i_es_pid);
            if (!p_es)
            {
              intf_ErrMsg("Warning: definition of program %d will be uncomplete\n", 
                          p_pgrm->i_number);
              /* The best way to handle this is to stop decoding the info for that
                 section but do as if everything is ok. Thus we will eventually have
                 an uncomplete ES table marked as being complete and some error msgs */
              break;
            }
            else
            {
              /* Store the description of that PID in the slot */
              p_es->i_type = p_pms[i_offset];
              p_es->b_psi = 0;
              if( i_es_pid == p_pgrm->i_pcr_pid )
                p_es->b_pcr = 1;
              else
                p_es->b_pcr = 0;
              
              /* Read additional info given by the descriptors */
              i_offset += 5;
              intf_DbgMsg("description length for PID %d: %d\n", p_es->i_id,
                       (U16_AT(&p_pms[i_offset-2]) & 0x0FFF));
              i_descr_end = (U16_AT(&p_pms[i_offset-2]) & 0x0FFF) + i_offset;
              intf_DbgMsg("description ends at offset: %d\n",  i_descr_end);
              while( i_offset < i_descr_end )
              {
                DecodeESDescriptor(&p_pms[i_offset], p_es);
                i_offset += p_pms[i_offset+1] + 2;
              }
            }

            /* Jump to next ES description */
          }

          /* We now know the info carried in this section */
          intf_Msg("Description of program %d complete\n", p_pgrm->i_number);
          p_pgrm->b_is_ok = 1;
          Set_known(p_descr->a_known_PMT_sections, i_current_section);
        
          /* Check if the PMT is now complete */
          p_descr->i_known_PMT_sections++;
          if( p_descr->i_known_PMT_sections >= i_last_section)
          {
              p_descr->b_is_PMT_complete = 1;

#ifdef AUTO_SPAWN
              /* Spawn an audio and a video thread, if possible. */
              for( i_es_loop = 0; i_es_loop < INPUT_MAX_ES; i_es_loop++ )
              {
                  switch( p_input->p_es[i_es_loop].i_type )
                  {
                  case MPEG1_VIDEO_ES:
                  case MPEG2_VIDEO_ES:
                  case MPEG1_AUDIO_ES:
                  case MPEG2_AUDIO_ES:
                      /* Spawn an audio thread */
                      input_AddPgrmElem( p_input, p_input->p_es[i_es_loop].i_id );
                      break;
                  default:
                  }
              }
#endif
          }
        }
    }

#undef p_descr
}



/******************************************************************************
 * DecodeSrvDescrSection
 ******************************************************************************
 * A finir et a refaire proprement ????
 ******************************************************************************/
void DecodeSrvDescrSection( byte_t* p_sdt, input_thread_t *p_input )
{
  u16 i_stream_id;
  u8 i_version;
  u16 i_length;
  
  int i_index;
  int i_offset;
  boolean_t b_must_update = 0;
  
  int i_descr_end;

  ASSERT(p_sdt);
  ASSERT(p_input);

#define p_stream (p_input->p_stream)
  
   /* Read stream id and version number immediately, to be sure they will be
      initialised in all the cases in which we will need them */
   i_stream_id = U16_AT(&p_sdt[3]);
   i_version = (p_sdt[5] >> 1) & 0x1F;
   intf_DbgMsg("TS Id: %d, version: %d\n", i_stream_id, i_version);

   /* Take the descriptor into account only if it describes the streams we are
      receiving */
   if( p_stream->i_stream_id != i_stream_id )
   {
     intf_DbgMsg("SDT doen't apply to our TS but to %s: aborting\n",
                  U16_AT(&p_sdt[3]));
   }
   else
   {
     /* Section applyies to our TS, test if the SDT is up to date */
     if( p_stream->i_SDT_version != i_version )
     {
       intf_Msg("SDT has been updated, NOT YET IMPLEMENTED\n");

       /* Ask the PSI decoder to rebuild the table */
        b_must_update = 1;
     }
   }

   /* Rebuild the table if needed */
   if( b_must_update )
   {
     intf_DbgMsg("Updating SDT table\n");

     i_length = p_sdt[1] & 0x0FFF;
     i_offset = 11;

     while(i_offset < i_length)
     {
       
       /* Find the program to which the description applies */
       for( i_index = 0; i_index < p_stream->i_pgrm_number; i_index++ )
       {
         if( p_stream->ap_programs[i_index]->i_number == U16_AT(&p_sdt[i_offset]) )
         {
           /* Here we are */
           intf_Msg("FOUND: %d\n", p_stream->ap_programs[i_index]->i_number);
           break;
         }
       }

       /* Copy the info to the description of that program */
       i_offset += 5;
       intf_DbgMsg("description length for SDT: %d\n",
                   (U16_AT(&p_sdt[i_offset-2]) & 0x0FFF));
       i_descr_end = (U16_AT(&p_sdt[i_offset-2]) & 0x0FFF) + i_offset;
       intf_DbgMsg("description ends at offset: %d\n",  i_descr_end);
       while( i_offset < i_descr_end )
       {
         DecodePgrmDescriptor(&p_sdt[i_offset], p_stream->ap_programs[i_index]);
         i_offset += p_sdt[i_offset+1] + 2;
       }
     }
   }
#undef p_stream
};







/******************************************************************************
 * DecodePgrmDescr
 ******************************************************************************
 * Decode any descriptor applying to the definition of a program
 ******************************************************************************/
static void DecodePgrmDescriptor( byte_t* p_descriptor, pgrm_descriptor_t* p_pgrm )
{
    u8 i_type;                                     /* Type of the descriptor */
    u8 i_length;                                 /* Length of the descriptor */
#ifdef DVB_EXTENSIONS
    int i_offset;                      /* Current position in the descriptor */
#endif

    ASSERT(p_descriptor);
    ASSERT(p_pgrm);

    /* Read type and length of the descriptor */
    i_type = p_descriptor[0];
    i_length = p_descriptor[1];

    /* Handle specific descriptor info */
    switch(i_type)
    {
#ifdef DVB_EXTENSIONS        
    case PSI_SERVICE_DESCRIPTOR:
    {
        /* Store service type */
        p_pgrm->i_srv_type = p_descriptor[2];

        /* Jump to the beginning of the service name */
        i_offset = p_descriptor[3] + 5;

        /* Check that the charset used is the normal one (latin) by testing the
           first byte of the name */
        if( p_descriptor[i_offset] >= 0x20 )
        {
            /* The charset is the one of our computer: just manually dup the string */
            p_pgrm->psz_srv_name = malloc(i_length - i_offset +1);
            memcpy(p_pgrm->psz_srv_name, &p_descriptor[i_offset], i_length - i_offset);
            p_pgrm->psz_srv_name[i_length - i_offset + 1] = '\0';
        }
        else
        {
            /* Indicate that the name couldn't be printed */
            p_pgrm->psz_srv_name = "Ununderstandable :)";
        }
        break;
    }
#endif
    default:
//        intf_DbgMsg("Unhandled program descriptor received (type: %d)\n", i_type);
//        intf_Msg("Unhandled ES descriptor received (type: %d)\n", i_type);
    }
}




/******************************************************************************
 * DecodeESDescriptor
 ******************************************************************************
 * Decode any descriptor applying to the definition of an ES
 ******************************************************************************/
static void DecodeESDescriptor( byte_t* p_descriptor, es_descriptor_t* p_es )
{
    u8 i_type;                                     /* Type of the descriptor */
    u8 i_length;                                 /* Length of the descriptor */
//    int i_offset;                      /* Current position in the descriptor */

    ASSERT(p_descriptor);
    ASSERT(p_es);

    /* Read type and length of the descriptor */
    i_type = p_descriptor[0];
    i_length = p_descriptor[1];

    switch( i_type )
    {
    case PSI_VIDEO_STREAM_DESCRIPTOR:
    {
        intf_DbgMsg("Video stream descriptor received\n");
        break;
    }
    case PSI_AUDIO_STREAM_DESCRIPTOR:
    {
        intf_DbgMsg("Audio stream descriptor received\n");
        break;
    }
    case PSI_TARGET_BACKGROUND_GRID_DESCRIPTOR:
    {
        intf_DbgMsg("Target background descriptor received\n");
        break;
    }   
    case PSI_VIDEO_WINDOW_DESCRIPTOR:
    {
        intf_DbgMsg("Video window descriptor received\n");
        break;
    }
    default:
//        intf_DbgMsg("Unhandled ES descriptor received (type: %d)\n", i_type);
//        intf_Msg("Unhandled ES descriptor received (type: %d)\n", i_type);
    }
}



/******************************************************************************
 * input_AddPsiPID: Start to receive the PSI info contained in a PID
 ******************************************************************************
 * Add a descriptor to the table of es descriptor for that es and mark the es
 * as being to be received by the input (since all PSI must be received to
 * build the description of the program)
 ******************************************************************************/
static int input_AddPsiPID( input_thread_t *p_input, int i_pid )
{
  int i_index;
  es_descriptor_t* p_psi_es;
  int i_rc = 0;  

  /* Store the description of this stream in the input thread */
  p_psi_es = AddESDescr(p_input, NULL, i_pid);
  
  if(p_psi_es)
  {
    /* Precise this ES carries PSI */
    p_psi_es->b_psi = 1;

    /* Create the buffer needed by the PSI decoder */
    p_psi_es->p_psi_section = malloc( sizeof( psi_section_t) );
    if( !p_psi_es->p_psi_section )
    {
      intf_ErrMsg( "Malloc error\n" );
      i_rc = -1;
    }
    else
    {
      /* Init the reception for that PID */
      p_psi_es->p_psi_section->b_running_section = 0;
//      p_psi_es->p_psi_section->b_discard_payload = 0;

      /* Ask the input thread to demultiplex it: since the interface
         can also access the table of selected es, lock the elementary
         stream structure */
      pthread_mutex_lock( &p_input->es_lock );
      for( i_index = 0; i_index < INPUT_MAX_SELECTED_ES; i_index++ )
      {
        if( !p_input->pp_selected_es[i_index] )
        {
          intf_DbgMsg( "Free Selected ES slot found at offset %d: Will use it for PID %d\n",
                       i_index, i_pid );
          p_input->pp_selected_es[i_index] = p_psi_es;
          break;
        }
      }
      pthread_mutex_unlock( &p_input->es_lock );

      if( i_index >= INPUT_MAX_SELECTED_ES )
      {
        intf_ErrMsg( "Stream carries to many PID for our decoder\n" );
        i_rc = -1;
      }
    }
  }

  return( i_rc );
}


/******************************************************************************
 * input_DelPsiPID: Stop to receive the PSI info contained in a PID
 ******************************************************************************
 * Remove the PID from the list of ES descriptors and from the list of ES that
 * the input must receive.
 * Known PID for PSI should always be received, so that their description
 * should be pointed out by a member of pp_selected_es. But as INPUT_MAX_ES
 * can be different of INPUT_MAX_SELECTED_ES, this may happen, so that we must
 * do 2 loops.
 ******************************************************************************/
static int input_DelPsiPID( input_thread_t *p_input, int i_pid )
{
  int i_es_index, i_last_sel;  

  intf_DbgMsg( "Deleting PSI PID %d\n", i_pid );

  /* Stop to receive the ES. Since the interface can also access the table
     of selected es, lock the elementary stream structure */
  pthread_mutex_lock( &p_input->es_lock );
  
  for( i_es_index = 0; i_es_index < INPUT_MAX_SELECTED_ES; i_es_index++ )
  {
    if( p_input->pp_selected_es[i_es_index] &&
        p_input->pp_selected_es[i_es_index]->i_id == i_pid )
    {
      /* Unmark the stream */
      p_input->pp_selected_es[i_es_index] = NULL;

      /* There must not be any gap in the pp_selected_es, so move the last
         selected stream to this slot */
      for( i_last_sel = i_es_index; p_input->pp_selected_es[i_last_sel] &&
            i_last_sel < INPUT_MAX_SELECTED_ES; i_last_sel++ );
      p_input->pp_selected_es[i_es_index] = p_input->pp_selected_es[i_last_sel];
      p_input->pp_selected_es[i_last_sel] = NULL;
      break;
    }
  }

  pthread_mutex_unlock( &p_input->es_lock );

#ifdef DEBUG
  /* Check if the pp_selected_es table may be corrupted */
  if( i_es_index >= INPUT_MAX_PROGRAM_ES )
  {
    intf_ErrMsg( "DelPsiPID error: PID %d is not currently received\n", i_pid );
  }
#endif

  /* Remove the desription of that ES from the table of ES */
  DestroyESDescr(p_input, NULL, i_pid);

  return( 0 );
}



/******************************************************************************
 * Precalculate the 32-bit CRC table
 ******************************************************************************
 * This table is a global variable shared by all decoders, so it has to be
 * initialised only once
 ******************************************************************************/
void BuildCrc32Table( )
{
    u32 i, j, k;
    for( i = 0 ; i < 256 ; i++ )
    {
        k = 0;
        for (j = (i << 24) | 0x800000 ; j != 0x80000000 ; j <<= 1)
            k = (k << 1) ^ (((k ^ j) & 0x80000000) ? 0x04c11db7 : 0);
        i_crc_32_table[i] = k;
    }
}


/******************************************************************************
 * Test the validity of a checksum
 ******************************************************************************
 * The checksum must be stored at the end of the data, and the given size must
 * include the 32 bits of the CRC.
 * Return 0 if the checksum is OK, any other value if the data are corrupted
 ******************************************************************************/
int CheckCRC32(byte_t* p_data, int i_data_size)
{  
  int i;
  u32 i_crc = 0xffffffff;

  ASSERT(p_data);

  for (i = 0; i < i_data_size; i++)
    i_crc = (i_crc << 8) ^ i_crc_32_table[(i_crc >> 24) ^ p_data[i]];

  return i_crc;
}



/******************************************************************************
 * Is_known: check if a given section has already been received
 ******************************************************************************
 * As a table cannot be segmented into more than 256 sections, we store a 256
 * bits long table, each bit set to one indicating that the corresponding
 * saction has been received
 ******************************************************************************/
boolean_t Is_known( byte_t* a_known_section, u8 i_section )
{
  byte_t mask;
  boolean_t b_is_known;
  
  /* Where to get the information ? */
  int i_bit_in_byte = i_section % 8;
  int i_byte_in_table = (i_section - i_bit_in_byte) / 8;

  /* Build mask to read the Is_known flag */
  mask = 0x01 << i_bit_in_byte;
  
  /* Read the flag */
  b_is_known = a_known_section[i_byte_in_table] & mask;

  return b_is_known;
}


/******************************************************************************
 * Set_known: mark a given section has having been received
 ******************************************************************************
 *
 ******************************************************************************/
static void Set_known( byte_t* a_known_section, u8 i_section )
{
  byte_t mask;
  
  /* Where to get the information ? */
  int i_bit_in_byte = i_section % 8;
  int i_byte_in_table = (i_section - i_bit_in_byte) / 8;

  /* Build mask to read the Is_known flag */
  mask = 0x01 << i_bit_in_byte;
  
  /* Set the flag */
  a_known_section[i_byte_in_table] |= mask;
}


/******************************************************************************
 * Unset_known: remove the 'received' mark for a given section
 ******************************************************************************
 * 
 ******************************************************************************/
static void Unset_known( byte_t* a_known_section, u8 i_section )
{
  byte_t mask;
  
  /* Where to get the information ? */
  int i_bit_in_byte = i_section % 8;
  int i_byte_in_table = (i_section - i_bit_in_byte) / 8;

  /* Build mask to read the Is_known flag */
  mask = 0x01 << i_bit_in_byte;
  mask = ~mask;
  
  /* Unset the flag */
  a_known_section[i_byte_in_table] &= mask;
}


/******************************************************************************
 * AddStreamDescr: add and init the stream descriptor of the given input
 ******************************************************************************
 * 
 ******************************************************************************/
static stream_descriptor_t* AddStreamDescr(input_thread_t* p_input, u16 i_stream_id)
{
  ASSERT(p_input);
  
  intf_DbgMsg("Adding description for stream %d\n", i_stream_id);

  p_input->p_stream = malloc( sizeof(stream_descriptor_t) );

  p_input->p_stream->i_stream_id = i_stream_id;

  p_input->p_stream->i_PAT_version = PSI_UNINITIALISED;
  p_input->p_stream->i_known_PAT_sections = 0;
  bzero( p_input->p_stream->a_known_PAT_sections,
         sizeof(*p_input->p_stream->a_known_PAT_sections) );
  p_input->p_stream->b_is_PAT_complete = 0;

  p_input->p_stream->i_known_PMT_sections = 0;
  bzero( p_input->p_stream->a_known_PMT_sections,
         sizeof(*p_input->p_stream->a_known_PMT_sections) );
  p_input->p_stream->b_is_PMT_complete = 0;

#ifdef DVB_EXTENSIONS
  p_input->p_stream->i_SDT_version = PSI_UNINITIALISED;
  p_input->p_stream->i_known_SDT_sections = 0;
  bzero( p_input->p_stream->a_known_SDT_sections,
         sizeof(*p_input->p_stream->a_known_SDT_sections) );
  p_input->p_stream->b_is_SDT_complete = 0;
#endif

  p_input->p_stream->i_pgrm_number = 0;
  p_input->p_stream->ap_programs = NULL;

  return p_input->p_stream;
}


/******************************************************************************
 * DestroyStreamDescr: destroy the stream desciptor of the given input
 ******************************************************************************
 * 
 ******************************************************************************/
static void DestroyStreamDescr(input_thread_t* p_input, u16 i_stream_id)
{
  int i_index;

  ASSERT(p_input);

  /* Free the structures that describes the programs of that stream */
  for( i_index = 0; i_index < p_input->p_stream->i_pgrm_number; i_index++ )
  {
    DestroyPgrmDescr( p_input, p_input->p_stream,
                      p_input->p_stream->ap_programs[i_index]->i_number );
  }
  
  /* Free the table of pgrm descriptors */
  free( p_input->p_stream->ap_programs );

  /* Free the structure that describes the stream itself */
  free( p_input->p_stream );

  /* Input thread has no more stream descriptor */
  p_input->p_stream = NULL;
}


/******************************************************************************
 * AddPgrmDescr: add and init a program descriptor
 ******************************************************************************
 * This program descriptor will be referenced in the given stream descriptor
 ******************************************************************************/
static pgrm_descriptor_t* AddPgrmDescr(stream_descriptor_t* p_stream, u16 i_pgrm_id)
{
  ASSERT(p_stream);

  intf_DbgMsg("Adding description for pgrm %d\n", i_pgrm_id);

  /* Add an entry to the list of program associated with the stream */
  p_stream->i_pgrm_number++;
  p_stream->ap_programs = realloc( p_stream->ap_programs, 
                                   p_stream->i_pgrm_number*sizeof(pgrm_descriptor_t*) );

  /* Allocate the structure to store this description */
  p_stream->ap_programs[p_stream->i_pgrm_number-1] = malloc(sizeof(pgrm_descriptor_t));

  /* Init this entry */
  p_stream->ap_programs[p_stream->i_pgrm_number-1]->i_number = i_pgrm_id;
  p_stream->ap_programs[p_stream->i_pgrm_number-1]->i_version = PSI_UNINITIALISED;
  p_stream->ap_programs[p_stream->i_pgrm_number-1]->b_is_ok = 0;

  p_stream->ap_programs[p_stream->i_pgrm_number-1]->i_es_number = 0;
  p_stream->ap_programs[p_stream->i_pgrm_number-1]->ap_es = NULL;

  /* descriptors ???? */

  return p_stream->ap_programs[p_stream->i_pgrm_number-1];
}


/******************************************************************************
 * AddPgrmDescr: destroy a program descriptor
 ******************************************************************************
 * All ES descriptions referenced in the descriptor will be deleted.
 ******************************************************************************/
static void DestroyPgrmDescr(input_thread_t* p_input, stream_descriptor_t* p_stream, u16 i_pgrm_id)
{
  int i_index;
  pgrm_descriptor_t* p_pgrm;
  
  ASSERT(p_stream);

  /* Find where is described this program */
  for(i_index = 0; i_index < p_stream->i_pgrm_number; i_index++)
  {
    if( p_stream->ap_programs[i_index]->i_number == i_pgrm_id )
    {
      /* Here we are */
      p_pgrm = p_stream->ap_programs[i_index];
      break;
    }
  }

  /* Free the structures that describes the es that belongs to that program */
  for( i_index = 0; i_index < p_pgrm->i_es_number; i_index++ )
    DestroyESDescr(p_input, p_pgrm, p_pgrm->ap_es[i_index]->i_id);

  /* Free the table of es descriptors */
  free( p_pgrm->ap_es );

  /* Free the description of this stream */
  free( p_pgrm );

  /* Remove this program from he list of programs of the stream */
  p_stream->i_pgrm_number--;
  p_stream->ap_programs[i_index] = p_stream->ap_programs[p_stream->i_pgrm_number];
  p_stream->ap_programs = realloc(p_stream->ap_programs, p_stream->i_pgrm_number);
}


/******************************************************************************
 * AddESDescr:
 ******************************************************************************
 * Reserve a slot in the table of ES descritors for the ES and add it to the
 * list of ES of p_pgrm. If p_pgrm if NULL, then the ES is considered as stand
 * alone (PSI ?)
 ******************************************************************************/
static es_descriptor_t* AddESDescr(input_thread_t* p_input,
                                   pgrm_descriptor_t* p_pgrm, u16 i_es_pid)
{
  int i_index;
  es_descriptor_t* p_es = NULL;
  
  ASSERT(p_input);
  
  intf_DbgMsg("Adding description for ES %d\n", i_es_pid);

  /* Find an empty slot to store the description of that es */
  for( i_index = 0; i_index < INPUT_MAX_ES &&
        p_input->p_es[i_index].i_id != EMPTY_PID; i_index++ );

  if( i_index >= INPUT_MAX_ES )
  {
    /* No slot is empty */
    intf_ErrMsg("Stream carries to many PID for our decoder\n");
  }
  else
  {
    /* Reserve the slot for that ES */
    p_es = &p_input->p_es[i_index];
    p_es->i_id = i_es_pid;
    intf_DbgMsg("Slot %d in p_es table reserved for ES %d\n", i_index, i_es_pid);
    
    /* Init its values */
    p_es->i_type = 0;  /* ??? */
    p_es->b_psi = 0;
    p_es->b_pcr = 0;
    
    p_es->p_pes_packet = NULL;
//    p_es->p_next_pes_packet = NULL;
    p_es->p_dec = NULL;

    /* Add this ES to the program definition if one is given */
    if( p_pgrm )
    {
      p_pgrm->i_es_number++;
      p_pgrm->ap_es = realloc( p_pgrm->ap_es, p_pgrm->i_es_number*sizeof(es_descriptor_t *) );
      p_pgrm->ap_es[p_pgrm->i_es_number-1] = p_es;
      intf_DbgMsg("Added ES %d to definition of pgrm %d\n", i_es_pid, p_pgrm->i_number);
    }
    else
      intf_DbgMsg("Added ES %d not added to the definition of any pgrm\n", i_es_pid);
  }
  
  return p_es;
}



/******************************************************************************
 * DestroyESDescr:
 ******************************************************************************
 * 
 ******************************************************************************/
static void DestroyESDescr(input_thread_t* p_input, pgrm_descriptor_t* p_pgrm, u16 i_pid)
{
  int i_index;
  
  ASSERT(p_pgrm);

  /* Look for the description of the ES */
  for(i_index = 0; i_index < INPUT_MAX_ES; i_index++)
  {
    if( p_input->p_es[i_index].i_id == i_pid )
    {
      /* The table of stream descriptors is static, so don't free memory
         but just mark the slot as unused */
      p_input->p_es[i_index].i_id = EMPTY_PID;
      break;
    }
  }

  /* Remove this ES from the description of the program if it is associated to
   one */
  if( p_pgrm )
  {
    for( i_index = 0; i_index < INPUT_MAX_ES; i_index++ )
    {
      if( p_input->p_es[i_index].i_id == i_pid )
      {
        p_pgrm->i_es_number--;
        p_pgrm->ap_es[i_index] = p_pgrm->ap_es[p_pgrm->i_es_number];
        p_pgrm->ap_es = realloc(p_pgrm->ap_es, p_pgrm->i_es_number);
        break;
      }
    }
  }
}
