/****************************************************************************
** libmatroska : parse Matroska files, see http://www.matroska.org/
**
** <file/class description>
**
** Copyright (C) 2002-2004 Steve Lhomme.  All rights reserved.
**
** This library is free software; you can redistribute it and/or
** modify it under the terms of the GNU Lesser General Public
** License as published by the Free Software Foundation; either
** version 2.1 of the License, or (at your option) any later version.
**
** This library 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
** Lesser General Public License for more details.
**
** You should have received a copy of the GNU Lesser General Public
** License along with this library; if not, write to the Free Software
** Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
**
** See http://www.gnu.org/licenses/lgpl-2.1.html for LGPL licensing information.**
** Contact license@matroska.org if any conditions of this licensing are
** not clear to you.
**
**********************************************************************/

/*!
    \file
    \version \$Id$
    \brief Test reading of the Header
    \author Steve Lhomme     <robux4 @ users.sf.net>
*/

#include <iostream>
#include <cassert>
#include <cinttypes>

#include <ebml/EbmlHead.h>
#include <ebml/EbmlSubHead.h>
#include <ebml/EbmlStream.h>
#include <ebml/EbmlContexts.h>
#include <ebml/EbmlVoid.h>
#include <ebml/EbmlCrc32.h>
#include "matroska/FileKax.h"
#include "matroska/KaxSegment.h"
#include "matroska/KaxContexts.h"
#include "matroska/KaxTracks.h"
#include "matroska/KaxInfoData.h"
#include "matroska/KaxCluster.h"
#include "matroska/KaxBlockData.h"
#include "matroska/KaxSeekHead.h"
#include "matroska/KaxCuesData.h"
#include <ebml/StdIOCallback.h>

using namespace libmatroska;
using namespace std;

#define NO_DISPLAY_DATA
//#define JUMP_TEST

