#include "ts_stream.h"
#include "defines.h"
#include "h264.h"
#include "h265.h"
#include "mp4_generic.h"
#include "nal.h"
#include <sys/stat.h>
#include <stdint.h>
#include "mpeg.h"


namespace TS{

  void ADTSRemainder::setRemainder(const aac::adts &p, const void *source, uint32_t avail, uint64_t bPos){
    if (!p.getCompleteSize()){return;}

    if (max < p.getCompleteSize()){
      void *newmainder = realloc(data, p.getCompleteSize());
      if (newmainder){
        max = p.getCompleteSize();
        data = (char *)newmainder;
      }
    }
    if (max >= p.getCompleteSize()){
      len = p.getCompleteSize();
      now = avail;
      bpos = bPos;
      memcpy(data, source, now);
    }
  }

  void ADTSRemainder::append(const char *p, uint32_t pLen){
    if (now + pLen > len){
      FAIL_MSG("Data to append does not fit into the remainder");
      return;
    }

    memcpy(data + now, p, pLen);
    now += pLen;
  }

  bool ADTSRemainder::isComplete(){return (len == now);}

  void ADTSRemainder::clear(){
    len = 0;
    now = 0;
    bpos = 0;
  }

  ADTSRemainder::ADTSRemainder(){
    data = 0;
    max = 0;
    now = 0;
    len = 0;
    bpos = 0;
  }
  ADTSRemainder::~ADTSRemainder(){
    if (data){
      free(data);
      data = 0;
    }
  }

  uint64_t ADTSRemainder::getLength(){return len;}

  uint64_t ADTSRemainder::getBpos(){return bpos;}

  uint64_t ADTSRemainder::getTodo(){return len - now;}
  char *ADTSRemainder::getData(){return data;}

  Stream::Stream(bool _threaded){
    threaded = _threaded;
    psCache = 0;
    psCacheTid = 0;
  }

  Stream::~Stream(){
  }

  void Stream::parse(char * newPack, uint64_t bytePos) {
    Packet newPacket;
    newPacket.FromPointer(newPack);
    parse(newPacket, bytePos);
  }

  void Stream::partialClear(){
    tthread::lock_guard<tthread::recursive_mutex> guard(tMutex);
    pesStreams.clear();
    psCacheTid = 0;
    psCache = 0;
    pesPositions.clear();
    outPackets.clear();
    buildPacket.clear();
    seenUnitStart.clear();
    lastms.clear();
    rolloverCount.clear();
  }

  void Stream::clear(){
    tthread::lock_guard<tthread::recursive_mutex> guard(tMutex);
    partialClear();
    pidToCodec.clear();
    adtsInfo.clear();
    spsInfo.clear();
    ppsInfo.clear();
    hevcInfo.clear();
    metaInit.clear();
    descriptors.clear();
    mappingTable.clear();
    lastPMT.clear();
    lastPAT = 0;
    pmtTracks.clear();
    remainders.clear();
    associationTable = ProgramAssociationTable();
  }

  void Stream::finish(){
    tthread::lock_guard<tthread::recursive_mutex> guard(tMutex);
    if (!pesStreams.size()){return;}

    for (std::map<size_t, std::deque<Packet> >::const_iterator i = pesStreams.begin();
         i != pesStreams.end(); i++){
      parsePES(i->first, true);
    }
  }

  void Stream::add(char * newPack, uint64_t bytePos) {
    Packet newPacket;
    newPacket.FromPointer(newPack);
    add(newPacket, bytePos);
  }

  void Stream::add(Packet & newPack, uint64_t bytePos) {
    tthread::lock_guard<tthread::recursive_mutex> guard(tMutex);
    uint32_t tid = newPack.getPID();
    bool unitStart = newPack.getUnitStart();
    static uint32_t wantPrev = 0;
    bool wantTrack = ((wantPrev == tid) || (tid == 0 || newPack.isPMT() || pidToCodec.count(tid)));
    if (!wantTrack){return;}
    if (psCacheTid != tid || !psCache){
      psCache = &(pesStreams[tid]);
      psCacheTid = tid;
    }
    if (unitStart || !psCache->empty()){
      wantPrev = tid;
      psCache->push_back(newPack);
      if (unitStart){
        pesPositions[tid].push_back(bytePos);
        ++(seenUnitStart[tid]);
      }
    }
  }

  bool Stream::isDataTrack(size_t tid) const {
    if (tid == 0){return false;}
    {
      tthread::lock_guard<tthread::recursive_mutex> guard(tMutex);
      return !pmtTracks.count(tid);
    }
  }

