/*
 * Copyright (C) 2007-2009 KenD00
 * 
 * This file is part of DumpHD.
 * 
 * DumpHD 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 3 of the License, or
 * (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package dumphd.util;

import java.util.Arrays;
import java.util.Vector;

/**
 * A class to provide easy access to the parts of a PES pack. This class works like a mask, no copy of the underlying data is made,
 * only some values are buffered for faster multiple access. Data which can be obtained through simple byte copy is not handled by this class
 * to avoid unnecessary memory copy. A fixed pack size of 2048 bytes is required for this class.
 * 
 * This class also defines the names of given pack / packet id's and types.
 * 
 * If getters or setters are called before a pack has been successfully parsed, exceptions may occur and the results are undefined.
 * 
 * If getters or setters are called which are not supported by the pack / packet type or scan type, the result is undefined if not stated otherwise.
 * 
 * @author KenD00 
 */
public final class PESPack {

   // Lookup-Tables for Stream IDs and different SubStream IDs
   // Although SubStream IDs are specific for their Stream ID there are some intersections
   public final static String[] S_ID_NAMES = new String[256];
   public final static String[] PS1_ID_NAMES = new String[256];
   public final static String[] PS2_ID_NAMES = new String[256];

   public final static int PACK_LENGTH = 2048;

