// Copyright (c) 2010 The WebM project authors. All Rights Reserved.
//
// Use of this source code is governed by a BSD-style license
// that can be found in the LICENSE file in the root of the source
// tree. An additional intellectual property rights grant can be found
// in the file PATENTS.  All contributing project authors may
// be found in the AUTHORS file in the root of the source tree.

#ifndef MKVPARSER_HPP
#define MKVPARSER_HPP

#include <cstdlib>
#include <cstdio>
#include <cstddef>

namespace mkvparser
{

const int E_FILE_FORMAT_INVALID = -2;
const int E_BUFFER_NOT_FULL = -3;

class IMkvReader
{
public:
    virtual int Read(long long pos, long len, unsigned char* buf) = 0;
    virtual int Length(long long* total, long long* available) = 0;
protected:
    virtual ~IMkvReader();
};

long long GetUIntLength(IMkvReader*, long long, long&);
long long ReadUInt(IMkvReader*, long long, long&);
long long UnserializeUInt(IMkvReader*, long long pos, long long size);

long UnserializeFloat(IMkvReader*, long long pos, long long size, double&);
long UnserializeInt(IMkvReader*, long long pos, long len, long long& result);

long UnserializeString(
        IMkvReader*,
        long long pos,
        long long size,
        char*& str);

long ParseElementHeader(
    IMkvReader* pReader,
    long long& pos,  //consume id and size fields
    long long stop,  //if you know size of element's parent
    long long& id,
    long long& size);

bool Match(IMkvReader*, long long&, unsigned long, long long&);
bool Match(IMkvReader*, long long&, unsigned long, unsigned char*&, size_t&);

void GetVersion(int& major, int& minor, int& build, int& revision);

struct EBMLHeader
{
    EBMLHeader();
    ~EBMLHeader();
    long long m_version;
    long long m_readVersion;
    long long m_maxIdLength;
    long long m_maxSizeLength;
    char* m_docType;
    long long m_docTypeVersion;
    long long m_docTypeReadVersion;

    long long Parse(IMkvReader*, long long&);
    void Init();
};


class Segment;
class Track;
class Cluster;

class Block
{
    Block(const Block&);
    Block& operator=(const Block&);

public:
    const long long m_start;
    const long long m_size;

    Block(long long start, long long size);
    ~Block();

    long Parse(IMkvReader*);

    long long GetTrackNumber() const;
    long long GetTimeCode(const Cluster*) const;  //absolute, but not scaled
    long long GetTime(const Cluster*) const;      //absolute, and scaled (ns)
    bool IsKey() const;
    void SetKey(bool);
    bool IsInvisible() const;

    enum Lacing { kLacingNone, kLacingXiph, kLacingFixed, kLacingEbml };
    Lacing GetLacing() const;

    int GetFrameCount() const;  //to index frames: [0, count)

    struct Frame
    {
        long long pos;  //absolute offset
        long len;

        long Read(IMkvReader*, unsigned char*) const;
    };

    const Frame& GetFrame(int frame_index) const;

private:
    long long m_track;   //Track::Number()
    short m_timecode;  //relative to cluster
    unsigned char m_flags;

    Frame* m_frames;
    int m_frame_count;

};


class BlockEntry
{
    BlockEntry(const BlockEntry&);
    BlockEntry& operator=(const BlockEntry&);

protected:
    BlockEntry(Cluster*, long index);

public:
    virtual ~BlockEntry();

    bool EOS() const;
    const Cluster* GetCluster() const;
    long GetIndex() const;
    virtual const Block* GetBlock() const = 0;

    enum Kind { kBlockEOS, kBlockSimple, kBlockGroup };
    virtual Kind GetKind() const = 0;

protected:
    Cluster* const m_pCluster;
    const long m_index;

};


class SimpleBlock : public BlockEntry
{
    SimpleBlock(const SimpleBlock&);
    SimpleBlock& operator=(const SimpleBlock&);

public:
    SimpleBlock(Cluster*, long index, long long start, long long size);
    long Parse();

    Kind GetKind() const;
    const Block* GetBlock() const;

protected:
    Block m_block;

};


class BlockGroup : public BlockEntry
{
    BlockGroup(const BlockGroup&);
    BlockGroup& operator=(const BlockGroup&);

public:
    BlockGroup(
        Cluster*,
        long index,
        long long block_start, //absolute pos of block's payload
        long long block_size,  //size of block's payload
        long long prev,
        long long next,
        long long duration);