  void Stream::parse(size_t tid){
    tthread::lock_guard<tthread::recursive_mutex> guard(tMutex);
    if (!pesStreams.count(tid) || pesStreams[tid].size() == 0){
      return;
    }
    if (psCacheTid != tid || !psCache){
      psCache = &(pesStreams[tid]);
      psCacheTid = tid;
    }

    // Handle PAT packets
    if (tid == 0){
      ///\todo Keep track of updates in PAT instead of keeping only the last PAT as a reference

      associationTable = psCache->back();
      associationTable.parsePIDs();
      lastPAT = Util::bootSecs();

      size_t pmtCount = associationTable.getProgramCount();
      for (size_t i = 0; i < pmtCount; i++){pmtTracks.insert(associationTable.getProgramPID(i));}

      pesStreams.erase(0);
      psCacheTid = 0;
      psCache = 0;
      return;
    }

    // Ignore conditional access packets. We don't care.
    if (tid == 1){return;}

    // Handle PMT packets
    if (pmtTracks.count(tid)){
      ///\todo Keep track of updates in PMT instead of keeping only the last PMT per program as a
      /// reference
      mappingTable[tid] = psCache->back();
      lastPMT[tid] = Util::bootSecs();
      ProgramMappingEntry entry = mappingTable[tid].getEntry(0);
      while (entry){
        uint32_t pid = entry.getElementaryPid();
        uint32_t sType = entry.getStreamType();
        switch (sType){
        case H264:
        case AAC:
        case H265:
        case AC3:
        case ID3:
        case MP2:
        case MPEG2:
          pidToCodec[pid] = sType;
          if (sType == ID3){
            metaInit[pid] = std::string(entry.getESInfo(), entry.getESInfoLength());
          }
          break;
          default: break;
        }
        entry.advance();
      }

      pesStreams.erase(tid);
      psCacheTid = 0;
      psCache = 0;
      return;
    }

    if (!pidToCodec.count(tid)){
      return; // skip unknown codecs
    }

    while(seenUnitStart[tid] > 1) {
      parsePES(tid);
    }
  }

  void Stream::parse(Packet & newPack, uint64_t bytePos) {
    add(newPack, bytePos);
    if (newPack.getUnitStart()){
      parse(newPack.getPID());
    }
  }

  bool Stream::hasPacketOnEachTrack() const{
    tthread::lock_guard<tthread::recursive_mutex> guard(tMutex);
    if (!pidToCodec.size()){
      // INFO_MSG("no packet on each track 1, pidtocodec.size: %d, outpacket.size: %d",
      // pidToCodec.size(), outPackets.size());
      return false;
    }
    size_t missing = 0;
    uint64_t firstTime = 0xffffffffffffffffull, lastTime = 0;
    for (std::map<size_t, uint32_t>::const_iterator it = pidToCodec.begin();
         it != pidToCodec.end(); it++){
      if (!hasPacket(it->first) || !outPackets.count(it->first) || !outPackets.at(it->first).size()){
        missing++;
      }else{
        if (outPackets.at(it->first).front().getTime() < firstTime){
          firstTime = outPackets.at(it->first).front().getTime();
        }
        if (outPackets.at(it->first).back().getTime() > lastTime){
          lastTime = outPackets.at(it->first).back().getTime();
        }
      }
    }

    return (!missing || (missing != pidToCodec.size() && lastTime - firstTime > 2000));
  }

  bool Stream::hasPacket(size_t tid) const {
    tthread::lock_guard<tthread::recursive_mutex> guard(tMutex);
    if (psCacheTid != tid && pesStreams.find(tid) == pesStreams.end()){
      return false;
    }
    if (outPackets.count(tid) && outPackets.at(tid).size()){
      return true;
    }
    if (pidToCodec.count(tid) && seenUnitStart.count(tid) && seenUnitStart.at(tid) > 1){
      return true;
    }
    return false;
  }

  bool Stream::hasPacket() const{
    tthread::lock_guard<tthread::recursive_mutex> guard(tMutex);
    if (!pesStreams.size()){
      return false;
    }

    if (outPackets.size()){
      for (std::map<size_t, std::deque<DTSC::Packet> >::const_iterator i =
               outPackets.begin();
           i != outPackets.end(); i++){
        if (i->second.size()){
          return true;
        }
      }
    }

    for (std::map<size_t, uint32_t>::const_iterator i = seenUnitStart.begin();
         i != seenUnitStart.end(); i++){
      if (pidToCodec.count(i->first) && i->second > 1){
        return true;
      }
    }

    return false;
  }

  uint64_t decodePTS(const char * data){
    uint64_t time;
    time = ((data[0] >> 1) & 0x07);
    time <<= 15;
    time |= ((uint32_t)data[1] << 7) | ((data[2] >> 1) & 0x7F);
    time <<= 15;
    time |= ((uint32_t)data[3] << 7) | ((data[4] >> 1) & 0x7F);
    time /= 90;
    return time;
  }