   /**
    * Static constructor for initialization of lookup-tables
    */
   static {
      // Fill name tables with default names (stream-number)
      for (int i = 0; i < 256; i++) {
         String nameString = String.format("0x%1$02X" , i);
         S_ID_NAMES[i] = nameString;
         PS1_ID_NAMES[i] = nameString;
         PS2_ID_NAMES[i] = nameString;
      }
      // Known stream names
      S_ID_NAMES[0xBA] = "PACK_HEADER";
      S_ID_NAMES[0xBB] = "SYSTEM_HEADER";
      S_ID_NAMES[0xBD] = "PRIVATE_STREAM_1";
      S_ID_NAMES[0xBE] = "PADDING_STREAM";
      S_ID_NAMES[0xBF] = "PRIVATE_STREAM_2";
      S_ID_NAMES[0xE0] = "MPEG-2_VIDEO_STREAM_FOR_MAIN";
      S_ID_NAMES[0xE1] = "MPEG-2_VIDEO_STREAM_FOR_SUB";
      S_ID_NAMES[0xE2] = "MPEG-4-AVC_VIDEO_STREAM_FOR_MAIN";
      S_ID_NAMES[0xE3] = "MPEG-4-AVC_VIDEO_STREAM_FOR_SUB";
      S_ID_NAMES[0xFD] = "VC-1_VIDEO_STREAM";
      // Known private stream 1 substream names
      // Subpicture streams
      PS1_ID_NAMES[0x20] = "SUBPICTURE_STREAM_#01";
      PS1_ID_NAMES[0x21] = "SUBPICTURE_STREAM_#02";
      PS1_ID_NAMES[0x22] = "SUBPICTURE_STREAM_#03";
      PS1_ID_NAMES[0x23] = "SUBPICTURE_STREAM_#04";
      PS1_ID_NAMES[0x24] = "SUBPICTURE_STREAM_#05";
      PS1_ID_NAMES[0x25] = "SUBPICTURE_STREAM_#06";
      PS1_ID_NAMES[0x26] = "SUBPICTURE_STREAM_#07";
      PS1_ID_NAMES[0x27] = "SUBPICTURE_STREAM_#08";
      PS1_ID_NAMES[0x28] = "SUBPICTURE_STREAM_#09";
      PS1_ID_NAMES[0x29] = "SUBPICTURE_STREAM_#10";
      PS1_ID_NAMES[0x2A] = "SUBPICTURE_STREAM_#11";
      PS1_ID_NAMES[0x2B] = "SUBPICTURE_STREAM_#12";
      PS1_ID_NAMES[0x2C] = "SUBPICTURE_STREAM_#13";
      PS1_ID_NAMES[0x2D] = "SUBPICTURE_STREAM_#14";
      PS1_ID_NAMES[0x2E] = "SUBPICTURE_STREAM_#15";
      PS1_ID_NAMES[0x2F] = "SUBPICTURE_STREAM_#16";
      PS1_ID_NAMES[0x30] = "SUBPICTURE_STREAM_#17";
      PS1_ID_NAMES[0x31] = "SUBPICTURE_STREAM_#18";
      PS1_ID_NAMES[0x32] = "SUBPICTURE_STREAM_#19";
      PS1_ID_NAMES[0x33] = "SUBPICTURE_STREAM_#20";
      PS1_ID_NAMES[0x34] = "SUBPICTURE_STREAM_#21";
      PS1_ID_NAMES[0x35] = "SUBPICTURE_STREAM_#22";
      PS1_ID_NAMES[0x36] = "SUBPICTURE_STREAM_#23";
      PS1_ID_NAMES[0x37] = "SUBPICTURE_STREAM_#24";
      PS1_ID_NAMES[0x38] = "SUBPICTURE_STREAM_#25";
      PS1_ID_NAMES[0x39] = "SUBPICTURE_STREAM_#26";
      PS1_ID_NAMES[0x3A] = "SUBPICTURE_STREAM_#27";
      PS1_ID_NAMES[0x3B] = "SUBPICTURE_STREAM_#28";
      PS1_ID_NAMES[0x3C] = "SUBPICTURE_STREAM_#29";
      PS1_ID_NAMES[0x3D] = "SUBPICTURE_STREAM_#30";
      PS1_ID_NAMES[0x3E] = "SUBPICTURE_STREAM_#31";
      PS1_ID_NAMES[0x3F] = "SUBPICTURE_STREAM_#32";
      // Dolby Digital Plus audio streams for main
      PS1_ID_NAMES[0xC0] = "DD+_AUDIO_STREAM_FOR_MAIN_#01";
      PS1_ID_NAMES[0xC1] = "DD+_AUDIO_STREAM_FOR_MAIN_#02";
      PS1_ID_NAMES[0xC2] = "DD+_AUDIO_STREAM_FOR_MAIN_#03";
      PS1_ID_NAMES[0xC3] = "DD+_AUDIO_STREAM_FOR_MAIN_#04";
      PS1_ID_NAMES[0xC4] = "DD+_AUDIO_STREAM_FOR_MAIN_#05";
      PS1_ID_NAMES[0xC5] = "DD+_AUDIO_STREAM_FOR_MAIN_#06";
      PS1_ID_NAMES[0xC6] = "DD+_AUDIO_STREAM_FOR_MAIN_#07";
      PS1_ID_NAMES[0xC7] = "DD+_AUDIO_STREAM_FOR_MAIN_#08";
      // Dolby Digital Plus audio streams for sub
      PS1_ID_NAMES[0xC8] = "DD+_AUDIO_STREAM_FOR_SUB_#01";
      PS1_ID_NAMES[0xC9] = "DD+_AUDIO_STREAM_FOR_SUB_#02";
      PS1_ID_NAMES[0xCA] = "DD+_AUDIO_STREAM_FOR_SUB_#03";
      PS1_ID_NAMES[0xCB] = "DD+_AUDIO_STREAM_FOR_SUB_#04";
      PS1_ID_NAMES[0xCC] = "DD+_AUDIO_STREAM_FOR_SUB_#05";
      PS1_ID_NAMES[0xCD] = "DD+_AUDIO_STREAM_FOR_SUB_#06";
      PS1_ID_NAMES[0xCE] = "DD+_AUDIO_STREAM_FOR_SUB_#07";
      PS1_ID_NAMES[0xCF] = "DD+_AUDIO_STREAM_FOR_SUB_#08";
      // Known private stream 2 substream names
      PS2_ID_NAMES[0x00] = "PCI_PKT";
      PS2_ID_NAMES[0x01] = "DSI_PKT";
      PS2_ID_NAMES[0x04] = "GCI_PKT";
      PS2_ID_NAMES[0x08] = "HL_PCK";
      PS2_ID_NAMES[0x80] = "ADV_PCK";
   }

   /**
    * The underlying byte array this object uses
    */
   private byte[] pack = null;
   /**
    * The offset of the PES Pack inside the underlying byte array
    */
   private int packOffset = 0;
   /**
    * Contains the packets of the pack
    */
   private Vector<PESPacket> packets = new Vector<PESPacket>(8);
   /**
    * The actual number of packets in this pack
    */
   private int packetCount = 0;