    long Parse();

    Kind GetKind() const;
    const Block* GetBlock() const;

    long long GetPrevTimeCode() const;  //relative to block's time
    long long GetNextTimeCode() const;  //as above
    long long GetDuration() const;

private:
    Block m_block;
    const long long m_prev;
    const long long m_next;
    const long long m_duration;

};

///////////////////////////////////////////////////////////////
// ContentEncoding element
// Elements used to describe if the track data has been encrypted or
// compressed with zlib or header stripping.
class ContentEncoding {
public:
    ContentEncoding();
    ~ContentEncoding();

    // ContentCompression element names
    struct ContentCompression {
        ContentCompression();
        ~ContentCompression();

        unsigned long long algo;
        unsigned char* settings;
    };

    // ContentEncryption element names
    struct ContentEncryption {
        ContentEncryption();
        ~ContentEncryption();

        unsigned long long algo;
        unsigned char* key_id;
        long long key_id_len;
        unsigned char* signature;
        long long signature_len;
        unsigned char* sig_key_id;
        long long sig_key_id_len;
        unsigned long long sig_algo;
        unsigned long long sig_hash_algo;
    };

    // Returns ContentCompression represented by |idx|. Returns NULL if |idx|
    // is out of bounds.
    const ContentCompression* GetCompressionByIndex(unsigned long idx) const;

    // Returns number of ContentCompression elements in this ContentEncoding
    // element.
    unsigned long GetCompressionCount() const;

    // Returns ContentEncryption represented by |idx|. Returns NULL if |idx|
    // is out of bounds.
    const ContentEncryption* GetEncryptionByIndex(unsigned long idx) const;

    // Returns number of ContentEncryption elements in this ContentEncoding
    // element.
    unsigned long GetEncryptionCount() const;

    // Parses the ContentEncoding element from |pReader|. |start| is the
    // starting offset of the ContentEncoding payload. |size| is the size in
    // bytes of the ContentEncoding payload. Returns true on success.
    bool ParseContentEncodingEntry(long long start,
                                   long long size,
                                   IMkvReader* const pReader);

    // Parses the ContentEncryption element from |pReader|. |start| is the
    // starting offset of the ContentEncryption payload. |size| is the size in
    // bytes of the ContentEncryption payload. |encryption| is where the parsed
    // values will be stored.
    void ParseEncryptionEntry(long long start,
                              long long size,
                              IMkvReader* const pReader,
                              ContentEncryption* const encryption);

    unsigned long long encoding_order() const { return encoding_order_; }
    unsigned long long encoding_scope() const { return encoding_scope_; }
    unsigned long long encoding_type() const { return encoding_type_; }

private:
    // Member variables for list of ContentCompression elements.
    ContentCompression** compression_entries_;
    ContentCompression** compression_entries_end_;

    // Member variables for list of ContentEncryption elements.
    ContentEncryption** encryption_entries_;
    ContentEncryption** encryption_entries_end_;

    // ContentEncoding element names
    unsigned long long encoding_order_;
    unsigned long long encoding_scope_;
    unsigned long long encoding_type_;

    // LIBWEBM_DISALLOW_COPY_AND_ASSIGN(ContentEncoding);
    ContentEncoding(const ContentEncoding&);
    ContentEncoding& operator=(const ContentEncoding&);
};

class Track
{
    Track(const Track&);
    Track& operator=(const Track&);

public:
    enum Type { kVideo = 1, kAudio = 2 };

    Segment* const m_pSegment;
    const long long m_element_start;
    const long long m_element_size;
    virtual ~Track();

    long GetType() const;
    long GetNumber() const;
    unsigned long long GetUid() const;
    const char* GetNameAsUTF8() const;
    const char* GetCodecNameAsUTF8() const;
    const char* GetCodecId() const;
    const unsigned char* GetCodecPrivate(size_t&) const;
    bool GetLacing() const;

    const BlockEntry* GetEOS() const;

    struct Settings
    {
        long long start;
        long long size;
    };

    class Info
    {
    public:
        Info();
        ~Info();
        int Copy(Info&) const;
        void Clear();
    private:
        Info(const Info&);
        Info& operator=(const Info&);
    public:
        long type;
        long number;
        unsigned long long uid;
        char* nameAsUTF8;
        char* codecId;
        char* codecNameAsUTF8;
        unsigned char* codecPrivate;
        size_t codecPrivateSize;
        bool lacing;
        Settings settings;
    private:
        int CopyStr(char* Info::*str, Info&) const;
    };