  void Stream::parsePES(size_t tid, bool finished){
    if (!pidToCodec.count(tid)){
      return; // skip unknown codecs
    }
    if (psCacheTid != tid || !psCache){
      psCache = &(pesStreams[tid]);
      psCacheTid = tid;
    }
    if (psCache->size() <= 1){
      if (!finished){FAIL_MSG("No PES packets to parse");}
      return;
    }
    // Find number of packets before unit Start
    size_t packNum = 1;
    std::deque<Packet>::iterator curPack = psCache->begin();

    if (seenUnitStart[tid] == 2 && psCache->begin()->getUnitStart() && psCache->rbegin()->getUnitStart()){
      packNum = psCache->size() - 1;
      curPack = psCache->end();
      curPack--;
    }else{
      curPack++;
      while (curPack != psCache->end() && !curPack->getUnitStart()){
        curPack++;
        packNum++;
      }
    }
    if (!finished && curPack == psCache->end()){
      FAIL_MSG("No PES packets to parse (%" PRIu32 ")", seenUnitStart[tid]);
      return;
    }
    
    // We now know we're deleting 1 UnitStart, so we can pop the pesPositions and lower the seenUnitStart counter.
    --(seenUnitStart[tid]);
    std::deque<uint64_t> &inPositions = pesPositions[tid];
    uint64_t bPos = inPositions.front();
    inPositions.pop_front();

    // Create a buffer for the current PES, and remove it from the pesStreams buffer.
    uint32_t paySize = 0;

    // Loop over the packets we need, and calculate the total payload size
    curPack = psCache->begin();
    int lastCtr = curPack->getContinuityCounter() - 1;
    for (size_t i = 0; i < packNum; i++){
      if (curPack->getContinuityCounter() == lastCtr){
        curPack++;
        continue;
      }
      lastCtr = curPack->getContinuityCounter();
      paySize += curPack->getPayloadLength();
      curPack++;
    }
    VERYHIGH_MSG("Parsing PES for track %zu, length %" PRIu32, tid, paySize);
    // allocate a buffer, do it all again, but this time also copy the data bytes over to char*
    // payload
    char *payload = (char *)malloc(paySize);
    if(!payload){
      FAIL_MSG("cannot allocate PES packet!");
      return;
    }

    paySize = 0;
    curPack = psCache->begin();
    lastCtr = curPack->getContinuityCounter() - 1;
    for (int i = 0; i < packNum; i++){
      if (curPack->getContinuityCounter() == lastCtr){
        curPack++;
        continue;
      }
      if (curPack->getContinuityCounter() - lastCtr != 1 && curPack->getContinuityCounter()){
        INFO_MSG("Parsing PES on track %zu, missed %d packets", tid,
                 curPack->getContinuityCounter() - lastCtr - 1);
      }
      lastCtr = curPack->getContinuityCounter();
      memcpy(payload + paySize, curPack->getPayload(), curPack->getPayloadLength());
      paySize += curPack->getPayloadLength();
      curPack++;
    }
    psCache->erase(psCache->begin(), curPack);
    // we now have the whole PES packet in char* payload, with a total size of paySize (including
    // headers)

    // Parse the PES header
    uint32_t offset = 0;

    while (offset < paySize){
      const char *pesHeader = payload + offset;

      // Check for large enough buffer
      if ((paySize - offset) < 9 || (paySize - offset) < 9 + pesHeader[8]){
        INFO_MSG("Not enough data on track %zu (%d / %d), discarding remainder of data", tid,
                 paySize - offset, 9 + pesHeader[8]);
        break;
      }

      // Check for valid PES lead-in
      if (pesHeader[0] != 0 || pesHeader[1] != 0x00 || pesHeader[2] != 0x01){
        INFO_MSG("Invalid PES Lead in on track %zu, discarding it", tid);
        break;
      }

      // Read the payload size.
      // Note: if the payload size is 0, then we assume the pes packet will cover the entire TS
      // Unit.
      // Note: this is technically only allowed for video pes streams.
      uint64_t realPayloadSize = Bit::btohs(pesHeader + 4);
      if (!realPayloadSize){
        realPayloadSize = paySize; // PES header size already included here
      }else{
        realPayloadSize += 6; // add the PES header size, always 6 bytes
      }
      // realPayloadSize is now the whole packet
      // We substract PES_header_data_length, plus the 9 bytes of mandatory header bytes
      realPayloadSize -= (9 + pesHeader[8]);


      // Read the metadata for this PES Packet
      ///\todo Determine keyframe-ness
      uint64_t timeStamp = 0;
      int64_t timeOffset = 0;
      uint64_t pesOffset = 9;       // mandatory headers
      if ((pesHeader[7] >> 6) & 0x02){// Check for PTS presence
        timeStamp = decodePTS(pesHeader + pesOffset);
        pesOffset += 5;
        if (((pesHeader[7] & 0xC0) >> 6) &
            0x01){// Check for DTS presence (yes, only if PTS present)
          timeOffset = timeStamp;
          timeStamp = decodePTS(pesHeader + pesOffset);
          pesOffset += 5;
          if (timeStamp > timeOffset){
            WARN_MSG("TS packet invalid: DTS > PTS. Ignoring DTS value.");
            timeStamp = timeOffset;
          }else{
            timeOffset -= timeStamp;
          }
        }
      }

      timeStamp += (rolloverCount[tid] * TS_PTS_ROLLOVER);

      if ((timeStamp < lastms[tid]) && ((timeStamp % TS_PTS_ROLLOVER) < 0.1 * TS_PTS_ROLLOVER) && ((lastms[tid] % TS_PTS_ROLLOVER) > 0.9 * TS_PTS_ROLLOVER)){
        ++rolloverCount[tid];
        timeStamp += TS_PTS_ROLLOVER;
      }


      if (pesHeader[7] & 0x20){// ESCR - ignored
        pesOffset += 6;
      }
      if (pesHeader[7] & 0x10){// ESR - ignored
        pesOffset += 3;
      }
      if (pesHeader[7] & 0x08){// trick mode - ignored
        pesOffset += 1;
      }
      if (pesHeader[7] & 0x04){// additional copy - ignored
        pesOffset += 1;
      }
      if (pesHeader[7] & 0x02){// crc - ignored
        pesOffset += 2;
      }

      if (paySize - offset - pesOffset < realPayloadSize){
        WARN_MSG("Packet loss detected (%" PRIu64 " != %" PRIu64 "), glitches will occur", paySize-offset-pesOffset, realPayloadSize);
        realPayloadSize = paySize - offset - pesOffset;
      }

      const char *pesPayload = pesHeader + pesOffset;
      parseBitstream(tid, pesPayload, realPayloadSize, timeStamp, timeOffset, bPos, pesHeader[6] & 0x04 );
      lastms[tid] = timeStamp;

      // Shift the offset by the payload size, the mandatory headers and the optional
      // headers/padding
      offset += realPayloadSize + (9 + pesHeader[8]);
    }
    free(payload);
  }