   /**
    * Constructs a new PESPack.
    */
   public PESPack() {
      // Nothing
   }

   /**
    * Parses the given pack. If a exception occurs the state of this object is undefined.
    * In quick scan mode, not the complete header is parsed, only the parts for StreamID and SubstreamID.
    * 
    * FIXME: I think in full scan mode sometimes the data in the header is shorter than the length field indicates and
    *        because i just continue reading after parsing the header i read from the wrong location (results into wrong substream id)
    *  
    * @param pack Byte array which contains the pack data to parse
    * @param offset Offset to the pack data in the supplied byte array
    * @param quickScan If true, quick scan mode is used, otherwise full scan mode
    */
   public void parse(byte[] pack, int offset, boolean quickScan) throws PESParserException {
      // Current absolute position in the byte array
      int pos = 0;
      int endOffset = offset + PACK_LENGTH;
      // Check if pack is 2048 bytes
      if (endOffset <= pack.length) {
         // Check pack start_code and pack_identifier
         if (ByteArray.getVarLong(pack, offset, 4) == 0x000001BA) {
            // Overread pack header and stuffing bytes
            pos = offset + 14 + (pack[offset + 13] & 0x07);
            // Update the internal values
            this.pack = pack;
            this.packOffset = offset;
            this.packetCount = 0;
            // Parse the whole pack
            // Check if enough space is left for a packet header, if not, disregard the rest
            while (pos + 6 <= endOffset) {
               // Check start_code
               if (ByteArray.getVarLong(pack, pos, 3) == 0x000001) {
                  if (packets.size() <= packetCount) {
                     packets.add(new PESPacket());
                     //System.out.println("Packet added, size now: " + packets.size() + ", packetCount: " + packetCount);
                  }
                  //System.out.println("Getting packet: " + packetCount);
                  // Increment packetCount after retrieving the packet because packetCount is one bigger than the index
                  PESPacket currentPacket = packets.get(packetCount);
                  packetCount += 1;
                  currentPacket.packetOffset = pos;
                  //System.out.println("Current Pos: " + pos);
                  // Advance to stream_id
                  pos += 3;
                  currentPacket.stream_id = ByteArray.getUByte(pack, pos);
                  currentPacket.stream_idOffset = pos; 
                  // Assume that only SYSTEM_STREAM, PADDING_STREAM, PRIVATE_STREAM_2 dont have an extension
                  if (currentPacket.stream_id == 0xBB || currentPacket.stream_id == 0xBE || currentPacket.stream_id == 0xBF) {
                     currentPacket.hasExtension = false;
                  } else {
                     currentPacket.hasExtension = true;
                  }
                  // Step behind stream_id
                  pos += 1;
                  // Check pes_packet_length
                  currentPacket.length = ByteArray.getUShort(pack, pos);
                  // Step behind the pes_packet_length
                  pos += 2;
                  // Check if packet does not exceed pack size
                  if (pos + currentPacket.length <= endOffset) {
                     //if (currentPacket.length != 0x07EC) System.out.println("PacketLength: " + currentPacket.length);
                     if (currentPacket.hasExtension) {
                        // Check if packet is big enough for the following extension
                        if (pos + 3 <= endOffset) {
                           // Check for quickScan
                           // In quickScan mode the header gets overread, it is jumped directly to the substrem_id position
                           // Its unimportant if there is really a substrem_id, it is later checked if there is a substream_id
                           if (quickScan) {
                              // Jump to pes_header_length
                              pos += 2;
                              int phl = ByteArray.getUByte(pack, pos);
                              // Jump just behind the extension
                              pos += 1;
                              // Check if the packet is big enough for the following header data
                              // It is not checked if there is also room for the substream_id, that is checked after decision if there is a substream_id
                              if (pos + phl  <= endOffset) {
                                 if (currentPacket.stream_id == 0xFD) {
                                    // For VC-1 streams, the stream_id_extension is just the byte before the first payload byte
                                    pos += phl - 1;
                                 } else {
                                    // For every other streams, the substream_id is the first byte of the payload
                                    pos += phl;
                                 }
                              } else throw new PESParserException("Packet header size exceeds packet size at: 0x" + Integer.toHexString(pos - offset).toUpperCase());
                           } else {
                              // TODO: Somehow full scan mode looks quite useless now
                              // Parse the extension (without header data)
                              // These values are not really needed to be buffered
                              // All values are not needed to be buffered, they are just stored because they are used for the header data size calculation
                              //currentPacket.psc = (pack[pos] >>> 4) & 0x03;
                              //currentPacket.pp = (pack[pos] >>> 3) & 0x01;
                              //currentPacket.dai = (pack[pos] >>> 2) & 0x01;
                              //currentPacket.c = (pack[pos] >>> 1) & 0x01;
                              //currentPacket.coo = pack[pos] & 0x01;
                              // Step to next byte in extension
                              pos += 1;
                              currentPacket.ptsdts = (pack[pos] >>> 6) & 0x03;
                              currentPacket.escr = (pack[pos] >>> 5) & 0x01;
                              currentPacket.esr = (pack[pos] >>> 4) & 0x01;
                              currentPacket.aci = (pack[pos] >>> 2) & 0x01;
                              currentPacket.pescrc = (pack[pos] >>> 1) & 0x01;
                              currentPacket.pesext1 = pack[pos] & 0x1;
                              // Modify for easy size-calculation
                              if (currentPacket.ptsdts > 0) {
                                 currentPacket.ptsdts -= 1;
                              }
                              // Step to next byte in extension
                              pos += 1;
                              int pesEndOffset = pos + 1 + ByteArray.getUByte(pack, pos);
                              // Advance behind the parsed section
                              pos += 1 + (currentPacket.ptsdts * 5) + (currentPacket.escr * 6) + (currentPacket.esr * 3) + (currentPacket.aci * 1) + (currentPacket.pescrc * 2);
                              // Check if packet is big enough for the following header
                              if (pesEndOffset <= endOffset) {
                                 // Check if pes_extension_1 is present and parse it if it is
                                 if (currentPacket.pesext1 == 1) {
                                    // Check if packet is big enough for pes_extension_1
                                    // Use < to because we are reading from pos
                                    if (pos < pesEndOffset) {
                                       currentPacket.pesext1Offset = pos;
                                       // Parse (without header data)
                                       currentPacket.pespd = (pack[pos] >>> 7) & 0x01;
                                       currentPacket.phf = (pack[pos] >>> 6) & 0x01;
                                       currentPacket.ppsc = (pack[pos] >>> 5) & 0x01;
                                       currentPacket.pstdb = (pack[pos] >>> 4) & 0x01;
                                       currentPacket.pesext2 = pack[pos] & 0x01;
                                       // Advance behind the parsed section
                                       pos += 1 + (currentPacket.pespd * 16) + (currentPacket.phf * 1) + (currentPacket.ppsc * 2) + (currentPacket.pstdb * 2);
                                       // Check if pes_extension_2 is present and parse it if it is
                                       if (currentPacket.pesext2 == 1) {
                                          // Check if packet is big enough for pes_extension_2
                                          // Use < to because we are reading from pos
                                          if (pos < pesEndOffset) {
                                             // Parse (without header data)
                                             currentPacket.pesext2Offset = pos;
                                             currentPacket.pefl = pack[pos] & 0x7F;
                                             // Check if packet is big enough for pes_extension_2 data
                                             // Use < because pos is still on the length-byte
                                             if (pos + currentPacket.pefl < pesEndOffset) {
                                                // If this is a VC-1 Packet, then jump to the pes_extension_2 data block, because there is the substream_id
                                                // Otherwise jump BEHIND that section and assume the the substream_id (if any) is the following byte
                                                if (currentPacket.stream_id == 0xFD) {
                                                   pos += 1;
                                                } else {
                                                   pos += 1 + currentPacket.pefl;
                                                }
                                             } else throw new PESParserException("Packet header too small for pes_extension_2 data at: 0x" + Integer.toHexString(pos - offset).toUpperCase());
                                          } else throw new PESParserException("Packet header too small for pes_extension_2 at: 0x" + Integer.toHexString(pos - offset).toUpperCase());
                                       } // End if pes_extension_2 decision
                                    } else throw new PESParserException("Packet header too small for pes_extension_1 at: 0x" + Integer.toHexString(pos - offset).toUpperCase());
                                 } // End if pes_extension_1 decision
                              } else throw new PESParserException("Packet header size exceeds packet size at: 0x" + Integer.toHexString(pos - offset).toUpperCase());
                           } // End if of scan mode decision
                        } else throw new PESParserException("Packet too small for extension at: 0x" + Integer.toHexString(pos - offset).toUpperCase());
                     }
                     // Set substream_id only for private stream 1, private stream 2, VC-1 video stream
                     if (currentPacket.stream_id == 0xBD || currentPacket.stream_id == 0xBF || currentPacket.stream_id == 0xFD) {
                        // Check if packet is big enough for substream_id
                        // Use < to because we are reading from pos
                        if (pos < endOffset) {
                           // The current byte is the substream_id
                           currentPacket.substream_id = ByteArray.getUByte(pack, pos);
                           currentPacket.substream_idOffset = pos;
                           // Advance to the first payload byte
                           // Note: for private stream 1 and 2 this is actually the second real payload byte
                           pos += 1;
                        } else throw new PESParserException("Packet too small for substream_id at: 0x" + Integer.toHexString(pos - offset).toUpperCase());
                     }
                     currentPacket.payloadOffset = pos;
                     // If scrambled, jump to end end of the pack, otherwise jump to the start of the next packet (there may be no remaining packets)
                     if (isScrambledImpl(currentPacket)) {
                        pos = endOffset;
                     } else {
                        pos = currentPacket.packetOffset + 6 + currentPacket.length;
                     }
                     //System.out.println("New pos: " + pos);
                  } else throw new PESParserException("Packet size exceeds pack size at: 0x" + Integer.toHexString(pos - offset).toUpperCase());
               } else {
                  // Fix for Elephants Dream and maybe others: NAV_PCK has no PCI_PKT, the section is all zero
                  // No Packet Startcode found behind the last packet, check if we are in a 0 padded section
                  for (int searchPointer = pos; searchPointer < endOffset; searchPointer++) {
                     //System.out.println("Offset: " + searchPointer + " is: " + pack[searchPointer]);
                     if (pack[searchPointer] != 0x00) {
                        // Check if there is enough space for a Packet Header, otherwise throw an Exception
                        //System.out.println("Advancement: " + (searchPointer - pos) + ", free space: " + (endOffset - pos));
                        if (searchPointer - pos >= 2 && endOffset - searchPointer >= 6) {
                           // Jump to positions back to the start of a possible packet
                           pos = searchPointer - 2;
                           break;
                        } else throw new PESParserException("Invalid packet start code at: 0x" + Integer.toHexString(pos - offset).toUpperCase());
                     }
                  }
               }
            } // End while packet searching loop
         } else throw new PESParserException(String.format("Invalid pack start code and /or pack identifier: 0x%1$08X", ByteArray.getVarLong(pack, offset, 4)));
      } else throw new PESParserException("Pack data too small");
   }