    long GetFirst(const BlockEntry*&) const;
    long GetNext(const BlockEntry* pCurr, const BlockEntry*& pNext) const;
    virtual bool VetEntry(const BlockEntry*) const = 0;
    virtual long Seek(long long time_ns, const BlockEntry*&) const = 0;

    const ContentEncoding* GetContentEncodingByIndex(unsigned long idx) const;
    unsigned long GetContentEncodingCount() const;

    void ParseContentEncodingsEntry(long long start, long long size);

protected:
    Track(
        Segment*,
        long long element_start,
        long long element_size);

    Info m_info;

    class EOSBlock : public BlockEntry
    {
    public:
        EOSBlock();

        Kind GetKind() const;
        const Block* GetBlock() const;
    };

    EOSBlock m_eos;

private:
    ContentEncoding** content_encoding_entries_;
    ContentEncoding** content_encoding_entries_end_;
};


class VideoTrack : public Track
{
    VideoTrack(const VideoTrack&);
    VideoTrack& operator=(const VideoTrack&);

    VideoTrack(
        Segment*,
        long long element_start,
        long long element_size);

public:
    static long Parse(
        Segment*,
        const Info&,
        long long element_start,
        long long element_size,
        VideoTrack*&);

    long long GetWidth() const;
    long long GetHeight() const;
    double GetFrameRate() const;

    bool VetEntry(const BlockEntry*) const;
    long Seek(long long time_ns, const BlockEntry*&) const;

private:
    long long m_width;
    long long m_height;
    double m_rate;

};


class AudioTrack : public Track
{
    AudioTrack(const AudioTrack&);
    AudioTrack& operator=(const AudioTrack&);

    AudioTrack(
        Segment*,
        long long element_start,
        long long element_size);
public:
    static long Parse(
        Segment*,
        const Info&,
        long long element_start,
        long long element_size,
        AudioTrack*&);

    double GetSamplingRate() const;
    long long GetChannels() const;
    long long GetBitDepth() const;
    bool VetEntry(const BlockEntry*) const;
    long Seek(long long time_ns, const BlockEntry*&) const;

private:
    double m_rate;
    long long m_channels;
    long long m_bitDepth;
};


class Tracks
{
    Tracks(const Tracks&);
    Tracks& operator=(const Tracks&);

public:
    Segment* const m_pSegment;
    const long long m_start;
    const long long m_size;
    const long long m_element_start;
    const long long m_element_size;

    Tracks(
        Segment*,
        long long start,
        long long size,
        long long element_start,
        long long element_size);

    ~Tracks();

    long Parse();

    unsigned long GetTracksCount() const;

    const Track* GetTrackByNumber(long tn) const;
    const Track* GetTrackByIndex(unsigned long idx) const;

private:
    Track** m_trackEntries;
    Track** m_trackEntriesEnd;

    long ParseTrackEntry(
        long long payload_start,
        long long payload_size,
        long long element_start,
        long long element_size,
        Track*&) const;

};


class SegmentInfo
{
    SegmentInfo(const SegmentInfo&);
    SegmentInfo& operator=(const SegmentInfo&);

public:
    Segment* const m_pSegment;
    const long long m_start;
    const long long m_size;
    const long long m_element_start;
    const long long m_element_size;

    SegmentInfo(
        Segment*,
        long long start,
        long long size,
        long long element_start,
        long long element_size);

    ~SegmentInfo();

    long Parse();

    long long GetTimeCodeScale() const;
    long long GetDuration() const;  //scaled
    const char* GetMuxingAppAsUTF8() const;
    const char* GetWritingAppAsUTF8() const;
    const char* GetTitleAsUTF8() const;

private:
    long long m_timecodeScale;
    double m_duration;
    char* m_pMuxingAppAsUTF8;
    char* m_pWritingAppAsUTF8;
    char* m_pTitleAsUTF8;
};


class SeekHead
{
    SeekHead(const SeekHead&);
    SeekHead& operator=(const SeekHead&);

public:
    Segment* const m_pSegment;
    const long long m_start;
    const long long m_size;
    const long long m_element_start;
    const long long m_element_size;

    SeekHead(
        Segment*,
        long long start,
        long long size,
        long long element_start,
        long long element_size);

