/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim:set ts=2 sw=2 sts=2 et cindent: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#if !defined(WebMBufferedParser_h_)
#define WebMBufferedParser_h_

#include "nsISupportsImpl.h"
#include "nsTArray.h"
#include "mozilla/ReentrantMonitor.h"
#include "MediaResource.h"

namespace mozilla {

// Stores a stream byte offset and the scaled timecode of the block at
// that offset.
struct WebMTimeDataOffset
{
  WebMTimeDataOffset(int64_t aEndOffset, uint64_t aTimecode,
                     int64_t aInitOffset, int64_t aSyncOffset,
                     int64_t aClusterEndOffset)
    : mEndOffset(aEndOffset)
    , mInitOffset(aInitOffset)
    , mSyncOffset(aSyncOffset)
    , mClusterEndOffset(aClusterEndOffset)
    , mTimecode(aTimecode)
  {}

  bool operator==(int64_t aEndOffset) const {
    return mEndOffset == aEndOffset;
  }

  bool operator!=(int64_t aEndOffset) const {
    return mEndOffset != aEndOffset;
  }

  bool operator<(int64_t aEndOffset) const {
    return mEndOffset < aEndOffset;
  }

  int64_t mEndOffset;
  int64_t mInitOffset;
  int64_t mSyncOffset;
  int64_t mClusterEndOffset;
  uint64_t mTimecode;
};

// A simple WebM parser that produces data offset to timecode pairs as it
// consumes blocks.  A new parser is created for each distinct range of data
// received and begins parsing from the first WebM cluster within that
// range.  Old parsers are destroyed when their range merges with a later
// parser or an already parsed range.  The parser may start at any position
// within the stream.
struct WebMBufferedParser
{
  explicit WebMBufferedParser(int64_t aOffset)
    : mStartOffset(aOffset)
    , mCurrentOffset(aOffset)
    , mInitEndOffset(-1)
    , mBlockEndOffset(-1)
    , mState(READ_ELEMENT_ID)
    , mNextState(READ_ELEMENT_ID)
    , mVIntRaw(false)
    , mLastInitStartOffset(-1)
    , mClusterSyncPos(0)
    , mVIntLeft(0)
    , mBlockSize(0)
    , mClusterTimecode(0)
    , mClusterOffset(0)
    , mClusterEndOffset(-1)
    , mBlockOffset(0)
    , mBlockTimecode(0)
    , mBlockTimecodeLength(0)
    , mSkipBytes(0)
    , mTimecodeScale(1000000)
    , mGotTimecodeScale(false)
  {
    if (mStartOffset != 0) {
      mState = FIND_CLUSTER_SYNC;
    }
  }

  uint32_t GetTimecodeScale() {
    MOZ_ASSERT(mGotTimecodeScale);
    return mTimecodeScale;
  }

  // If this parser is not expected to parse a segment info, it must be told
  // the appropriate timecode scale to use from elsewhere.
  void SetTimecodeScale(uint32_t aTimecodeScale) {
    mTimecodeScale = aTimecodeScale;
    mGotTimecodeScale = true;
  }

  // Steps the parser through aLength bytes of data.  Always consumes
  // aLength bytes.  Updates mCurrentOffset before returning.  Acquires
  // aReentrantMonitor before using aMapping.
  // Returns false if an error was encountered.
  bool Append(const unsigned char* aBuffer, uint32_t aLength,
              nsTArray<WebMTimeDataOffset>& aMapping,
              ReentrantMonitor& aReentrantMonitor);

  bool operator==(int64_t aOffset) const {
    return mCurrentOffset == aOffset;
  }

  bool operator<(int64_t aOffset) const {
    return mCurrentOffset < aOffset;
  }

  // Returns the start offset of the init (EBML) or media segment (Cluster)
  // following the aOffset position. If none were found, returns mBlockEndOffset.
  // This allows to determine the end of the interval containg aOffset.
  int64_t EndSegmentOffset(int64_t aOffset);

  // The offset at which this parser started parsing.  Used to merge
  // adjacent parsers, in which case the later parser adopts the earlier
  // parser's mStartOffset.
  int64_t mStartOffset;

  // Current offset within the stream.  Updated in chunks as Append() consumes
  // data.
  int64_t mCurrentOffset;

  // Tracks element's end offset. This indicates the end of the first init
  // segment. Will only be set if a Segment Information has been found.
  int64_t mInitEndOffset;

  // End offset of the last block parsed.
  // Will only be set if a complete block has been parsed.
  int64_t mBlockEndOffset;

private:
  enum State {
    // Parser start state.  Expects to begin at a valid EBML element.  Move
    // to READ_VINT with mVIntRaw true, then return to READ_ELEMENT_SIZE.
    READ_ELEMENT_ID,

    // Store element ID read into mVInt into mElement.mID.  Move to
    // READ_VINT with mVIntRaw false, then return to PARSE_ELEMENT.
    READ_ELEMENT_SIZE,

    // Parser start state for parsers started at an arbitrary offset.  Scans
    // forward for the first cluster, then move to READ_ELEMENT_ID.
    FIND_CLUSTER_SYNC,

    // Simplistic core of the parser.  Does not pay attention to nesting of
    // elements.  Checks mElement for an element ID of interest, then moves
    // to the next state as determined by the element ID.
    PARSE_ELEMENT,

    // Read the first byte of a variable length integer.  The first byte
    // encodes both the variable integer's length and part of the value.
    // The value read so far is stored in mVInt.mValue and the length is
    // stored in mVInt.mLength.  The number of bytes left to read is stored
    // in mVIntLeft.
    READ_VINT,