  void Stream::setLastms(size_t tid, uint64_t timestamp){
    lastms[tid] = timestamp;
    rolloverCount[tid] = timestamp / TS_PTS_ROLLOVER;
  }

  void Stream::parseBitstream(size_t tid, const char *pesPayload, uint64_t realPayloadSize,
                              uint64_t timeStamp, int64_t timeOffset, uint64_t bPos, bool alignment){

    // Create a new (empty) DTSC Packet at the end of the buffer
    unsigned long thisCodec = pidToCodec[tid];
    std::deque<DTSC::Packet> & out = outPackets[tid];
    if (thisCodec == AAC){
      // Parse all the ADTS packets
      uint64_t offsetInPes = 0;
      uint64_t msRead = 0;

      if (remainders.count(tid) && remainders[tid].getLength()){
        offsetInPes = std::min(remainders[tid].getTodo(), realPayloadSize);
        remainders[tid].append(pesPayload, offsetInPes);

        if (remainders[tid].isComplete()){
          aac::adts adtsPack(remainders[tid].getData(), remainders[tid].getLength());
          if (adtsPack){
            if (!adtsInfo.count(tid) || !adtsInfo[tid].sameHeader(adtsPack)){
              MEDIUM_MSG("Setting new ADTS header: %s", adtsPack.toPrettyString().c_str());
              adtsInfo[tid] = adtsPack;
            }
            out.push_back(DTSC::Packet());
            out.back().genericFill(
                timeStamp - ((adtsPack.getSampleCount() * 1000) / adtsPack.getFrequency()),
                timeOffset, tid, adtsPack.getPayload(), adtsPack.getPayloadSize(),
                remainders[tid].getBpos(), 0);
          }
          remainders[tid].clear();
        }
      }
      while (offsetInPes < realPayloadSize){
        aac::adts adtsPack(pesPayload + offsetInPes, realPayloadSize - offsetInPes);
        if (adtsPack && adtsPack.getCompleteSize() + offsetInPes <= realPayloadSize){
          if (!adtsInfo.count(tid) || !adtsInfo[tid].sameHeader(adtsPack)){
            DONTEVEN_MSG("Setting new ADTS header: %s", adtsPack.toPrettyString().c_str());
            adtsInfo[tid] = adtsPack;
          }
          out.push_back(DTSC::Packet());
          if (adtsPack.getPayloadSize()){
            out.back().genericFill(timeStamp + msRead, timeOffset, tid,
                                               adtsPack.getPayload(), adtsPack.getPayloadSize(), bPos,
                                               0);
            offsetInPes += adtsPack.getCompleteSize();
            msRead += (adtsPack.getSampleCount() * 1000) / adtsPack.getFrequency();
          }else{
            offsetInPes++;
          }
        }else{
          /// \todo What about the case that we have an invalid start, going over the PES boundary?
          if (!adtsPack){
            offsetInPes++;
          }else{
            // remainder, keep it, use it next time
            remainders[tid].setRemainder(adtsPack, pesPayload + offsetInPes,
                                         realPayloadSize - offsetInPes, bPos);
            offsetInPes = realPayloadSize; // skip to end of PES
          }
        }
      }
    }
    if (thisCodec == ID3 || thisCodec == AC3 || thisCodec == MP2){
      out.push_back(DTSC::Packet());
      out.back().genericFill(timeStamp, timeOffset, tid, pesPayload, realPayloadSize,
                                         bPos, 0);
      if (thisCodec == MP2 && !mp2Hdr.count(tid)){
        mp2Hdr[tid] = std::string(pesPayload, realPayloadSize);
      }

    }

    if (thisCodec == H264 || thisCodec == H265){
      const char *nextPtr;
      const char *pesEnd = pesPayload+realPayloadSize;
      bool isKeyFrame = false;
      uint32_t nalSize = 0;



      nextPtr = nalu::scanAnnexB(pesPayload, realPayloadSize);
      if (!nextPtr){
        nextPtr = pesEnd;
        nalSize = realPayloadSize;
        if(!alignment && timeStamp && buildPacket.count(tid) && timeStamp != buildPacket[tid].getTime()){
          FAIL_MSG("No startcode in packet @ %" PRIu64 " ms, and time is not equal to %" PRIu64 " ms so can't merge", timeStamp, buildPacket[tid].getTime());
          return;
        }
        DTSC::Packet & bp = buildPacket[tid];
        if (alignment){
          // If the timestamp differs from current PES timestamp, send the previous packet out and
          // fill a new one.
          if (bp.getTime() != timeStamp){
            // Add the finished DTSC packet to our output buffer
            out.push_back(bp);

            size_t size;
            char * tmp ;
            bp.getString("data", tmp, size);

            INFO_MSG("buildpacket: size: %zu, timestamp: %" PRIu64, size, bp.getTime())

            // Create a new empty packet with the key frame bit set to true
            bp.null();
            bp.genericFill(timeStamp, timeOffset, tid, 0, 0, bPos, true);
            bp.setKeyFrame(false);
          }
        
          // Check if this is a keyframe
          parseNal(tid, pesPayload, nextPtr, isKeyFrame);
          // If yes, set the keyframe flag
          if (isKeyFrame){
            bp.setKeyFrame(true);
          }

          // No matter what, now append the current NAL unit to the current packet
          bp.appendNal(pesPayload, nalSize);
        }else{
          bp.upgradeNal(pesPayload, nalSize);
          return;
        }
      }

      while (nextPtr < pesEnd){
        if (!nextPtr){nextPtr = pesEnd;}
        //Calculate size of NAL unit, removing null bytes from the end
        nalSize = nalu::nalEndPosition(pesPayload, nextPtr - pesPayload) - pesPayload;

        if (nalSize){
          // If we don't have a packet yet, init an empty packet with the key frame bit set to true
          if (!buildPacket.count(tid)){
            buildPacket[tid].genericFill(timeStamp, timeOffset, tid, 0, 0, bPos, true);
            buildPacket[tid].setKeyFrame(false);
          }
          DTSC::Packet & bp = buildPacket[tid];

          // Check if this is a keyframe
          parseNal(tid, pesPayload, nextPtr, isKeyFrame);
          // If yes, set the keyframe flag
          if (isKeyFrame){
            bp.setKeyFrame(true);
          }

          // If the timestamp differs from current PES timestamp, send the previous packet out and
          // fill a new one.
          if (bp.getTime() != timeStamp){
            // Add the finished DTSC packet to our output buffer
            out.push_back(bp);
            bp.null();
            bp.genericFill(timeStamp, timeOffset, tid, 0, 0, bPos, true);
            bp.setKeyFrame(false);
          }
          // No matter what, now append the current NAL unit to the current packet
          bp.appendNal(pesPayload, nalSize);
        }

        if (((nextPtr - pesPayload) + 3) >= realPayloadSize){return;}//end of the line
        
        realPayloadSize -= ((nextPtr - pesPayload) + 3); // decrease the total size
        pesPayload = nextPtr + 3;

        nextPtr = nalu::scanAnnexB(pesPayload, realPayloadSize);
      }
    }
    if (thisCodec == MPEG2){
      const char *origBegin = pesPayload;
      size_t origSize = realPayloadSize;
      const char *nextPtr;
      const char *pesEnd = pesPayload+realPayloadSize;

      bool isKeyFrame = false;

      nextPtr = nalu::scanAnnexB(pesPayload, realPayloadSize);
      if (!nextPtr){
        WARN_MSG("No start code found in entire PES packet!");
        return;
      }

      uint32_t nalno = 0;
      //We only check the first 8 packets, because keys should always be near the front of a PES.
      while (nextPtr < pesEnd && nalno < 8){
        if (!nextPtr){nextPtr = pesEnd;}
        //Calculate size of NAL unit, removing null bytes from the end
        nalu::nalEndPosition(pesPayload, nextPtr - pesPayload);

        // Check if this is a keyframe
        parseNal(tid, pesPayload, nextPtr, isKeyFrame);
        ++nalno;

        if (((nextPtr - pesPayload) + 3) >= realPayloadSize){break;}//end of the loop
        realPayloadSize -= ((nextPtr - pesPayload) + 3); // decrease the total size
        pesPayload = nextPtr + 3;
        nextPtr = nalu::scanAnnexB(pesPayload, realPayloadSize);
      }
      out.push_back(DTSC::Packet());
      out.back().genericFill(timeStamp, timeOffset, tid, origBegin, origSize, bPos, isKeyFrame);
    }
  }