   public String toString() {
      StringBuffer idString = new StringBuffer(64);
      idString.append("PES-Pack :: ");
      // Go through all packets
      for (int i = 0; i < packetCount; i++) {
         PESPacket currentPacket = packets.get(i);
         idString.append("stream_id: ");
         idString.append(S_ID_NAMES[currentPacket.stream_id]);
         // For private stream 1 use the corresponding lut
         if (currentPacket.stream_id == 0xBD) {
            idString.append(", substream_id: ");
            idString.append(PS1_ID_NAMES[currentPacket.substream_id]);
         } else {
            // For private stream 2 use the corresponding lut
            if (currentPacket.stream_id == 0xBF) {
               idString.append(", substream_id: ");
               idString.append(PS2_ID_NAMES[currentPacket.substream_id]);
            } else {
               // For VC-1 stream the name is hardcoded
               if (currentPacket.stream_id == 0xFD) {
                  if (currentPacket.substream_id == 0x55) {
                     idString.append("_FOR_MAIN");
                  } else {
                     if (currentPacket.substream_id == 0x56) {
                        idString.append("_FOR_SUB");
                     } else {
                        idString.append(", stream_id_extension: 0x");
                        idString.append(Integer.toHexString(currentPacket.substream_id).toUpperCase());
                        idString.append(" (INVALID)");
                     }
                  }
               }
            }
         }
         // If this is not the last packet add a separator
         if (i + 1 < packetCount) {
            idString.append(" :: ");
         }
      }
      return idString.toString();
   }