    ~SeekHead();

    long Parse();

    struct Entry
    {
        //the SeekHead entry payload
        long long id;
        long long pos;

        //absolute pos of SeekEntry ID
        long long element_start;

        //SeekEntry ID size + size size + payload
        long long element_size;
    };

    int GetCount() const;
    const Entry* GetEntry(int idx) const;

    struct VoidElement
    {
        //absolute pos of Void ID
        long long element_start;

        //ID size + size size + payload size
        long long element_size;
    };

    int GetVoidElementCount() const;
    const VoidElement* GetVoidElement(int idx) const;

private:
    Entry* m_entries;
    int m_entry_count;

    VoidElement* m_void_elements;
    int m_void_element_count;

    static bool ParseEntry(
        IMkvReader*,
        long long pos,  //payload
        long long size,
        Entry*);

};

class Cues;
class CuePoint
{
    friend class Cues;

    CuePoint(long, long long);
    ~CuePoint();

    CuePoint(const CuePoint&);
    CuePoint& operator=(const CuePoint&);

public:
    long long m_element_start;
    long long m_element_size;

    void Load(IMkvReader*);

    long long GetTimeCode() const;      //absolute but unscaled
    long long GetTime(const Segment*) const;  //absolute and scaled (ns units)

    struct TrackPosition
    {
        long long m_track;
        long long m_pos;  //of cluster
        long long m_block;
        //codec_state  //defaults to 0
        //reference = clusters containing req'd referenced blocks
        //  reftime = timecode of the referenced block

        void Parse(IMkvReader*, long long, long long);
    };

    const TrackPosition* Find(const Track*) const;

private:
    const long m_index;
    long long m_timecode;
    TrackPosition* m_track_positions;
    size_t m_track_positions_count;

};


class Cues
{
    friend class Segment;

    Cues(
        Segment*,
        long long start,
        long long size,
        long long element_start,
        long long element_size);
    ~Cues();

    Cues(const Cues&);
    Cues& operator=(const Cues&);

public:
    Segment* const m_pSegment;
    const long long m_start;
    const long long m_size;
    const long long m_element_start;
    const long long m_element_size;

    bool Find(  //lower bound of time_ns
        long long time_ns,
        const Track*,
        const CuePoint*&,
        const CuePoint::TrackPosition*&) const;

#if 0
    bool FindNext(  //upper_bound of time_ns
        long long time_ns,
        const Track*,
        const CuePoint*&,
        const CuePoint::TrackPosition*&) const;
#endif

    const CuePoint* GetFirst() const;
    const CuePoint* GetLast() const;
    const CuePoint* GetNext(const CuePoint*) const;

    const BlockEntry* GetBlock(
                        const CuePoint*,
                        const CuePoint::TrackPosition*) const;

    bool LoadCuePoint() const;
    long GetCount() const;  //loaded only
    //long GetTotal() const;  //loaded + preloaded
    bool DoneParsing() const;

private:
    void Init() const;
    void PreloadCuePoint(long&, long long) const;

    mutable CuePoint** m_cue_points;
    mutable long m_count;
    mutable long m_preload_count;
    mutable long long m_pos;

};


class Cluster
{
    friend class Segment;

    Cluster(const Cluster&);
    Cluster& operator=(const Cluster&);

public:
    Segment* const m_pSegment;

public:
    static Cluster* Create(
        Segment*,
        long index,       //index in segment
        long long off);   //offset relative to segment
        //long long element_size);

    Cluster();  //EndOfStream
    ~Cluster();

    bool EOS() const;

    long long GetTimeCode() const;   //absolute, but not scaled
    long long GetTime() const;       //absolute, and scaled (nanosecond units)
    long long GetFirstTime() const;  //time (ns) of first (earliest) block
    long long GetLastTime() const;   //time (ns) of last (latest) block

    long GetFirst(const BlockEntry*&) const;
    long GetLast(const BlockEntry*&) const;
    long GetNext(const BlockEntry* curr, const BlockEntry*& next) const;

    const BlockEntry* GetEntry(const Track*, long long ns = -1) const;
    const BlockEntry* GetEntry(
        const CuePoint&,
        const CuePoint::TrackPosition&) const;
    //const BlockEntry* GetMaxKey(const VideoTrack*) const;

//    static bool HasBlockEntries(const Segment*, long long);

    static long HasBlockEntries(
            const Segment*,
            long long idoff,
            long long& pos,
            long& size);