  void Stream::getPacket(size_t tid, DTSC::Packet & pack) {
    tthread::lock_guard<tthread::recursive_mutex> guard(tMutex);
    pack.null();
    if (!hasPacket(tid)){
      ERROR_MSG("Trying to obtain a packet on track %zu, but no full packet is available", tid);
      return;
    }

    bool packetReady = outPackets.count(tid) && outPackets[tid].size();

    if (!packetReady){
      parse(tid);
      packetReady = outPackets.count(tid) && outPackets[tid].size();
    }

    if (!packetReady){
      ERROR_MSG("Track %lu: PES without valid packets?", tid);
      return;
    }

    pack = outPackets[tid].front();
    outPackets[tid].pop_front();

    if (!outPackets[tid].size()){outPackets.erase(tid);}

  }

  void Stream::parseNal(size_t tid, const char *pesPayload, const char *nextPtr, bool &isKeyFrame){
    bool firstSlice = true;
    char typeNal;

    if (pidToCodec[tid] == MPEG2){
      typeNal = pesPayload[0];
      switch (typeNal){
        case 0xB3:
          if (!mpeg2SeqHdr.count(tid)){
            mpeg2SeqHdr[tid] = std::string(pesPayload, (nextPtr - pesPayload));
          }
          break;
        case 0xB5:
          if (!mpeg2SeqExt.count(tid)){
            mpeg2SeqExt[tid] = std::string(pesPayload, (nextPtr - pesPayload));
          }
          break;
        case 0xB8:
          isKeyFrame = true;
          break;
      }
      return;
    }

    isKeyFrame = false;
    if (pidToCodec[tid] == H264){
      typeNal = pesPayload[0] & 0x1F;
      switch (typeNal){
      case 0x01:{
        if (firstSlice){
          firstSlice = false;
          if (!isKeyFrame){
            Utils::bitstream bs;
            for (size_t i = 1; i < 10 && i < (nextPtr - pesPayload); i++){
              if (i + 2 < (nextPtr - pesPayload) &&
                  (memcmp(pesPayload + i, "\000\000\003", 3) == 0)){// Emulation prevention bytes
                bs.append(pesPayload + i, 2);
                i += 2;
              }else{
                bs.append(pesPayload + i, 1);
              }
            }
            bs.getExpGolomb(); // Discard first_mb_in_slice
            uint64_t sliceType = bs.getUExpGolomb();
            if (sliceType == 2 || sliceType == 4 || sliceType == 7 || sliceType == 9){
              isKeyFrame = true;
            }
          }
        }
        break;
      }
      case 0x05:{
        isKeyFrame = true;
        break;
      }
      case 0x07:{
        spsInfo[tid] = std::string(pesPayload, (nextPtr - pesPayload));
        break;
      }
      case 0x08:{
        ppsInfo[tid] = std::string(pesPayload, (nextPtr - pesPayload));
        break;
      }
      default: break;
      }
    }else if (pidToCodec[tid] == H265){
      typeNal = (((pesPayload[0] & 0x7E) >> 1) & 0xFF);
      switch (typeNal){
      case 2:
      case 3: // TSA Picture
      case 4:
      case 5: // STSA Picture
      case 6:
      case 7: // RADL Picture
      case 8:
      case 9: // RASL Picture
      case 16:
      case 17:
      case 18: // BLA Picture
      case 19:
      case 20:   // IDR Picture
      case 21:{// CRA Picture
        isKeyFrame = true;
        break;
      }
      case 32:
      case 33:
      case 34:{
        tthread::lock_guard<tthread::recursive_mutex> guard(tMutex);
        hevcInfo[tid].addUnit(std::string(pesPayload, nextPtr - pesPayload)); // may i convert to (char *)?
        break;
      }
      default: break;
      }
    }
  }