    // Reads the remaining mVIntLeft bytes into mVInt.mValue.
    READ_VINT_REST,

    // mVInt holds the parsed timecode scale, store it in mTimecodeScale,
    // then return READ_ELEMENT_ID.
    READ_TIMECODESCALE,

    // mVInt holds the parsed cluster timecode, store it in
    // mClusterTimecode, then return to READ_ELEMENT_ID.
    READ_CLUSTER_TIMECODE,

    // mBlockTimecodeLength holds the remaining length of the block timecode
    // left to read.  Read each byte of the timecode into mBlockTimecode.
    // Once complete, calculate the scaled timecode from the cluster
    // timecode, block timecode, and timecode scale, and insert a
    // WebMTimeDataOffset entry into aMapping if one is not already present
    // for this offset.
    READ_BLOCK_TIMECODE,

    // Will skip the current tracks element and set mInitEndOffset if an init
    // segment has been found.
    // Currently, only assumes it's the end of the tracks element.
    CHECK_INIT_FOUND,

    // Skip mSkipBytes of data before resuming parse at mNextState.
    SKIP_DATA,
  };

  // Current state machine action.
  State mState;

  // Next state machine action.  SKIP_DATA and READ_VINT_REST advance to
  // mNextState when the current action completes.
  State mNextState;

  struct VInt {
    VInt() : mValue(0), mLength(0) {}
    uint64_t mValue;
    uint64_t mLength;
  };

  struct EBMLElement {
    uint64_t Length() { return mID.mLength + mSize.mLength; }
    VInt mID;
    VInt mSize;
  };

  EBMLElement mElement;

  VInt mVInt;

  bool mVIntRaw;

  // EBML start offset. This indicates the start of the last init segment
  // parsed. Will only be set if an EBML element has been found.
  int64_t mLastInitStartOffset;

  // Current match position within CLUSTER_SYNC_ID.  Used to find sync
  // within arbitrary data.
  uint32_t mClusterSyncPos;

  // Number of bytes of mVInt left to read.  mVInt is complete once this
  // reaches 0.
  uint32_t mVIntLeft;

  // Size of the block currently being parsed.  Any unused data within the
  // block is skipped once the block timecode has been parsed.
  uint64_t mBlockSize;

  // Cluster-level timecode.
  uint64_t mClusterTimecode;

  // Start offset of the cluster currently being parsed.  Used as the sync
  // point offset for the offset-to-time mapping as each block timecode is
  // been parsed.
  int64_t mClusterOffset;

  // End offset of the cluster currently being parsed. -1 if unknown.
  int64_t mClusterEndOffset;

  // Start offset of the block currently being parsed.  Used as the byte
  // offset for the offset-to-time mapping once the block timecode has been
  // parsed.
  int64_t mBlockOffset;

  // Block-level timecode.  This is summed with mClusterTimecode to produce
  // an absolute timecode for the offset-to-time mapping.
  int16_t mBlockTimecode;

  // Number of bytes of mBlockTimecode left to read.
  uint32_t mBlockTimecodeLength;

  // Count of bytes left to skip before resuming parse at mNextState.
  // Mostly used to skip block payload data after reading a block timecode.
  uint32_t mSkipBytes;

  // Timecode scale read from the segment info and used to scale absolute
  // timecodes.
  uint32_t mTimecodeScale;

  // True if we read the timecode scale from the segment info or have
  // confirmed that the default value is to be used.
  bool mGotTimecodeScale;
};

class WebMBufferedState final
{
  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(WebMBufferedState)

public:
  WebMBufferedState()
    : mReentrantMonitor("WebMBufferedState")
    , mLastBlockOffset(-1)
  {
    MOZ_COUNT_CTOR(WebMBufferedState);
  }

  void NotifyDataArrived(const unsigned char* aBuffer, uint32_t aLength, int64_t aOffset);
  void Reset();
  void UpdateIndex(const MediaByteRangeSet& aRanges, MediaResource* aResource);
  bool CalculateBufferedForRange(int64_t aStartOffset, int64_t aEndOffset,
                                 uint64_t* aStartTime, uint64_t* aEndTime);

  // Returns true if mTimeMapping is not empty and sets aOffset to
  // the latest offset for which decoding can resume without data
  // dependencies to arrive at aTime. aTime will be clamped to the start
  // of mTimeMapping if it is earlier than the first element, and to the end
  // if later than the last
  bool GetOffsetForTime(uint64_t aTime, int64_t* aOffset);

  // Returns end offset of init segment or -1 if none found.
  int64_t GetInitEndOffset();
  // Returns the end offset of the last complete block or -1 if none found.
  int64_t GetLastBlockOffset();

  // Returns start time
  bool GetStartTime(uint64_t *aTime);

  // Returns keyframe for time
  bool GetNextKeyframeTime(uint64_t aTime, uint64_t* aKeyframeTime);

private:
  // Private destructor, to discourage deletion outside of Release():
  ~WebMBufferedState() {
    MOZ_COUNT_DTOR(WebMBufferedState);
  }

  // Synchronizes access to the mTimeMapping array and mLastBlockOffset.
  ReentrantMonitor mReentrantMonitor;

  // Sorted (by offset) map of data offsets to timecodes.  Populated
  // on the main thread as data is received and parsed by WebMBufferedParsers.
  nsTArray<WebMTimeDataOffset> mTimeMapping;
  // The last complete block parsed. -1 if not set.
  int64_t mLastBlockOffset;

  // Sorted (by offset) live parser instances.  Main thread only.
  nsTArray<WebMBufferedParser> mRangeParsers;
};

} // namespace mozilla

#endif