    long GetEntryCount() const;

    long Load(long long& pos, long& size) const;

    long Parse(long long& pos, long& size) const;
    long GetEntry(long index, const mkvparser::BlockEntry*&) const;

protected:
    Cluster(
        Segment*,
        long index,
        long long element_start);
        //long long element_size);

public:
    const long long m_element_start;
    long long GetPosition() const;  //offset relative to segment

    long GetIndex() const;
    long long GetElementSize() const;
    //long long GetPayloadSize() const;

    //long long Unparsed() const;

private:
    long m_index;
    mutable long long m_pos;
    //mutable long long m_size;
    mutable long long m_element_size;
    mutable long long m_timecode;
    mutable BlockEntry** m_entries;
    mutable long m_entries_size;
    mutable long m_entries_count;

    long ParseSimpleBlock(long long, long long&, long&);
    long ParseBlockGroup(long long, long long&, long&);

    long CreateBlock(long long id, long long pos, long long size);
    long CreateBlockGroup(long long, long long);
    long CreateSimpleBlock(long long, long long);

};


class Segment
{
    friend class Cues;
    friend class VideoTrack;
    friend class AudioTrack;

    Segment(const Segment&);
    Segment& operator=(const Segment&);

private:
    Segment(
        IMkvReader*,
        long long elem_start,
        //long long elem_size,
        long long pos,
        long long size);

public:
    IMkvReader* const m_pReader;
    const long long m_element_start;
    //const long long m_element_size;
    const long long m_start;  //posn of segment payload
    const long long m_size;   //size of segment payload
    Cluster m_eos;  //TODO: make private?

    static long long CreateInstance(IMkvReader*, long long, Segment*&);
    ~Segment();

    long Load();  //loads headers and all clusters

    //for incremental loading
    //long long Unparsed() const;
    bool DoneParsing() const;
    long long ParseHeaders();  //stops when first cluster is found
    //long FindNextCluster(long long& pos, long& size) const;
    long LoadCluster(long long& pos, long& size);  //load one cluster
    long LoadCluster();

    long ParseNext(
            const Cluster* pCurr,
            const Cluster*& pNext,
            long long& pos,
            long& size);

#if 0
    //This pair parses one cluster, but only changes the state of the
    //segment object when the cluster is actually added to the index.
    long ParseCluster(long long& cluster_pos, long long& new_pos) const;
    bool AddCluster(long long cluster_pos, long long new_pos);
#endif

    const SeekHead* GetSeekHead() const;
    const Tracks* GetTracks() const;
    const SegmentInfo* GetInfo() const;
    const Cues* GetCues() const;

    long long GetDuration() const;

    unsigned long GetCount() const;
    const Cluster* GetFirst() const;
    const Cluster* GetLast() const;
    const Cluster* GetNext(const Cluster*);

    const Cluster* FindCluster(long long time_nanoseconds) const;
    //const BlockEntry* Seek(long long time_nanoseconds, const Track*) const;

    const Cluster* FindOrPreloadCluster(long long pos);

    long ParseCues(
        long long cues_off,  //offset relative to start of segment
        long long& parse_pos,
        long& parse_len);

private:

    long long m_pos;  //absolute file posn; what has been consumed so far
    Cluster* m_pUnknownSize;

    SeekHead* m_pSeekHead;
    SegmentInfo* m_pInfo;
    Tracks* m_pTracks;
    Cues* m_pCues;
    Cluster** m_clusters;
    long m_clusterCount;         //number of entries for which m_index >= 0
    long m_clusterPreloadCount;  //number of entries for which m_index < 0
    long m_clusterSize;          //array size

    long DoLoadCluster(long long&, long&);
    long DoLoadClusterUnknownSize(long long&, long&);
    long DoParseNext(const Cluster*&, long long&, long&);

    void AppendCluster(Cluster*);
    void PreloadCluster(Cluster*, ptrdiff_t);

    //void ParseSeekHead(long long pos, long long size);
    //void ParseSeekEntry(long long pos, long long size);
    //void ParseCues(long long);

    const BlockEntry* GetBlock(
        const CuePoint&,
        const CuePoint::TrackPosition&);

};

}  //end namespace mkvparser

inline long mkvparser::Segment::LoadCluster()
{
    long long pos;
    long size;

    return LoadCluster(pos, size);
}

#endif  //MKVPARSER_HPP