  uint32_t Stream::getEarliestPID(){
    tthread::lock_guard<tthread::recursive_mutex> guard(tMutex);

    uint64_t packTime = 0xFFFFFFFFull;
    uint32_t packTrack = 0;

    for (std::map<size_t, std::deque<DTSC::Packet> >::iterator it = outPackets.begin();
         it != outPackets.end(); it++){
      if (it->second.front().getTime() < packTime){
        packTrack = it->first;
        packTime = it->second.front().getTime();
      }
    }

    return packTrack;
  }

  void Stream::getEarliestPacket(DTSC::Packet &pack){
    tthread::lock_guard<tthread::recursive_mutex> guard(tMutex);
    pack.null();

    uint64_t packTime = 0xFFFFFFFFull;
    uint64_t packTrack = 0;

    for (std::map<size_t, std::deque<DTSC::Packet> >::iterator it = outPackets.begin();
         it != outPackets.end(); it++){
      if (it->second.size() && it->second.front().getTime() < packTime){
        packTrack = it->first;
        packTime = it->second.front().getTime();
      }
    }

    if (packTrack){getPacket(packTrack, pack);}
  }

  void Stream::initializeMetadata(DTSC::Meta &meta, size_t tid, size_t mappingId){
    tthread::lock_guard<tthread::recursive_mutex> guard(tMutex);

    size_t mId = mappingId;
    
    for (std::map<size_t, uint32_t>::const_iterator it = pidToCodec.begin();
         it != pidToCodec.end(); it++){
      if (tid && it->first != tid){continue;}

      if (mId == 0){mId = it->first;}

      if (meta.tracks.count(mId) && meta.tracks[mId].codec.size()){continue;}

      switch (it->second){
      case H264:{
        if (!spsInfo.count(it->first) || !ppsInfo.count(it->first)){
          MEDIUM_MSG("Aborted meta fill for h264 track %lu: no SPS/PPS", it->first);
          continue;
        }
        meta.tracks[mId].type = "video";
        meta.tracks[mId].codec = "H264";
        meta.tracks[mId].trackID = mId;
        std::string tmpBuffer = spsInfo[it->first];
        h264::sequenceParameterSet sps(spsInfo[it->first].data(), spsInfo[it->first].size());
        h264::SPSMeta spsChar = sps.getCharacteristics();
        meta.tracks[mId].width = spsChar.width;
        meta.tracks[mId].height = spsChar.height;
        meta.tracks[mId].fpks = spsChar.fps * 1000;
        MP4::AVCC avccBox;
        avccBox.setVersion(1);
        avccBox.setProfile(spsInfo[it->first][1]);
        avccBox.setCompatibleProfiles(spsInfo[it->first][2]);
        avccBox.setLevel(spsInfo[it->first][3]);
        avccBox.setSPSCount(1);
        avccBox.setSPS(spsInfo[it->first]);
        avccBox.setPPSCount(1);
        avccBox.setPPS(ppsInfo[it->first]);
        meta.tracks[mId].init = std::string(avccBox.payload(), avccBox.payloadSize());
      }break;
      case H265:{
        if (!hevcInfo.count(it->first) || !hevcInfo[it->first].haveRequired()){
          MEDIUM_MSG("Aborted meta fill for hevc track %lu: no info nal unit", it->first);
          continue;
        }
        meta.tracks[mId].type = "video";
        meta.tracks[mId].codec = "HEVC";
        meta.tracks[mId].trackID = mId;
        meta.tracks[mId].init = hevcInfo[it->first].generateHVCC();
        h265::metaInfo metaInfo = hevcInfo[it->first].getMeta();
        meta.tracks[mId].width = metaInfo.width;
        meta.tracks[mId].height = metaInfo.height;
        meta.tracks[mId].fpks = metaInfo.fps * 1000;
        int pmtCount = associationTable.getProgramCount();
        for (int i = 0; i < pmtCount; i++){
          int pid = associationTable.getProgramPID(i);
          ProgramMappingEntry entry = mappingTable[pid].getEntry(0);
          while (entry){
            if (entry.getElementaryPid() == tid){
              meta.tracks[mId].lang =
                  ProgramDescriptors(entry.getESInfo(), entry.getESInfoLength()).getLanguage();
            }
            entry.advance();
          }
        }
      }break;
      case MPEG2:{
        meta.tracks[mId].type = "video";
        meta.tracks[mId].codec = "MPEG2";
        meta.tracks[mId].trackID = mId;
        meta.tracks[mId].init = std::string("\000\000\001", 3) + mpeg2SeqHdr[it->first] + std::string("\000\000\001", 3) + mpeg2SeqExt[it->first];

        Mpeg::MPEG2Info info = Mpeg::parseMPEG2Header(meta.tracks[mId].init);
        meta.tracks[mId].width = info.width;
        meta.tracks[mId].height = info.height;
        meta.tracks[mId].fpks = info.fps * 1000;
      }break;
      case ID3:{
        meta.tracks[mId].type = "meta";
        meta.tracks[mId].codec = "ID3";
        meta.tracks[mId].trackID = mId;
        meta.tracks[mId].init = metaInit[it->first];
      }break;
      case AC3:{
        meta.tracks[mId].type = "audio";
        meta.tracks[mId].codec = "AC3";
        meta.tracks[mId].trackID = mId;
        meta.tracks[mId].size = 16;
        ///\todo Fix these 2 values
        meta.tracks[mId].rate = 0;
        meta.tracks[mId].channels = 0;
      }break;
      case MP2:{
        meta.tracks[mId].type = "audio";
        meta.tracks[mId].codec = "MP2";
        meta.tracks[mId].trackID = mId;

        Mpeg::MP2Info info = Mpeg::parseMP2Header(mp2Hdr[it->first]);
        meta.tracks[mId].rate = info.sampleRate;
        meta.tracks[mId].channels = info.channels;

        ///\todo Fix this value
        meta.tracks[mId].size = 0;
      }break;
      case AAC:{
        meta.tracks[mId].type = "audio";
        meta.tracks[mId].codec = "AAC";
        meta.tracks[mId].trackID = mId;
        meta.tracks[mId].size = 16;
        meta.tracks[mId].rate = adtsInfo[it->first].getFrequency();
        meta.tracks[mId].channels = adtsInfo[it->first].getChannelCount();
        char audioInit[2]; // 5 bits object type, 4 bits frequency index, 4 bits channel index
        audioInit[0] = ((adtsInfo[it->first].getAACProfile() & 0x1F) << 3) |
                       ((adtsInfo[it->first].getFrequencyIndex() & 0x0E) >> 1);
        audioInit[1] = ((adtsInfo[it->first].getFrequencyIndex() & 0x01) << 7) |
                       ((adtsInfo[it->first].getChannelConfig() & 0x0F) << 3);
        meta.tracks[mId].init = std::string(audioInit, 2);
      }break;
      }

      size_t pmtCount = associationTable.getProgramCount();
      for (size_t i = 0; i < pmtCount; i++){
        uint32_t pid = associationTable.getProgramPID(i);
        ProgramMappingEntry entry = mappingTable[pid].getEntry(0);
        while (entry){
          if (entry.getElementaryPid() == tid){
            meta.tracks[mId].lang =
                ProgramDescriptors(entry.getESInfo(), entry.getESInfoLength()).getLanguage();
          }
          entry.advance();
        }
      }
      MEDIUM_MSG("Initialized track %lu as %s %s", it->first, meta.tracks[mId].codec.c_str(),
                 meta.tracks[mId].type.c_str());
    }
  }