   /**
    * Returnes the scramble state of the pack.
    * This method is safe to call on every type of pack.
    * 
    * @return If true, this pack is scrambled, otherwise not
    */
   public boolean isScrambled() {
      return isScrambledImpl(packets.get(0));
   }

   /**
    * This method actually determines the scramble state.
    * 
    * @param packet This packet is checked for its scramble state
    * @return If true, the packet is scrambled, otherwise not
    */
   private boolean isScrambledImpl(PESPacket packet) {
      // If packet has an extension, use the pes_scrambling_control bit
      if (packet.hasExtension) {
         return (((pack[packet.packetOffset + 6] >>> 4) & 0x03) != 0);
      } else {
         // Otherwise decide by pack type
         // Assume every pack unencrypted except a HL_PCK
         return isHlPckImpl(packet);
      }
   }

   /**
    * Sets the scrambled state of the pack.
    * This method is safe to call on a pack which has no scramble bit, nothing gets changed in that case.
    * 
    * @param scrambled The new scrambled state
    */
   public void setScrambled(boolean scrambled) {
      PESPacket tmp = packets.get(0);
      // Change scramble bit only if the first packet has an extension
      if (tmp.hasExtension) {
         pack[tmp.packetOffset + 6] = (byte)(pack[tmp.packetOffset + 6] & 0xCF);
         if (scrambled) {
            pack[tmp.packetOffset + 6] = (byte)(pack[tmp.packetOffset + 6] | 0x10);
         }
      }
   }