/*!
    \note you can generate the file used in this example using test6.cpp
  \todo the blocks with "titi" and "tetetete" don't seem to be read !
*/
int main(int argc, char **argv)
{
    try {
  StdIOCallback Cluster_file((argc>=2 ? argv[1] : "muxed.mkv"), MODE_READ);

  // create the Matroska file
///  FileKax MuxedFile(Cluster_file);
//  bool bAllowDummy = false; // don't read elements we don't know
  bool bAllowDummy = true; // even read elements we don't know (needed for CRC checking)

  // read the EBML head
  EbmlStream aStream(Cluster_file);
  EbmlElement * ElementLevel0;
  EbmlElement * ElementLevel1;
  EbmlElement * ElementLevel2;
  EbmlElement * ElementLevel3 = NULL;
  EbmlElement * ElementLevel4 = NULL;

///  MuxedFile.ReadHead();
  // find the EBML head in the file
  ElementLevel0 = aStream.FindNextID(EbmlHead::ClassInfos, 0xFFFFFFFFL);
  if (ElementLevel0 != NULL)
  {
    printf("EBML : ");
    for (unsigned int i=0; i<EbmlId(*ElementLevel0).Length; i++)
    {
      printf("[%02X]", (EbmlId(*ElementLevel0).Value >> (8*(3-i))) & 0xFF);
    }
    printf("\n");

    ElementLevel0->SkipData(aStream, EbmlHead_Context);
    if (ElementLevel0 != NULL)
      delete ElementLevel0;
  }

  int UpperElementLevel = 0;
  KaxSegment * Segment;
  KaxInfo * SegmentInfo;
  KaxTrackEntry * TrackAudio;
  KaxTrackEntry * TrackVideo;
  KaxCluster *SegmentCluster;
  KaxCues *CuesEntry;
  KaxSeekHead *MetaSeek;
  KaxChapters *Chapters;
  KaxTags *AllTags;
  uint64 TimecodeScale = 1000000;

  // find the segment to read
  ElementLevel0 = aStream.FindNextID(KaxSegment::ClassInfos, 0xFFFFFFFFL);
  if (ElementLevel0 != NULL)
  {
    if (EbmlId(*ElementLevel0) == KaxSegment::ClassInfos.GlobalId) {
      Segment = static_cast<KaxSegment*>(ElementLevel0);
//      MuxedFile.ReadTracks();
//      MuxedFile.ReadCodec();
      // scan the file for a Tracks element (all previous Level1 elements are discarded)
      ElementLevel1 = aStream.FindNextElement(ElementLevel0->Generic().Context, UpperElementLevel, 0, bAllowDummy);

      while (ElementLevel1 != NULL) {
        if (UpperElementLevel > 0) {
          break;
        }
        if (UpperElementLevel < 0) {
          UpperElementLevel = 0;
        }

        /// \todo switch the type of the element to check if it's one we want to handle, like attachements
        if (EbmlId(*ElementLevel1) == EbmlVoid::ClassInfos.GlobalId) {
          printf("\n- Void found\n");
        } else if (EbmlId(*ElementLevel1) == KaxTracks::ClassInfos.GlobalId) {
          // found the Tracks element
          printf("\n- Segment Tracks found\n");
          // handle the data in Tracks here.
          // poll for new tracks and handle them
          ElementLevel2 = aStream.FindNextElement(ElementLevel1->Generic().Context, UpperElementLevel, 0, bAllowDummy);

          while (ElementLevel2 != NULL) {
            if (UpperElementLevel > 0) {
              break;
            }
            if (UpperElementLevel < 0) {
              UpperElementLevel = 0;
            }
            /// \todo switch the type of the element to check if it's one we want to handle, like attachements
            if (EbmlId(*ElementLevel2) == KaxTrackEntry::ClassInfos.GlobalId) {
              printf("* Found a track\n");

              ElementLevel3 = aStream.FindNextElement(ElementLevel2->Generic().Context, UpperElementLevel, 0, bAllowDummy);
              while (ElementLevel3 != NULL) {
                if (UpperElementLevel > 0) {
                  break;
                }
                if (UpperElementLevel < 0) {
                  UpperElementLevel = 0;
                }
                // read the data we care about in a track
                // Track number
                if (EbmlId(*ElementLevel3) == KaxTrackNumber::ClassInfos.GlobalId) {
                  KaxTrackNumber & TrackNum = *static_cast<KaxTrackNumber*>(ElementLevel3);
                  TrackNum.ReadData(aStream.I_O());
                  printf("Track # %d\n", uint8(TrackNum));
                }

                // Track type
                else if (EbmlId(*ElementLevel3) == KaxTrackType::ClassInfos.GlobalId) {
                  KaxTrackType & TrackType = *static_cast<KaxTrackType*>(ElementLevel3);
                  TrackType.ReadData(aStream.I_O());
                  printf("Track type : ");
                  switch(uint8(TrackType))
                  {
                  case track_audio:
                    printf("Audio");
                    TrackAudio = static_cast<KaxTrackEntry *>(ElementLevel2);
                    TrackAudio->SetGlobalTimecodeScale(TimecodeScale);
                    break;
                  case track_video:
                    printf("Video");
                    TrackVideo = static_cast<KaxTrackEntry *>(ElementLevel2);
                    TrackVideo->SetGlobalTimecodeScale(TimecodeScale);
                    break;
                  default:
                    printf("unknown");
                  }
                  printf("\n");
                }

                else if (EbmlId(*ElementLevel3) == KaxTrackFlagLacing::ClassInfos.GlobalId) {
                  printf("Flag Lacing\n");
                }
                else if (EbmlId(*ElementLevel3) == KaxCodecID::ClassInfos.GlobalId) {
                  KaxCodecID & CodecID = *static_cast<KaxCodecID*>(ElementLevel3);
                  CodecID.ReadData(aStream.I_O());
                  printf("Codec ID   : %s\n", string(CodecID).c_str());
                }

                if (UpperElementLevel > 0) {
                  assert(0 == 1); // impossible to be here ?
                  UpperElementLevel--;
                  delete ElementLevel2;
                  ElementLevel2 = ElementLevel3;
                  if (UpperElementLevel > 0)
                    break;
                } else {
                  ElementLevel3->SkipData(aStream, ElementLevel3->Generic().Context);
                  delete ElementLevel3;

                  ElementLevel3 = aStream.FindNextElement(ElementLevel2->Generic().Context, UpperElementLevel, 0, bAllowDummy);
                }
              }
            }
            if (UpperElementLevel > 0) {
              UpperElementLevel--;
              delete ElementLevel2;
              ElementLevel2 = ElementLevel3;
              if (UpperElementLevel > 0)
                break;
            } else {
              ElementLevel2->SkipData(aStream, ElementLevel2->Generic().Context);
              delete ElementLevel2;

              ElementLevel2 = aStream.FindNextElement(ElementLevel1->Generic().Context, UpperElementLevel, 0, bAllowDummy);
            }
          }
        }

        else if (EbmlId(*ElementLevel1) == KaxInfo::ClassInfos.GlobalId) {
          printf("\n- Segment Informations found\n");
          SegmentInfo = static_cast<KaxInfo *>(ElementLevel1);

#ifdef JUMP_TEST
          // test jumping to a random location and find the next Level 0 / Level 1 element
          aStream.I_O().setFilePointer(79, seek_current);
          printf("Seeked at position 0x%X in the file", aStream.I_O().getFilePointer());
          int LowLevel = 1;
          ElementLevel2 = aStream.FindNextElement(KaxSegment::ClassInfos.Context, LowLevel, 0, false); // search up to one level lower
          UpperElementLevel = LowLevel;
#else // JUMP_TEST
          // read the data we care about in matroska
          /// \todo There should be a way to get the default values of the elements not defined
          ElementLevel2 = aStream.FindNextElement(ElementLevel1->Generic().Context, UpperElementLevel, 0, bAllowDummy);
          while (ElementLevel2 != NULL) {
            if (UpperElementLevel > 0) {
              break;
            }
            if (UpperElementLevel < 0) {
              UpperElementLevel = 0;
            }
            if (EbmlId(*ElementLevel2) == KaxTimecodeScale::ClassInfos.GlobalId) {
              KaxTimecodeScale *TimeScale = static_cast<KaxTimecodeScale*>(ElementLevel2);
              TimeScale->ReadData(aStream.I_O());
              printf("Timecode Scale %d\n", uint32(*TimeScale));
              TimecodeScale = uint64(*TimeScale);
            } else if (EbmlId(*ElementLevel2) == KaxDuration::ClassInfos.GlobalId) {
              printf("Segment duration\n");
            } else if (EbmlId(*ElementLevel2) == KaxDateUTC::ClassInfos.GlobalId) {
              printf("Date UTC\n");
            } else if (EbmlId(*ElementLevel2) == KaxTitle::ClassInfos.GlobalId) {
              printf("Title\n");
            } else if (EbmlId(*ElementLevel2) == KaxMuxingApp::ClassInfos.GlobalId) {
              KaxMuxingApp *pApp = static_cast<KaxMuxingApp*>(ElementLevel2);
              pApp->ReadData(aStream.I_O());
              printf("Muxing App : %ls\n", UTFstring(*pApp).c_str());
            } else if (EbmlId(*ElementLevel2) == KaxWritingApp::ClassInfos.GlobalId) {
              KaxWritingApp *pApp = static_cast<KaxWritingApp*>(ElementLevel2);
              pApp->ReadData(aStream.I_O());
              printf("Writing App : %ls (����)\n", UTFstring(*pApp).c_str());
            }

            if (UpperElementLevel > 0) {
              UpperElementLevel--;
              delete ElementLevel2;
              ElementLevel2 = ElementLevel3;
              if (UpperElementLevel > 0)
                break;
            } else {
              ElementLevel2->SkipData(aStream, ElementLevel2->Generic().Context);
              delete ElementLevel2;

              ElementLevel2 = aStream.FindNextElement(ElementLevel1->Generic().Context, UpperElementLevel, 0, bAllowDummy);
            }
          }
#endif // JUMP_TEST
        }

        else if (EbmlId(*ElementLevel1) == KaxCluster::ClassInfos.GlobalId) {
          printf("\n- Segment Clusters found\n");
          SegmentCluster = static_cast<KaxCluster *>(ElementLevel1);
//          SegmentCluster->ClearElement();
          uint32 ClusterTimecode;
          EbmlCrc32 *pChecksum = NULL;
          uint32 SizeInCrc;
          uint64 CrcPositionStart = 0;

#ifdef MEMORY_READ // read the Cluster in memory and then extract elements from memory
          SegmentCluster->Read(aStream, KaxCluster::ClassInfos.Context, UpperElementLevel, ElementLevel2, bAllowDummy);
          if (SegmentCluster->CheckMandatory()) {
            printf("  * All mandatory elements found *\n");
          } else {
            printf("  * Some mandatory elements ar missing !!! *\n");
          }

          // display the elements read
          unsigned int Index0;
          for (Index0 = 0; Index0<SegmentCluster->ListSize() ;Index0++) {
            printf(" - found %s\n", (*SegmentCluster)[Index0]->Generic().DebugName);
          }
#else // not MEMORY_READ
          // read blocks and discard the ones we don't care about
          ElementLevel2 = aStream.FindNextElement(ElementLevel1->Generic().Context, UpperElementLevel, 0, bAllowDummy);
          while (ElementLevel2 != NULL) {
            if (UpperElementLevel > 0) {
              break;
            }
            if (UpperElementLevel < 0) {
              UpperElementLevel = 0;
            }
            if (EbmlId(*ElementLevel2) == KaxClusterTimecode::ClassInfos.GlobalId) {
              printf("Cluster timecode found\n");
              KaxClusterTimecode & ClusterTime = *static_cast<KaxClusterTimecode*>(ElementLevel2);
              ClusterTime.ReadData(aStream.I_O());
              ClusterTimecode = uint32(ClusterTime);
              SegmentCluster->InitTimecode(ClusterTimecode, TimecodeScale);
            } else  if (EbmlId(*ElementLevel2) == KaxBlockGroup::ClassInfos.GlobalId) {
              printf("Block Group found\n");
#ifdef TEST_BLOCKGROUP_READ
              KaxBlockGroup & aBlockGroup = *static_cast<KaxBlockGroup*>(ElementLevel2);
//              aBlockGroup.ClearElement();
              // Extract the valuable data from the Block

              aBlockGroup.Read(aStream, KaxBlockGroup::ClassInfos.Context, UpperElementLevel, ElementLevel3, bAllowDummy);
              KaxBlock * DataBlock = static_cast<KaxBlock *>(aBlockGroup.FindElt(KaxBlock::ClassInfos));
              if (DataBlock != NULL) {
//                DataBlock->ReadData(aStream.I_O());
                DataBlock->SetParent(*SegmentCluster);
                printf("   Track # %d / %d frame%s / Timecode %" PRIu64 "\n",DataBlock->TrackNum(), DataBlock->NumberFrames(), (DataBlock->NumberFrames() > 1)?"s":"", DataBlock->GlobalTimecode());
              } else {
                printf("   A BlockGroup without a Block !!!");
              }
              KaxBlockDuration * BlockDuration = static_cast<KaxBlockDuration *>(aBlockGroup.FindElt(KaxBlockDuration::ClassInfos));
              if (BlockDuration != NULL) {
                printf("  Block Duration %d scaled ticks : %" PRIu32 " ns\n", uint32(*BlockDuration), uint32(*BlockDuration) * TimecodeScale);
              }
              KaxReferenceBlock * RefTime = static_cast<KaxReferenceBlock *>(aBlockGroup.FindElt(KaxReferenceBlock::ClassInfos));
              if (RefTime != NULL) {
                printf("  Reference frame at scaled (%" PRId32 ") timecode " PRId32 "\n", int32(*RefTime), int32(int64(*RefTime) * TimecodeScale));
              }
#else // TEST_BLOCKGROUP_READ
              // read the data we care about in matroska
              /// \todo There should be a way to get the default values of the elements not defined
              ElementLevel3 = aStream.FindNextElement(ElementLevel2->Generic().Context, UpperElementLevel, 0, bAllowDummy);
              while (ElementLevel3 != NULL) {
                if (UpperElementLevel > 0) {
                  break;
                }
                if (UpperElementLevel < 0) {
                  UpperElementLevel = 0;
                }
                if (EbmlId(*ElementLevel3) == KaxBlock::ClassInfos.GlobalId) {
                  printf(" Block Data\n");
                  KaxBlock & DataBlock = *static_cast<KaxBlock*>(ElementLevel3);
#ifdef NO_DISPLAY_DATA
                  DataBlock.ReadData(aStream.I_O(), SCOPE_PARTIAL_DATA);
#else // NO_DISPLAY_DATA
                  DataBlock.ReadData(aStream.I_O(), SCOPE_ALL_DATA);
#endif // NO_DISPLAY_DATA
                  DataBlock.SetParent(*SegmentCluster);
                  printf("   Track # %d / %d frame%s / Timecode %" PRIu64 "\n",DataBlock.TrackNum(), DataBlock.NumberFrames(), (DataBlock.NumberFrames() > 1)?"s":"", DataBlock.GlobalTimecode());
#ifndef NO_DISPLAY_DATA
                  for (unsigned int i=0; i< DataBlock.NumberFrames(); i++) {
                    printf("   [%s]\n",DataBlock.GetBuffer(i).Buffer()); // STRING ONLY POSSIBLE WITH THIS PARTICULAR EXAMPLE (the binary data is a string)
                  }
#endif // NO_DISPLAY_DATA
//                  printf("Codec ID   : %s\n", &binary(CodecID)); // strings for the moment (example)
                } else if (EbmlId(*ElementLevel3) == KaxBlockVirtual::ClassInfos.GlobalId) {
                  printf(" Virtual Block\n");
                } else if (EbmlId(*ElementLevel3) == KaxReferenceVirtual::ClassInfos.GlobalId) {
                  printf("  virtual Reference\n");
                } else if (EbmlId(*ElementLevel3) == KaxReferencePriority::ClassInfos.GlobalId) {
                  printf("  Reference priority\n");
                } else if (EbmlId(*ElementLevel3) == KaxReferenceBlock::ClassInfos.GlobalId) {
                  KaxReferenceBlock & RefTime = *static_cast<KaxReferenceBlock*>(ElementLevel3);
                  RefTime.ReadData(aStream.I_O());
                  printf("  Reference frame at scaled (%d) timecode %" PRId32 "\n", int32(RefTime), int32(int64(RefTime) * TimecodeScale));
                } else if (EbmlId(*ElementLevel3) == KaxBlockDuration::ClassInfos.GlobalId) {
                  KaxBlockDuration & BlockDuration = *static_cast<KaxBlockDuration*>(ElementLevel3);
                  BlockDuration.ReadData(aStream.I_O());
                  printf("  Block Duration %" PRIu32 " scaled ticks : %" PRIu64 " ns\n", uint32(BlockDuration), uint32(BlockDuration) * TimecodeScale);
                }
                if (UpperElementLevel > 0) {
                  UpperElementLevel--;
                  delete ElementLevel3;
                  ElementLevel3 = ElementLevel4;
                  if (UpperElementLevel > 0)
                    break;
                } else {
                  ElementLevel3->SkipData(aStream, ElementLevel3->Generic().Context);

                  ElementLevel3 = aStream.FindNextElement(ElementLevel2->Generic().Context, UpperElementLevel, 0, bAllowDummy);
                }
              }
#endif // TEST_BLOCKGROUP_READ
            } else if (EbmlId(*ElementLevel2) == EbmlCrc32::ClassInfos.GlobalId) {
              printf("Cluster CheckSum !\n");
              pChecksum = static_cast<EbmlCrc32*>(ElementLevel2);
              pChecksum->ReadData(aStream.I_O());
              SegmentCluster->ForceChecksum( pChecksum->GetCrc32() ); // not use later
              SizeInCrc = 0;
              CrcPositionStart = aStream.I_O().getFilePointer();
            }

            if (UpperElementLevel > 0) {
              UpperElementLevel--;
              delete ElementLevel2;
              ElementLevel2 = ElementLevel3;
              if (UpperElementLevel > 0)
                break;
            } else {
              ElementLevel2->SkipData(aStream, ElementLevel2->Generic().Context);
              if (ElementLevel2 != pChecksum)
                delete ElementLevel2;

              ElementLevel2 = aStream.FindNextElement(ElementLevel1->Generic().Context, UpperElementLevel, 0, bAllowDummy);
            }
          }
#endif // not MEMORY_READ
          if (pChecksum != NULL) {
            EbmlCrc32 ComputedChecksum;
            uint64 CurrPosition = aStream.I_O().getFilePointer();
            uint64 CrcPositionEnd = ElementLevel2->GetElementPosition();
            binary *SupposedBufferInCrc = new binary [CrcPositionEnd - CrcPositionStart];
            aStream.I_O().setFilePointer(CrcPositionStart);
            aStream.I_O().readFully(SupposedBufferInCrc, CrcPositionEnd - CrcPositionStart);
            aStream.I_O().setFilePointer(CurrPosition);
            ComputedChecksum.FillCRC32(SupposedBufferInCrc, CrcPositionEnd - CrcPositionStart);
            delete [] SupposedBufferInCrc;
            if (pChecksum->GetCrc32() == ComputedChecksum.GetCrc32()) {
              printf(" ++ CheckSum verification succeeded ++");
            } else {
              printf(" ++ CheckSum verification FAILED !!! ++");
            }
            delete pChecksum;
            pChecksum = NULL;
          }
        }
        else if (EbmlId(*ElementLevel1) == KaxCues::ClassInfos.GlobalId) {
          printf("\n- Cue entries found\n");
          CuesEntry = static_cast<KaxCues *>(ElementLevel1);
          CuesEntry->SetGlobalTimecodeScale(TimecodeScale);
          // read everything in memory
          CuesEntry->Read(aStream, KaxCues::ClassInfos.Context, UpperElementLevel, ElementLevel2, bAllowDummy); // build the entries in memory
          if (CuesEntry->CheckMandatory()) {
            printf("  * All mandatory elements found *\n");
          } else {
            printf("  * Some mandatory elements ar missing !!! *\n");
          }
          CuesEntry->Sort();
          // display the elements read
          unsigned int Index0;
          for (Index0 = 0; Index0<CuesEntry->ListSize() ;Index0++) {
            if ((*CuesEntry)[Index0]->Generic().GlobalId == KaxCuePoint::ClassInfos.GlobalId) {
              printf(" Cue Point\n");

              KaxCuePoint & CuePoint = *static_cast<KaxCuePoint *>((*CuesEntry)[Index0]);
              unsigned int Index1;
              for (Index1 = 0; Index1<CuePoint.ListSize() ;Index1++) {
                if (CuePoint[Index1]->Generic().GlobalId == KaxCueTime::ClassInfos.GlobalId) {
                  KaxCueTime & CueTime = *static_cast<KaxCueTime *>(CuePoint[Index1]);
                  printf("  Time %" PRIu64 "\n", uint64(CueTime) * TimecodeScale);
                } else if (CuePoint[Index1]->Generic().GlobalId == KaxCueTrackPositions::ClassInfos.GlobalId) {
                  KaxCueTrackPositions & CuePos = *static_cast<KaxCueTrackPositions *>(CuePoint[Index1]);
                  printf("  Positions\n");

                  unsigned int Index2;
                  for (Index2 = 0; Index2<CuePos.ListSize() ;Index2++) {
                    if (CuePos[Index2]->Generic().GlobalId == KaxCueTrack::ClassInfos.GlobalId) {
                      KaxCueTrack & CueTrack = *static_cast<KaxCueTrack *>(CuePos[Index2]);
                      printf("   Track %d\n", uint16(CueTrack));
                    } else if (CuePos[Index2]->Generic().GlobalId == KaxCueClusterPosition::ClassInfos.GlobalId) {
                      KaxCueClusterPosition & CuePoss = *static_cast<KaxCueClusterPosition *>(CuePos[Index2]);
                      printf("   Cluster position %" PRIu64 "\n", uint64(CuePoss));
                    } else if (CuePos[Index2]->Generic().GlobalId == KaxCueReference::ClassInfos.GlobalId) {
                      KaxCueReference & CueRefs = *static_cast<KaxCueReference *>(CuePos[Index2]);
                      printf("   Reference\n");

                      unsigned int Index3;
                      for (Index3 = 0; Index3<CueRefs.ListSize() ;Index3++) {
                        if (CueRefs[Index3]->Generic().GlobalId == KaxCueRefTime::ClassInfos.GlobalId) {
                          KaxCueRefTime & CueTime = *static_cast<KaxCueRefTime *>(CueRefs[Index3]);
                          printf("    Time %d\n", uint32(CueTime));
                        } else if (CueRefs[Index3]->Generic().GlobalId == KaxCueRefCluster::ClassInfos.GlobalId) {
                          KaxCueRefCluster & CueClust = *static_cast<KaxCueRefCluster *>(CueRefs[Index3]);
                          printf("    Cluster position %" PRIu64 "\n", uint64(CueClust));
                        } else {
                          printf("    - found %s\n", CueRefs[Index3]->Generic().DebugName);
                        }
                      }
                    } else {
                      printf("   - found %s\n", CuePos[Index2]->Generic().DebugName);
                    }
                  }
                } else {
                  printf("  - found %s\n", CuePoint[Index1]->Generic().DebugName);
                }
              }
            } else {
              printf(" - found %s\n", (*CuesEntry)[Index0]->Generic().DebugName);
            }
          }
        }
        else if (EbmlId(*ElementLevel1) == KaxSeekHead::ClassInfos.GlobalId) {
          printf("\n- Meta Seek found\n");
          MetaSeek = static_cast<KaxSeekHead *>(ElementLevel1);
          // read it in memory
          MetaSeek->Read(aStream, KaxSeekHead::ClassInfos.Context, UpperElementLevel, ElementLevel2, bAllowDummy);
          if (MetaSeek->CheckMandatory()) {
            printf("  * All mandatory elements found *\n");
          } else {
            printf("  * Some mandatory elements ar missing !!! *\n");
          }
          unsigned int Index0;
          for (Index0 = 0; Index0<MetaSeek->ListSize() ;Index0++) {
            if ((*MetaSeek)[Index0]->Generic().GlobalId == KaxSeek::ClassInfos.GlobalId) {
              printf("   Seek Point\n");
              KaxSeek & SeekPoint = *static_cast<KaxSeek *>((*MetaSeek)[Index0]);
              unsigned int Index1;
              for (Index1 = 0; Index1<SeekPoint.ListSize() ;Index1++) {
                if (SeekPoint[Index1]->Generic().GlobalId == KaxSeekID::ClassInfos.GlobalId) {
                  KaxSeekID * SeekID = static_cast<KaxSeekID *>(SeekPoint[Index1]);
                  printf("    Seek ID ");
                  for (unsigned int i=0; i<SeekID->GetSize(); i++) {
                    printf("%02X", SeekID->GetBuffer()[i]);
                  }
                  printf("\n");
                } else if (SeekPoint[Index1]->Generic().GlobalId == KaxSeekPosition::ClassInfos.GlobalId) {
                  KaxSeekPosition * SeekPos = static_cast<KaxSeekPosition *>(SeekPoint[Index1]);
                  printf("    Seek position %d\n", uint32(*SeekPos));
                }
              }
            }
          }
        } else if (EbmlId(*ElementLevel1) == KaxChapters::ClassInfos.GlobalId) {
          printf("\n- Chapters found\n");
          Chapters = static_cast<KaxChapters *>(ElementLevel1);
          // read it in memory
          Chapters->Read(aStream, KaxChapters::ClassInfos.Context, UpperElementLevel, ElementLevel2, bAllowDummy);
          if (Chapters->CheckMandatory()) {
            printf("  * All mandatory elements found *\n");
          } else {
            printf("  * Some mandatory elements ar missing !!! *\n");
          }
          unsigned int Index0;
          for (Index0 = 0; Index0<Chapters->ListSize() ;Index0++) {
            if ((*Chapters)[Index0]->Generic().GlobalId == KaxEditionEntry::ClassInfos.GlobalId) {
              printf("   Edition\n");
              KaxEditionEntry & Edition = *static_cast<KaxEditionEntry *>((*Chapters)[Index0]);
              unsigned int Index2;
              for (Index2 = 0; Index2<Edition.ListSize() ;Index2++) {
                if (Edition[Index2]->Generic().GlobalId == KaxChapterAtom::ClassInfos.GlobalId) {
                  printf("     Chapter Atom\n");
                  KaxChapterAtom & aChapterAtom = *static_cast<KaxChapterAtom *>(Edition[Index2]);
                  unsigned int Index3;
                  for (Index3 = 0; Index3<aChapterAtom.ListSize() ;Index3++) {
                    if (aChapterAtom[Index3]->Generic().GlobalId == KaxChapterUID::ClassInfos.GlobalId) {
                      printf("      Chapter UID 0x%08x\n", uint32(*static_cast<EbmlUInteger *>(aChapterAtom[Index3])) );
                    } else if (aChapterAtom[Index3]->Generic().GlobalId == KaxChapterTimeStart::ClassInfos.GlobalId) {
                      printf("      Time Start %d\n", uint32(*static_cast<EbmlUInteger *>(aChapterAtom[Index3])) );
                    } else if (aChapterAtom[Index3]->Generic().GlobalId == KaxChapterTimeEnd::ClassInfos.GlobalId) {
                      printf("      Time End %d ns\n", uint32(*static_cast<EbmlUInteger *>(aChapterAtom[Index3])) );
                    } else if (aChapterAtom[Index3]->Generic().GlobalId == KaxChapterTrack::ClassInfos.GlobalId) {
                      printf("      Track list\n");
                    } else if (aChapterAtom[Index3]->Generic().GlobalId == KaxChapterDisplay::ClassInfos.GlobalId) {
                      printf("      Display info\n");
                      KaxChapterDisplay & aDisplay = *static_cast<KaxChapterDisplay *>(aChapterAtom[Index3]);
                      unsigned int Index4;
                      for (Index4 = 0; Index4<aDisplay.ListSize() ;Index4++) {
                        if (aDisplay[Index4]->Generic().GlobalId == KaxChapterString::ClassInfos.GlobalId) {
                          printf("       Display \"%ls\"\n", UTFstring(*static_cast<EbmlUnicodeString *>(aDisplay[Index4])).c_str() );
                        } else if (aDisplay[Index4]->Generic().GlobalId == KaxChapterLanguage::ClassInfos.GlobalId) {
                          printf("       For language \"%s\"\n", std::string(*static_cast<EbmlString *>(aDisplay[Index4])).c_str() );
                        } else if (aDisplay[Index4]->Generic().GlobalId == KaxChapterCountry::ClassInfos.GlobalId) {
                          printf("       For country \"%s\"\n", std::string(*static_cast<EbmlString *>(aDisplay[Index4])).c_str() );
                        } else if (aDisplay[Index4]->IsDummy()) {
                          printf("       Dummy !!!\n");
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        } else if (EbmlId(*ElementLevel1) == KaxTags::ClassInfos.GlobalId) {
          printf("\n- Tags found\n");
          AllTags = static_cast<KaxTags *>(ElementLevel1);
          // read it in memory
          AllTags->Read(aStream, KaxTags::ClassInfos.Context, UpperElementLevel, ElementLevel2, bAllowDummy);
          if (AllTags->CheckMandatory()) {
            printf("  * All mandatory elements found *\n");
          } else {
            printf("  * Some mandatory elements ar missing !!! *\n");
          }
          unsigned int Index0;
          for (Index0 = 0; Index0<AllTags->ListSize() ;Index0++) {
            if ((*AllTags)[Index0]->Generic().GlobalId == KaxTag::ClassInfos.GlobalId) {
              printf("   Tag\n");
              KaxTag & TagElt = *static_cast<KaxTag *>((*AllTags)[Index0]);
              unsigned int Index1;
              for (Index1 = 0; Index1<TagElt.ListSize() ;Index1++) {
//                 bool bRemoved = false, bRemovedDone = true;
                if (TagElt[Index1]->Generic().GlobalId == KaxTagTargets::ClassInfos.GlobalId) {
                  printf("    Targets\n");
                  KaxTagTargets & Targets = *static_cast<KaxTagTargets *>(TagElt[Index1]);
                  unsigned int Index2;
                  for (Index2 = 0; Index2<Targets.ListSize() ;Index2++) {
                    if (Targets[Index2]->Generic().GlobalId == KaxTagTrackUID::ClassInfos.GlobalId) {
#ifndef TEST_REMOVE
                      printf("     Track UID\n");
#else // TEST_REMOVE
                      printf("     Track UID (will be removed)\n");
                      /*************** Test to remove an element ***************/
                      Targets.Remove(Index2);
                      bRemoved = true;
                      bRemovedDone = false;
                      Index2--;
                      Index1--;
#endif // TEST_REMOVE
                    } else if (Targets[Index2]->Generic().GlobalId == KaxTagChapterUID::ClassInfos.GlobalId) {
                      printf("     Chapter UID\n");
#if 0
                    } else if (Targets[Index2]->Generic().GlobalId == KaxTagMultiComment::ClassInfos.GlobalId) {
                      printf("     Comment\n");
                      KaxTagMultiComment & Comment = *static_cast<KaxTagMultiComment *>(Targets[Index2]);
                      unsigned int Index3;
                      for (Index3 = 0; Index3<Comment.ListSize() ;Index3++) {
                        if (Comment[Index3]->Generic().GlobalId == KaxTagMultiCommentName::ClassInfos.GlobalId) {
                          KaxTagMultiCommentName & CommentName = *static_cast<KaxTagMultiCommentName *>(Comment[Index3]);
                          printf("      Comment Name \"%s\"\n", std::string(CommentName).c_str());
                        }
                      }
//                    } else if (Targets[Index2]->Generic().GlobalId == DummyRawElement::ClassInfos.GlobalId) {
#endif
                    }
                  }
#ifdef TEST_REMOVE
                  if (bRemoved) {
                    printf("    -- Again After Deletion --\n");
                    bRemoved = false;
                  } else if (bRemovedDone) {
                    printf("    -- Done --\n");
                  }
#endif // TEST_REMOVE
#if 0
                } else if (TagElt[Index1]->Generic().GlobalId == KaxTagGeneral::ClassInfos.GlobalId) {
                  printf("    General\n");
                  KaxTagGeneral & General = *static_cast<KaxTagGeneral *>(TagElt[Index1]);
                  unsigned int Index2;
                  for (Index2 = 0; Index2<General.ListSize() ;Index2++) {
                    if (General[Index2]->Generic().GlobalId == KaxTagSubject::ClassInfos.GlobalId) {
                      printf("     Subject\n");
                    } else if (General[Index2]->Generic().GlobalId == KaxTagBibliography::ClassInfos.GlobalId) {
                      printf("     Bibliography\n");
                    } else if (General[Index2]->Generic().GlobalId == KaxTagLanguage::ClassInfos.GlobalId) {
                      printf("     Language\n");
                    }
                  }
                } else if (TagElt[Index1]->Generic().GlobalId == KaxTagMultiCommercial::ClassInfos.GlobalId) {
                  printf("    MultiCommercial\n");
                  KaxTagMultiCommercial & Commercials = *static_cast<KaxTagMultiCommercial *>(TagElt[Index1]);
                  unsigned int Index2;
                  for (Index2 = 0; Index2<Commercials.ListSize() ;Index2++) {
                    if (Commercials[Index2]->Generic().GlobalId == KaxTagCommercial::ClassInfos.GlobalId) {
                      printf("     Commercial\n");
                      KaxTagCommercial & Commercial = *static_cast<KaxTagCommercial *>(Commercials[Index2]);
                      unsigned int Index3;
                      for (Index3 = 0; Index3<Commercial.ListSize() ;Index3++) {
                        if (Commercial[Index3]->Generic().GlobalId == KaxTagMultiCommercialType::ClassInfos.GlobalId) {
                          printf("      Type\n");
                        } else if (Commercial[Index3]->Generic().GlobalId == KaxTagMultiPrice::ClassInfos.GlobalId) {
                          printf("      Prices\n");
                          KaxTagMultiPrice & Prices = *static_cast<KaxTagMultiPrice *>(Commercial[Index3]);
                          unsigned int Index4;
                          for (Index4 = 0; Index4<Prices.ListSize(); Index4++) {
                            if (Prices[Index4]->Generic().GlobalId == KaxTagMultiPriceCurrency::ClassInfos.GlobalId) {
                              printf("       Currency\n");
                            } else if (Prices[Index4]->Generic().GlobalId == KaxTagMultiPriceAmount::ClassInfos.GlobalId) {
                              printf("       Amount\n");
                            }
                          }
                        }
                      }
                    }
                  }
                } else if (TagElt[Index1]->Generic().GlobalId == KaxTagMultiDate::ClassInfos.GlobalId) {
                  printf("    MultiDate\n");
                } else if (TagElt[Index1]->Generic().GlobalId == KaxTagMultiComment::ClassInfos.GlobalId) {
                  printf("    Comment\n");
                  KaxTagMultiComment & Comment = *static_cast<KaxTagMultiComment *>(TagElt[Index1]);
                  unsigned int Index2;
                  for (Index2 = 0; Index2<Comment.ListSize() ;Index2++) {
                    if (Comment[Index2]->Generic().GlobalId == KaxTagMultiCommentName::ClassInfos.GlobalId) {
                      KaxTagMultiCommentName & CommentName = *static_cast<KaxTagMultiCommentName *>(Comment[Index2]);
                      printf("     Comment Name \"%s\"\n", std::string(CommentName).c_str());
                    }
                  }
#endif
                }
              }
            }
          }
          if (AllTags->HasChecksum()) {
            if (AllTags->VerifyChecksum()) {
              printf(" ++ CheckSum verification succeeded ++\n");
            } else {
              printf(" ++ CheckSum verification FAILED !!! ++\n");
            }
          }
        }

        if (UpperElementLevel > 0) {
          UpperElementLevel--;
          delete ElementLevel1;
          ElementLevel1 = ElementLevel2;
          if (UpperElementLevel > 0)
            break;
        } else {
          ElementLevel1->SkipData(aStream, ElementLevel1->Generic().Context);
          delete ElementLevel1;

          ElementLevel1 = aStream.FindNextElement(ElementLevel0->Generic().Context, UpperElementLevel, 0, bAllowDummy);
        }
      }
    }
  }

#ifdef OLD
  uint8 TrackNumber = MuxedFile.GetTrackNumber();

  TrackInfo *Tracks = new TrackInfo[TrackNumber];
  TrackInfoAudio aAudioTrack;
  TrackInfoVideo aVideoTrack;

  Track * track1 = MuxedFile.GetTrack(1);
  Track * track2 = MuxedFile.GetTrack(2);
  Track * track3 = MuxedFile.GetTrack(3); // should be 0
  // get information about the 1st track

  MuxedFile.Track_GetInfo(track1, Tracks[0]);
  displayTrackInfo(Tracks[0]);

  if (Tracks[0].TrackType == track_audio) {
      MuxedFile.Track_GetInfo_Audio(track1, aAudioTrack);
      displayAudioInfo(aAudioTrack);
  }
  else
      cout << "the expected audio track is not an audio one :(" << endl;

  MuxedFile.Track_GetInfo(track3, Tracks[2]);
  displayTrackInfo(Tracks[2]);

  if (Tracks[2].TrackType == track_video) {
      MuxedFile.Track_GetInfo_Video(track3, aVideoTrack);
      displayVideoInfo(aVideoTrack);
  }
  else
      cout << "the expected video track is not an video one :(" << endl;

  // let's read only track1 (audio)
  MuxedFile.SelectReadingTrack(track1);
  MuxedFile.SelectReadingTrack(track2);
  MuxedFile.SelectReadingTrack(track1,false);
  MuxedFile.SelectReadingTrack(track1);
  MuxedFile.SelectReadingTrack(track2,false);
  MuxedFile.SelectReadingTrack(track2);
  MuxedFile.SelectReadingTrack(track3);

  // read the frames from the selected tracks and put them in an output file
  StdIOCallback Output_file1("out-binr.bin", MODE_CREATE);
  StdIOCallback Output_file2("out-text.bin", MODE_CREATE);
  StdIOCallback Output_file3("out-vide.bin", MODE_CREATE);

  Track * TrackRead;
  uint32 timecode; // not used yet
  binary *aFrame;
  uint32 aFrameSize;
  while (MuxedFile.ReadFrame(TrackRead, timecode, aFrame, aFrameSize))
  {
      if (TrackRead == track1)
      {
    Output_file1.write(aFrame, aFrameSize);
      }
      else if (TrackRead == track2)
      {
    Output_file2.write(aFrame, aFrameSize);
      }
      else if (TrackRead == track3)
      {
    Output_file3.write(aFrame, aFrameSize);
      }
      else cout << "received a frame from an unwanted track" << endl;
  }
  delete[] Tracks;
#endif // OLD
    }
    catch (exception & Ex)
    {
    cout << Ex.what() << endl;
    return -1;
    }

    return 0;
}