  std::set<size_t> Stream::getActiveTracks(){
    tthread::lock_guard<tthread::recursive_mutex> guard(tMutex);
    std::set<size_t> result;
    // Track 0 is always active
    result.insert(0);
    // IF PAT updated in the last 5 seconds, check for contents
    if (Util::bootSecs() - lastPAT < 5){
      size_t pmtCount = associationTable.getProgramCount();
      // For each PMT
      for (size_t i = 0; i < pmtCount; i++){
        size_t pid = associationTable.getProgramPID(i);
        // Add PMT track
        result.insert(pid);
        // IF PMT updated in last 5 seconds, check for contents
        if (Util::bootSecs() - lastPMT[pid] < 5){
          ProgramMappingEntry entry = mappingTable[pid].getEntry(0);
          // Add all tracks in PMT
          while (entry){
            switch (entry.getStreamType()){
            case H264:
            case AAC:
            case H265:
            case AC3:
            case ID3:
            case MP2:
            case MPEG2:
              result.insert(entry.getElementaryPid()); break;
            default: break;
            }
            entry.advance();
          }
        }
      }
    }
    return result;
  }

  void Stream::eraseTrack(size_t tid){
    tthread::lock_guard<tthread::recursive_mutex> guard(tMutex);
    pesStreams.erase(tid);
    psCacheTid = 0;
    psCache = 0;
    pesPositions.erase(tid);
    outPackets.erase(tid);
  }
}