   /**
    * Returns true if this is a NV_PCK.
    * This method is safe to call on every type of pack. 
    * 
    * @return If true, this is a NV_PCK, otherwise not
    */
   public boolean isNavPck() {
      // An NV_PCK contains a system header packet and a GCI, PCI, and DSI packet, the last each in a private stream 2
      // Fix for Elephants Dream and maybe others: the NV_PCK contains only a system header packet and a GCI and DSI packet, the last two each in a private stream 2
      if (packetCount == 3) {
         PESPacket gci = packets.get(1);
         PESPacket dsi = packets.get(2);
         if (packets.get(0).stream_id == 0xBB && gci.stream_id == 0xBF && gci.substream_id == 0x04 && dsi.stream_id == 0xBF && dsi.substream_id == 0x01) {
            return true;
         }
      } else {
         if (packetCount == 4) {
            PESPacket gci = packets.get(1);
            PESPacket pci = packets.get(2);
            PESPacket dsi = packets.get(3);
            if (packets.get(0).stream_id == 0xBB && gci.stream_id == 0xBF && gci.substream_id == 0x04 && pci.stream_id == 0xBF && pci.substream_id == 0x00 && dsi.stream_id == 0xBF && dsi.substream_id == 0x01) {
               return true;
            }
         }
      }
      return false;
   }

   /**
    * Returns true if this is an ADV_PCK.
    * This method is safe to call on every type of pack. 
    * 
    * @return If true, this is a ADV_PCK, otherwise not
    */
   public boolean isAdvPck() {
      PESPacket tmp = packets.get(0);
      // An ADV_PCK is in a private stream 2 with substream_id 0x80
      if (tmp.stream_id == 0xBF && tmp.substream_id == 0x80) {
         return true;
      } else {
         return false;
      }
   }

   /**
    * Returns true if this is a HL_PCK.
    * This method is safe to call on every type of pack. 
    * 
    * @return If true, this is a HL_PCK, otherwise not
    */
   public boolean isHlPck() {
      return isHlPckImpl(packets.get(0));
   }

   /**
    * This method actually determines if the given packet is a HL_PCK.
    * 
    * @param packet The packet to check if it is a HL_PCK
    * @return If true, the packet is a HL_PCK, otherwise not
    */
   private boolean isHlPckImpl(PESPacket packet) {
      // A HL_PCK is in a private stream 2 with substream_id 0x08
      if (packet.stream_id == 0xBF && packet.substream_id == 0x08) {
         return true;
      } else {
         return false;
      }
   }

   /**
    * Returns the number of packets in this pack.
    * 
    * @return The number of packets in this pack
    */
   public int packetCount() {
      return packetCount;
   }

   /**
    * Returns the requested packet, counting starts at 0.
    * 
    * @param index The packet to return
    * @return The packet, null if the packet index is invalid
    */
   public PESPacket getPacket(int index) {
      if (index >= 0 && index < packetCount) {
         return packets.get(index);
      } else return null;
   }

   /**
    * Pads the pack starting after the given packet index, counting starts at 0. To pad the whole pack use index -1.
    * Padding after the last packet is allowed, but if the pack structure is correct it fills already the complete pack length.
    * 
    * The physical pack structure will be correct after this method call, but the information about the pack in this class will be wrong,
    * the pack MUST be parsed again if methods of this class are used after this method call.
    * 
    * @param index The index of the packet after which the padding should start
    * @return True, if the pack was padded, false if not (wrong packet index)
    */
   public boolean pad(int index) {
      // Check if the index is valid
      if (index >= -1 && index < packetCount) {
         int paddingOffset = 0;
         if (index == -1) {
            // Pad the whole packet, start AT the offset of the first packet
            // TODO: What if the first packet does not start behind the pack header? Is this allowed?
            paddingOffset = packets.get(0).packetOffset; 
         } else {
            // Pad after the given packet, start BEHIND the offset of that packet
            PESPacket padPacket = packets.get(0);
            paddingOffset = padPacket.packetOffset + 6 + padPacket.length;
         }
         int paddingEndOffset = packOffset + PESPack.PACK_LENGTH; 
         int padAmount = paddingEndOffset - paddingOffset;
         if (padAmount < 6) {
            // Remaining space to small for a padding packet, pad with zero
            Arrays.fill(pack, paddingOffset, paddingEndOffset, (byte)0x00);
         } else {
            // Enough space remaining for a padding packet, insert it
            ByteArray.setInt(pack, paddingOffset, 0x000001BE);
            ByteArray.setShort(pack, paddingOffset + 4, padAmount - 6);
            Arrays.fill(pack, paddingOffset + 6, paddingEndOffset, (byte)0xFF);
         }
         return true;
      } else return false;
   }


   /**
    * This class represents a PES Packet and provides methods to access and modify some of its attributes. This class works like a mask,
    * no data is copied, it works directly on the underlying byte array. However, some values are buffered for faster access.
    * 
    * The set methods usually only set that specific attribute they are intended for, they do set other dependend attributes if not noted explicit, e.g.
    * setting the StreamID of a packet that has no Header Extension to a StreamID that has a Header Extension does not add a Header Extension.
    * Therefore the methods which check for a specific attribute, e.g. the Header Extension, may return a value not expected for the present Packet type after
    * some attributes of it have been changed. 
    * 
    * TODO: Provide methods to access the Header Extension fields?
    *  
    * @author KenD00 
    */
   public final class PESPacket {

      /**
       * Start offset of this packet in the underlying pack byte array
       */
      private int packetOffset = 0;

      /**
       * Offset of the stream_id
       */
      private int stream_idOffset = 0;
      /**
       * Offset of the substream_id
       */
      private int substream_idOffset = 0;
      /**
       * Stream_id of the packet
       */
      private int stream_id = 0;
      /**
       * Substream_id of the packet
       */
      private int substream_id = -1;

      /**
       * Length of this packet
       */
      private int length = 0;
      /**
       * Offset to the payload of the packet. This may not be the start of the PES Packet payload
       * (e.g. for Private streams this is the second byte of the PES Packet Payload) 
       */
      private int payloadOffset = 0;

      /**
       * If true, this packet has a header extension, otherwise not
       */
      private boolean hasExtension = false;
      // The values of the header extension (without header data for that section)
      //private int psc = 0;
      //private int pp = 0;
      //private int dai = 0;
      //private int c = 0;
      //private int coo = 0;
      private int ptsdts = 0;
      private int escr = 0;
      private int esr = 0;
      private int aci = 0;
      private int pescrc = 0;
      private int pesext1 = 0;

      /**
       * Offset of the pes_extension_1 header
       */
      private int pesext1Offset = 0;
      // The values of the pes_extension_1 (without header data for that section)
      private int pespd = 0;
      private int phf = 0;
      private int ppsc = 0;
      private int pstdb = 0;
      private int pesext2 = 0;

      /**
       * Offset of the pes_extension_2 header
       */
      private int pesext2Offset = 0;
      // The values of the pes_extension_2 (wihtout header data for that section)
      private int pefl = 0;


      /**
       * Private constructor to avoid instantiation from outside.
       */
      private PESPacket() {
         // Nothing
      }

      /**
       * Returns the StreamID of the packet.
       * 
       * @return The StreamID of the packet
       */
      public int getStreamId() {
         return stream_id;
      }

      /**
       * Sets the StreamID of the packet.
       * 
       * @param stream_id The new StreamID
       */
      public void setStreamId(int stream_id) {
         this.stream_id = stream_id; 
         ByteArray.setByte(pack, stream_idOffset, stream_id);
      }

      /**
       * Returns true if the packet has a header extension.
       * 
       * @return If true, the packet has a header extension, otherwise not
       */
      public boolean hasExtension() {
         return hasExtension;
      }

      /**
       * Returns true if the packet has a SubstreamID.
       * 
       * @return If true, the packet has a SubstreamID, otherwise not
       */
      public boolean hasSubstreamId() {
         return (substream_idOffset != 0);
      }

      /**
       * Returns the SubstreamID of the packet.
       * If this packet does not have a SubstreamID -1 is returned
       * 
       * @return The SubstreamID of the packet, -1 if this packet does not have a SubstreamID
       */
      public int getSubstreamId() {
         return substream_id;
      }

      /**
       * Sets the SubstreamID of the packet.
       * If this packet does not have a SubstreamID nothing is changed.
       * 
       * @param substream_id The new SubstreamID
       * @return True, if the SubstreamID was changed, false otherwise
       */
      public boolean setSubstreamId(int substream_id) {
         if (substream_idOffset != 0) {
            this.substream_id = substream_id;
            ByteArray.setByte(pack, substream_idOffset, substream_id);
            return true;
         } else return false;
      }

      /**
       * Returns the length of the packet. The length count starts just behind the PES Packet Header, it includes a possible present Header Extension!
       * @return Length of the packet excluding the PES Packet Header
       */
      public int length() {
         return length;
      }

      /**
       * Offset to the payload. In case of Private Streams this is actually one byte behind the PES Packet Payload start because the first byte is the SubstreamID.
       *  
       * @return Offset from the start of this packet to the start of the payload
       */
      public int payloadOffset() {
         return payloadOffset;
      }

      /**
       * The length of the payload.
       * 
       * @return The length of the payload
       */
      public int getPayloadLength() {
         return packetOffset + 6 + length - payloadOffset;
      }

      /**
       * Sets the length of the payload of this packet.
       * The length must be grather or equal than 0 and the PES Pack must not exceed the PES Pack length with this new payload length.
       * These constraints are checked, the length is not modified if they are not met. This method destroys the structure of the PES Pack if the
       * new payload length does not extend the packet to the end of the pack, this must be fixed externally!
       * 
       * @param payloadLength The new payload length
       * @return True, if the payload length was changed, false otherwise
       */
      public boolean setPayloadLength(int payloadLength) {
         // Check if the new payloadLength is positive and the packet would not exceed the Pack
         if (payloadLength >= 0 && payloadOffset + payloadLength <= packOffset + PESPack.PACK_LENGTH) {
            // payloadOffset must not start just behind the Pack Header, so determine the difference and add it to the length
            length = payloadLength + payloadOffset - (packetOffset + 6);
            ByteArray.setShort(pack, packetOffset + 4, length);
            return true;
         } else return false;
      }

   }

}
