/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* 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/. */

#include "FlacDemuxer.h"

#include "mozilla/Maybe.h"
#include "BitReader.h"
#include "prenv.h"
#include "FlacFrameParser.h"
#include "VideoUtils.h"
#include "TimeUnits.h"

extern mozilla::LazyLogModule gMediaDemuxerLog;
#define LOG(msg, ...) \
  DDMOZ_LOG(gMediaDemuxerLog, LogLevel::Debug, msg, ##__VA_ARGS__)
#define LOGV(msg, ...) \
  DDMOZ_LOG(gMediaDemuxerLog, LogLevel::Verbose, msg, ##__VA_ARGS__)

using namespace mozilla::media;

namespace mozilla {
namespace flac {

// flac::FrameHeader - Holds the flac frame header and its parsing
// state.

class FrameHeader {
 public:
  const AudioInfo& Info() const { return mInfo; }

  uint32_t Size() const { return mSize; }

  bool IsValid() const { return mValid; }

  // Return the index (in samples) from the beginning of the track.
  int64_t Index() const { return mIndex; }

  // Parse the current packet and check that it made a valid flac frame header.
  // From https://xiph.org/flac/format.html#frame_header
  // A valid header is one that can be decoded without error and that has a
  // valid CRC.
  bool Parse(const uint8_t* aPacket, size_t aBytes) {
    BitReader br(aPacket, aBytes * 8);

    // Frame sync code.
    if ((br.ReadBits(15) & 0x7fff) != 0x7ffc) {
      return false;
    }

    // Variable block size stream code.
    mVariableBlockSize = br.ReadBit();

    // Block size and sample rate codes.
    int bs_code = br.ReadBits(4);
    int sr_code = br.ReadBits(4);

    // Channels and decorrelation.
    uint32_t ch_mode = br.ReadBits(4);
    if (ch_mode < FLAC_MAX_CHANNELS) {
      mInfo.mChannels = ch_mode + 1;
    } else if (ch_mode < FLAC_MAX_CHANNELS + FLAC_CHMODE_MID_SIDE) {
      // This is a special flac channels, we can't handle those yet. Treat it
      // as stereo.
      mInfo.mChannels = 2;
    } else {
      // invalid channel mode
      return false;
    }

    // Bits per sample.
    int bps_code = br.ReadBits(3);
    if (bps_code == 3 || bps_code == 7) {
      // Invalid sample size code.
      return false;
    }
    mInfo.mBitDepth = FlacSampleSizeTable[bps_code];

    // Reserved bit, must be 0.
    if (br.ReadBit()) {
      // Broken stream, invalid padding.
      return false;
    }

    // Sample or frame count.
    uint64_t frame_or_sample_num = br.ReadUTF8();
    if (frame_or_sample_num == UINT64_MAX) {
      // Sample/frame number invalid.
      return false;
    }

    // Blocksize
    if (bs_code == 0) {
      // reserved blocksize code
      return false;
    } else if (bs_code == 6) {
      mBlocksize = br.ReadBits(8) + 1;
    } else if (bs_code == 7) {
      mBlocksize = br.ReadBits(16) + 1;
    } else {
      mBlocksize = FlacBlocksizeTable[bs_code];
    }

    // The sample index is either:
    // 1- coded sample number if blocksize is variable or
    // 2- coded frame number if blocksize is known.
    // A frame is made of Blocksize sample.
    mIndex = mVariableBlockSize ? frame_or_sample_num
                                : frame_or_sample_num * mBlocksize;
    mFrameOrSampleNum = static_cast<uint64_t>(frame_or_sample_num);

    // Sample rate.
    if (sr_code < 12) {
      mInfo.mRate = FlacSampleRateTable[sr_code];
    } else if (sr_code == 12) {
      mInfo.mRate = br.ReadBits(8) * 1000;
    } else if (sr_code == 13) {
      mInfo.mRate = br.ReadBits(16);
    } else if (sr_code == 14) {
      mInfo.mRate = br.ReadBits(16) * 10;
    } else {
      // Illegal sample rate code.
      return false;
    }

    // Header CRC-8 check.
    uint8_t crc = 0;
    for (uint32_t i = 0; i < br.BitCount() / 8; i++) {
      crc = CRC8Table[crc ^ aPacket[i]];
    }
    mValid =
#ifdef FUZZING
        true;
#else
        crc == br.ReadBits(8);
#endif
    mSize = br.BitCount() / 8;

    if (mValid) {
      // Set the mimetype to make it a valid AudioInfo.
      mInfo.mMimeType = "audio/flac";
      // Set the codec specific data to flac, but leave it empty since we don't
      // have METADATA_BLOCK_STREAMINFO in the frame.
      mInfo.mCodecSpecificConfig =
          AudioCodecSpecificVariant{FlacCodecSpecificData{}};
    }

    return mValid;
  }

 private:
  friend class Frame;
  enum {
    FLAC_CHMODE_INDEPENDENT = 0,
    FLAC_CHMODE_LEFT_SIDE,
    FLAC_CHMODE_RIGHT_SIDE,
    FLAC_CHMODE_MID_SIDE,
  };
  AudioInfo mInfo;
  // mFrameOrSampleNum is either:
  // 1- coded sample number if blocksize is variable or
  // 2- coded frame number if blocksize is fixed.
  // A frame is made of Blocksize sample.
  uint64_t mFrameOrSampleNum = 0;
  // Index in samples from start;
  int64_t mIndex = 0;
  bool mVariableBlockSize = false;
  uint32_t mBlocksize = 0;
  uint32_t mSize = 0;
  bool mValid = false;

  static const uint32_t FlacSampleRateTable[16];
  static const uint32_t FlacBlocksizeTable[16];
  static const uint8_t FlacSampleSizeTable[8];
  static const uint8_t CRC8Table[256];
};

const uint32_t FrameHeader::FlacSampleRateTable[16] = {
    0,     88200, 176400, 192000, 8000, 16000, 22050, 24000,
    32000, 44100, 48000,  96000,  0,    0,     0,     0};

const uint32_t FrameHeader::FlacBlocksizeTable[16] = {
    0,        192,      576 << 0, 576 << 1, 576 << 2, 576 << 3,
    0,        0,        256 << 0, 256 << 1, 256 << 2, 256 << 3,
    256 << 4, 256 << 5, 256 << 6, 256 << 7};

const uint8_t FrameHeader::FlacSampleSizeTable[8] = {0,  8,  12, 0,
                                                     16, 20, 24, 0};

const uint8_t FrameHeader::CRC8Table[256] = {
    0x00, 0x07, 0x0E, 0x09, 0x1C, 0x1B, 0x12, 0x15, 0x38, 0x3F, 0x36, 0x31,
    0x24, 0x23, 0x2A, 0x2D, 0x70, 0x77, 0x7E, 0x79, 0x6C, 0x6B, 0x62, 0x65,
    0x48, 0x4F, 0x46, 0x41, 0x54, 0x53, 0x5A, 0x5D, 0xE0, 0xE7, 0xEE, 0xE9,
    0xFC, 0xFB, 0xF2, 0xF5, 0xD8, 0xDF, 0xD6, 0xD1, 0xC4, 0xC3, 0xCA, 0xCD,
    0x90, 0x97, 0x9E, 0x99, 0x8C, 0x8B, 0x82, 0x85, 0xA8, 0xAF, 0xA6, 0xA1,
    0xB4, 0xB3, 0xBA, 0xBD, 0xC7, 0xC0, 0xC9, 0xCE, 0xDB, 0xDC, 0xD5, 0xD2,
    0xFF, 0xF8, 0xF1, 0xF6, 0xE3, 0xE4, 0xED, 0xEA, 0xB7, 0xB0, 0xB9, 0xBE,
    0xAB, 0xAC, 0xA5, 0xA2, 0x8F, 0x88, 0x81, 0x86, 0x93, 0x94, 0x9D, 0x9A,
    0x27, 0x20, 0x29, 0x2E, 0x3B, 0x3C, 0x35, 0x32, 0x1F, 0x18, 0x11, 0x16,
    0x03, 0x04, 0x0D, 0x0A, 0x57, 0x50, 0x59, 0x5E, 0x4B, 0x4C, 0x45, 0x42,
    0x6F, 0x68, 0x61, 0x66, 0x73, 0x74, 0x7D, 0x7A, 0x89, 0x8E, 0x87, 0x80,
    0x95, 0x92, 0x9B, 0x9C, 0xB1, 0xB6, 0xBF, 0xB8, 0xAD, 0xAA, 0xA3, 0xA4,
    0xF9, 0xFE, 0xF7, 0xF0, 0xE5, 0xE2, 0xEB, 0xEC, 0xC1, 0xC6, 0xCF, 0xC8,
    0xDD, 0xDA, 0xD3, 0xD4, 0x69, 0x6E, 0x67, 0x60, 0x75, 0x72, 0x7B, 0x7C,
    0x51, 0x56, 0x5F, 0x58, 0x4D, 0x4A, 0x43, 0x44, 0x19, 0x1E, 0x17, 0x10,
    0x05, 0x02, 0x0B, 0x0C, 0x21, 0x26, 0x2F, 0x28, 0x3D, 0x3A, 0x33, 0x34,
    0x4E, 0x49, 0x40, 0x47, 0x52, 0x55, 0x5C, 0x5B, 0x76, 0x71, 0x78, 0x7F,
    0x6A, 0x6D, 0x64, 0x63, 0x3E, 0x39, 0x30, 0x37, 0x22, 0x25, 0x2C, 0x2B,
    0x06, 0x01, 0x08, 0x0F, 0x1A, 0x1D, 0x14, 0x13, 0xAE, 0xA9, 0xA0, 0xA7,
    0xB2, 0xB5, 0xBC, 0xBB, 0x96, 0x91, 0x98, 0x9F, 0x8A, 0x8D, 0x84, 0x83,
    0xDE, 0xD9, 0xD0, 0xD7, 0xC2, 0xC5, 0xCC, 0xCB, 0xE6, 0xE1, 0xE8, 0xEF,
    0xFA, 0xFD, 0xF4, 0xF3};

// flac::Frame - Frame meta container used to parse and hold a frame
// header and side info.
class Frame {
 public:
  // The FLAC signature is made of 14 bits set to 1; however the 15th bit is
  // mandatorily set to 0, so we need to find either of 0xfffc or 0xfffd 2-bytes
  // signature. We first use a bitmask to see if 0xfc or 0xfd is present. And if
  // so we check for the whole signature.
  int64_t FindNext(const uint8_t* aData, const uint32_t aLength) {
    // The non-variable size of a FLAC header is 32 bits followed by variable
    // size data and a 8 bits CRC.
    // There's no need to read the last 4 bytes, it can never make a complete
    // header.
    if (aLength < 4) {
      return -1;
    }
    uint32_t modOffset = aLength % 4;
    uint32_t i, j;

    for (i = 0; i < modOffset; i++) {
      if ((BigEndian::readUint16(aData + i) & 0xfffe) == 0xfff8) {
        if (mHeader.Parse(aData + i, aLength - i)) {
          return i;
        }
      }
    }

    for (; i < aLength - 4; i += 4) {
      uint32_t x = BigEndian::readUint32(aData + i);
      if (((x & ~(x + 0x01010101)) & 0x80808080)) {
        for (j = 0; j < 4; j++) {
          if ((BigEndian::readUint16(aData + i + j) & 0xfffe) == 0xfff8) {
            if (mHeader.Parse(aData + i + j, aLength - i - j)) {
              return i + j;
            }
          }
        }
      }
    }
    return -1;
  }

  // Find the next frame start in the current resource.
  // On exit return true, offset is set and resource points to the frame found.
  bool FindNext(MediaResourceIndex& aResource) {
    static const int BUFFER_SIZE = 4096;

    Reset();

    nsTArray<char> buffer;
    uint64_t originalOffset = static_cast<uint64_t>(aResource.Tell());
    uint64_t offset = originalOffset;
    uint32_t innerOffset = 0;

    do {
      uint32_t read = 0;
      buffer.SetLength(BUFFER_SIZE + innerOffset);
      nsresult rv =
          aResource.Read(buffer.Elements() + innerOffset, BUFFER_SIZE, &read);
      if (NS_FAILED(rv)) {
        return false;
      }

      const size_t bufSize = read + innerOffset;
      int64_t foundOffset =
          FindNext(reinterpret_cast<uint8_t*>(buffer.Elements()), bufSize);

      if (foundOffset >= 0) {
        SetOffset(aResource, static_cast<uint64_t>(foundOffset) + offset);
        return true;
      }

      if (read < BUFFER_SIZE) {
        // Nothing more to try on as we had reached EOS during the previous
        // read.
        mEOS = true;
        return false;
      }

      // Scan the next block;
      // We rewind a bit to re-try what could have been an incomplete packet.
      // The maximum size of a FLAC header being FLAC_MAX_FRAME_HEADER_SIZE so
      // we need to retry just after that amount.
      offset += bufSize - (FLAC_MAX_FRAME_HEADER_SIZE + 1);
      buffer.RemoveElementsAt(0, bufSize - (FLAC_MAX_FRAME_HEADER_SIZE + 1));
      innerOffset = buffer.Length();
    } while (offset - originalOffset < FLAC_MAX_FRAME_SIZE);

    return false;
  }

  uint64_t Offset() const { return mOffset; }

  const AudioInfo& Info() const { return Header().Info(); }

  void SetEndOffset(uint64_t aOffset) { mSize = aOffset - mOffset; }

  void SetEndTime(int64_t aIndex) {
    if (aIndex > Header().mIndex) {
      mDuration = aIndex - Header().mIndex;
    }
  }

  void ResetStartTimeIfNeeded(const Frame& aReferenceFrame) {
    if (Header().mVariableBlockSize ||
        aReferenceFrame.Header().mVariableBlockSize ||
        aReferenceFrame.Header().mBlocksize <= Header().mBlocksize) {
      // Not a fixed size frame, or nothing to adjust.
      return;
    }
    mHeader.mIndex =
        Header().mFrameOrSampleNum * aReferenceFrame.Header().mBlocksize;
  }

  uint32_t Size() const { return mSize; }

  TimeUnit Time() const {
    if (!IsValid()) {
      return TimeUnit::Invalid();
    }
    MOZ_ASSERT(Header().Info().mRate, "Invalid Frame. Need Header");
    return media::TimeUnit(Header().mIndex, Header().Info().mRate);
  }

  TimeUnit Duration() const {
    if (!IsValid()) {
      return TimeUnit();
    }
    MOZ_ASSERT(Header().Info().mRate, "Invalid Frame. Need Header");
    return media::TimeUnit(mDuration, Header().Info().mRate);
  }

  // Returns the parsed frame header.
  const FrameHeader& Header() const { return mHeader; }

  bool IsValid() const { return mHeader.IsValid(); }

  bool EOS() const { return mEOS; }

  void SetRate(uint32_t aRate) { mHeader.mInfo.mRate = aRate; };

  void SetBitDepth(uint32_t aBitDepth) { mHeader.mInfo.mBitDepth = aBitDepth; }

  void SetInvalid() { mHeader.mValid = false; }

  // Resets the frame header and data.
  void Reset() { *this = Frame(); }

 private:
  void SetOffset(MediaResourceIndex& aResource, uint64_t aOffset) {
    mOffset = aOffset;
    aResource.Seek(SEEK_SET, mOffset);
  }

  // The offset to the start of the header.
  uint64_t mOffset = 0;
  uint32_t mSize = 0;
  uint32_t mDuration = 0;
  bool mEOS = false;

  // The currently parsed frame header.
  FrameHeader mHeader;
};

class FrameParser {
 public:
  // Returns the currently parsed frame. Reset via EndFrameSession.
  const Frame& CurrentFrame() const { return mFrame; }

  // Returns the first parsed frame.
  const Frame& FirstFrame() const { return mFirstFrame; }

  // Clear the last parsed frame to allow for next frame parsing
  void EndFrameSession() {
    mNextFrame.Reset();
    mFrame.Reset();
  }

  // Attempt to find the next frame.
  bool FindNextFrame(MediaResourceIndex& aResource) {
    mFrame = mNextFrame;
    if (GetNextFrame(aResource)) {
      if (!mFrame.IsValid()) {
        mFrame = mNextFrame;
        // We need two frames to be able to start playing (or have reached EOS).
        GetNextFrame(aResource);
      }
    }

    if (mFrame.IsValid()) {
      if (mNextFrame.EOS()) {
        mFrame.SetEndOffset(static_cast<uint64_t>(aResource.Tell()));
        // If the blocksize is fixed, the frame's starting sample number will be
        // the frame number times the blocksize. However, the last block may
        // have been incorrectly set as shorter than the stream blocksize.
        // We recalculate the start time of this last sample using the first
        // frame blocksize.
        // TODO: should we use an overall counter of frames instead?
        mFrame.ResetStartTimeIfNeeded(mFirstFrame);
      } else if (mNextFrame.IsValid()) {
        mFrame.SetEndOffset(mNextFrame.Offset());
        mFrame.SetEndTime(mNextFrame.Header().Index());
      }
    }

    if (!mFirstFrame.IsValid()) {
      mFirstFrame = mFrame;
    }
    return mFrame.IsValid();
  }

  // Convenience methods to external FlacFrameParser ones.
  bool IsHeaderBlock(const uint8_t* aPacket, size_t aLength) const {
    auto res = mParser.IsHeaderBlock(aPacket, aLength);
    return res.isOk() ? res.unwrap() : false;
  }

  uint32_t HeaderBlockLength(const uint8_t* aPacket) const {
    return mParser.HeaderBlockLength(aPacket);
  }

  bool DecodeHeaderBlock(const uint8_t* aPacket, size_t aLength) {
    return mParser.DecodeHeaderBlock(aPacket, aLength).isOk();
  }

  bool HasFullMetadata() const { return mParser.HasFullMetadata(); }

  AudioInfo Info() const { return mParser.mInfo; }

  // Return a hash table with tag metadata.
  UniquePtr<MetadataTags> GetTags() const { return mParser.GetTags(); }

 private:
  bool GetNextFrame(MediaResourceIndex& aResource) {
    while (mNextFrame.FindNext(aResource)) {
      // Move our offset slightly, so that we don't find the same frame at the
      // next FindNext call.
      aResource.Seek(SEEK_CUR, mNextFrame.Header().Size());
      if (mFrame.IsValid() &&
          mNextFrame.Offset() - mFrame.Offset() < FLAC_MAX_FRAME_SIZE &&
          !CheckCRC16AtOffset(mFrame.Offset(), mNextFrame.Offset(),
                              aResource)) {
        // The frame doesn't match its CRC or would be too far, skip it..
        continue;
      }
      CheckFrameData();
      break;
    }
    return mNextFrame.IsValid();
  }

  bool CheckFrameData() {
    if (mNextFrame.Header().Info().mRate == 0 ||
        mNextFrame.Header().Info().mBitDepth == 0) {
      if (!Info().IsValid()) {
        // We can only use the STREAMINFO data if we have one.
        mNextFrame.SetInvalid();
      } else {
        if (mNextFrame.Header().Info().mRate == 0) {
          mNextFrame.SetRate(Info().mRate);
        }
        if (mNextFrame.Header().Info().mBitDepth == 0) {
          mNextFrame.SetBitDepth(Info().mBitDepth);
        }
      }
    }
    return mNextFrame.IsValid();
  }

  bool CheckCRC16AtOffset(int64_t aStart, int64_t aEnd,
                          MediaResourceIndex& aResource) const {
    int64_t size = aEnd - aStart;
    if (size <= 0) {
      return false;
    }
    UniquePtr<char[]> buffer(new char[static_cast<size_t>(size)]);
    uint32_t read = 0;
    if (NS_FAILED(aResource.ReadAt(aStart, buffer.get(), size, &read)) ||
        read != size) {
      NS_WARNING("Couldn't read frame content");
      return false;
    }

    uint16_t crc = 0;
    uint8_t* buf = reinterpret_cast<uint8_t*>(buffer.get());
    const uint8_t* end = buf + size;
    while (buf < end) {
      crc = CRC16Table[((uint8_t)crc) ^ *buf++] ^ (crc >> 8);
    }
#ifdef FUZZING
    return true;
#else
    return !crc;
#endif
  }

  const uint16_t CRC16Table[256] = {
      0x0000, 0x0580, 0x0F80, 0x0A00, 0x1B80, 0x1E00, 0x1400, 0x1180, 0x3380,
      0x3600, 0x3C00, 0x3980, 0x2800, 0x2D80, 0x2780, 0x2200, 0x6380, 0x6600,
      0x6C00, 0x6980, 0x7800, 0x7D80, 0x7780, 0x7200, 0x5000, 0x5580, 0x5F80,
      0x5A00, 0x4B80, 0x4E00, 0x4400, 0x4180, 0xC380, 0xC600, 0xCC00, 0xC980,
      0xD800, 0xDD80, 0xD780, 0xD200, 0xF000, 0xF580, 0xFF80, 0xFA00, 0xEB80,
      0xEE00, 0xE400, 0xE180, 0xA000, 0xA580, 0xAF80, 0xAA00, 0xBB80, 0xBE00,
      0xB400, 0xB180, 0x9380, 0x9600, 0x9C00, 0x9980, 0x8800, 0x8D80, 0x8780,
      0x8200, 0x8381, 0x8601, 0x8C01, 0x8981, 0x9801, 0x9D81, 0x9781, 0x9201,
      0xB001, 0xB581, 0xBF81, 0xBA01, 0xAB81, 0xAE01, 0xA401, 0xA181, 0xE001,
      0xE581, 0xEF81, 0xEA01, 0xFB81, 0xFE01, 0xF401, 0xF181, 0xD381, 0xD601,
      0xDC01, 0xD981, 0xC801, 0xCD81, 0xC781, 0xC201, 0x4001, 0x4581, 0x4F81,
      0x4A01, 0x5B81, 0x5E01, 0x5401, 0x5181, 0x7381, 0x7601, 0x7C01, 0x7981,
      0x6801, 0x6D81, 0x6781, 0x6201, 0x2381, 0x2601, 0x2C01, 0x2981, 0x3801,
      0x3D81, 0x3781, 0x3201, 0x1001, 0x1581, 0x1F81, 0x1A01, 0x0B81, 0x0E01,
      0x0401, 0x0181, 0x0383, 0x0603, 0x0C03, 0x0983, 0x1803, 0x1D83, 0x1783,
      0x1203, 0x3003, 0x3583, 0x3F83, 0x3A03, 0x2B83, 0x2E03, 0x2403, 0x2183,
      0x6003, 0x6583, 0x6F83, 0x6A03, 0x7B83, 0x7E03, 0x7403, 0x7183, 0x5383,
      0x5603, 0x5C03, 0x5983, 0x4803, 0x4D83, 0x4783, 0x4203, 0xC003, 0xC583,
      0xCF83, 0xCA03, 0xDB83, 0xDE03, 0xD403, 0xD183, 0xF383, 0xF603, 0xFC03,
      0xF983, 0xE803, 0xED83, 0xE783, 0xE203, 0xA383, 0xA603, 0xAC03, 0xA983,
      0xB803, 0xBD83, 0xB783, 0xB203, 0x9003, 0x9583, 0x9F83, 0x9A03, 0x8B83,
      0x8E03, 0x8403, 0x8183, 0x8002, 0x8582, 0x8F82, 0x8A02, 0x9B82, 0x9E02,
      0x9402, 0x9182, 0xB382, 0xB602, 0xBC02, 0xB982, 0xA802, 0xAD82, 0xA782,
      0xA202, 0xE382, 0xE602, 0xEC02, 0xE982, 0xF802, 0xFD82, 0xF782, 0xF202,
      0xD002, 0xD582, 0xDF82, 0xDA02, 0xCB82, 0xCE02, 0xC402, 0xC182, 0x4382,
      0x4602, 0x4C02, 0x4982, 0x5802, 0x5D82, 0x5782, 0x5202, 0x7002, 0x7582,
      0x7F82, 0x7A02, 0x6B82, 0x6E02, 0x6402, 0x6182, 0x2002, 0x2582, 0x2F82,
      0x2A02, 0x3B82, 0x3E02, 0x3402, 0x3182, 0x1382, 0x1602, 0x1C02, 0x1982,
      0x0802, 0x0D82, 0x0782, 0x0202,
  };

  FlacFrameParser mParser;
  // We keep the first parsed frame around for static info access
  // and the currently parsed frame.
  Frame mFirstFrame;
  Frame mNextFrame;
  Frame mFrame;
};

}  // namespace flac

// FlacDemuxer

FlacDemuxer::FlacDemuxer(MediaResource* aSource) : mSource(aSource) {
  DDLINKCHILD("source", aSource);
}

bool FlacDemuxer::InitInternal() {
  if (!mTrackDemuxer) {
    mTrackDemuxer = new FlacTrackDemuxer(mSource);
    DDLINKCHILD("track demuxer", mTrackDemuxer.get());
  }
  return mTrackDemuxer->Init();
}

RefPtr<FlacDemuxer::InitPromise> FlacDemuxer::Init() {
  if (!InitInternal()) {
    LOG("Init() failure: waiting for data");

    return InitPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_DEMUXER_ERR,
                                        __func__);
  }

  LOG("Init() successful");
  return InitPromise::CreateAndResolve(NS_OK, __func__);
}

uint32_t FlacDemuxer::GetNumberTracks(TrackInfo::TrackType aType) const {
  return (aType == TrackInfo::kAudioTrack) ? 1 : 0;
}

already_AddRefed<MediaTrackDemuxer> FlacDemuxer::GetTrackDemuxer(
    TrackInfo::TrackType aType, uint32_t aTrackNumber) {
  if (!mTrackDemuxer) {
    return nullptr;
  }

  return RefPtr<FlacTrackDemuxer>(mTrackDemuxer).forget();
}

bool FlacDemuxer::IsSeekable() const {
  return mTrackDemuxer && mTrackDemuxer->IsSeekable();
}

// FlacTrackDemuxer
FlacTrackDemuxer::FlacTrackDemuxer(MediaResource* aSource)
    : mSource(aSource), mParser(new flac::FrameParser()), mTotalFrameLen(0) {
  DDLINKCHILD("source", aSource);
  Reset();
}

FlacTrackDemuxer::~FlacTrackDemuxer() = default;

bool FlacTrackDemuxer::Init() {
  static const int BUFFER_SIZE = 4096;

  // First check if we have a valid Flac start.
  char buffer[BUFFER_SIZE];
  const uint8_t* ubuffer =  // only needed due to type constraints of ReadAt.
      reinterpret_cast<uint8_t*>(buffer);
  int64_t offset = 0;

  do {
    uint32_t read = 0;
    nsresult ret = mSource.ReadAt(offset, buffer, BUFFER_SIZE, &read);
    if (NS_FAILED(ret) || read < BUFFER_SIZE) {
      // Assume that if we can't read that many bytes while parsing the header,
      // that something is wrong.
      return false;
    }
    if (!mParser->IsHeaderBlock(ubuffer, BUFFER_SIZE)) {
      // Not a header and we haven't reached the end of the metadata blocks.
      // Will fall back to using the frames header instead.
      break;
    }
    uint32_t sizeHeader = mParser->HeaderBlockLength(ubuffer);
    RefPtr<MediaByteBuffer> block = mSource.MediaReadAt(offset, sizeHeader);
    if (!block || block->Length() != sizeHeader) {
      break;
    }
    if (!mParser->DecodeHeaderBlock(block->Elements(), sizeHeader)) {
      break;
    }
    offset += sizeHeader;
  } while (!mParser->HasFullMetadata());

  // First flac frame is found after the metadata.
  // Can seek there immediately to avoid reparsing it all.
  mSource.Seek(SEEK_SET, offset);

  // Find the first frame to fully initialise our parser.
  if (mParser->FindNextFrame(mSource)) {
    // Ensure that the next frame returned will be the first.
    mSource.Seek(SEEK_SET, mParser->FirstFrame().Offset());
    mParser->EndFrameSession();
  } else if (!mParser->Info().IsValid() || !mParser->FirstFrame().IsValid()) {
    // We must find at least a frame to determine the metadata.
    // We can't play this stream.
    return false;
  }

  if (!mParser->Info().IsValid() || !mParser->Info().mDuration.IsPositive()) {
    // Check if we can look at the last frame for the end time to determine the
    // duration when we don't have any.
    TimeAtEnd();
  }

  return true;
}

UniquePtr<TrackInfo> FlacTrackDemuxer::GetInfo() const {
  if (mParser->Info().IsValid()) {
    // We have a proper metadata header.
    UniquePtr<TrackInfo> info = mParser->Info().Clone();
    UniquePtr<MetadataTags> tags(mParser->GetTags());
    if (tags) {
      for (const auto& entry : *tags) {
        info->mTags.AppendElement(MetadataTag(entry.GetKey(), entry.GetData()));
      }
    }
    MOZ_ASSERT(info->IsAudio() &&
                   info->GetAsAudioInfo()
                       ->mCodecSpecificConfig.is<FlacCodecSpecificData>(),
               "Should get flac specific data from parser");
    return info;
  } else if (mParser->FirstFrame().Info().IsValid()) {
    // Use the first frame header.
    UniquePtr<TrackInfo> info = mParser->FirstFrame().Info().Clone();
    info->mDuration = Duration();
    MOZ_ASSERT(info->IsAudio() &&
                   info->GetAsAudioInfo()
                       ->mCodecSpecificConfig.is<FlacCodecSpecificData>(),
               "Should get flac specific data from parser");
    return info;
  }
  return nullptr;
}

bool FlacTrackDemuxer::IsSeekable() const {
  // For now we only allow seeking if a STREAMINFO block was found and with
  // a known number of samples (duration is set).
  return mParser->Info().IsValid() && mParser->Info().mDuration.IsPositive();
}

RefPtr<FlacTrackDemuxer::SeekPromise> FlacTrackDemuxer::Seek(
    const TimeUnit& aTime) {
  // Efficiently seek to the position.
  FastSeek(aTime);
  // Correct seek position by scanning the next frames.
  const TimeUnit seekTime = ScanUntil(aTime);

  return SeekPromise::CreateAndResolve(seekTime, __func__);
}

TimeUnit FlacTrackDemuxer::FastSeek(const TimeUnit& aTime) {
  LOG("FastSeek(%f) avgFrameLen=%f mParsedFramesDuration=%f offset=%" PRId64,
      aTime.ToSeconds(), AverageFrameLength(),
      mParsedFramesDuration.ToSeconds(), GetResourceOffset());

  // Invalidate current frames in the parser.
  mParser->EndFrameSession();

  if (!mParser->FirstFrame().IsValid()) {
    // Something wrong, and there's nothing to seek to anyway, so we can
    // do whatever here.
    mSource.Seek(SEEK_SET, 0);
    return TimeUnit();
  }

  if (aTime <= mParser->FirstFrame().Time()) {
    // We're attempting to seek prior the first frame, return the first frame.
    mSource.Seek(SEEK_SET, mParser->FirstFrame().Offset());
    return mParser->FirstFrame().Time();
  }

  // We look for the seek position using a bisection search, starting where the
  // estimated position might be using the average frame length.
  // Typically, with flac such approximation is typically useless.

  // Estimate where the position might be.
  int64_t pivot =
      aTime.ToSeconds() * AverageFrameLength() + mParser->FirstFrame().Offset();

  // Time in seconds where we can stop seeking and will continue using
  // ScanUntil.
  static const int GAP_THRESHOLD = 5;
  int64_t first = mParser->FirstFrame().Offset();
  int64_t last = mSource.GetLength();
  Maybe<uint64_t> lastFoundOffset;
  uint32_t iterations = 0;
  TimeUnit timeSeekedTo;

  do {
    iterations++;
    mSource.Seek(SEEK_SET, pivot);
    flac::Frame frame;
    if (!frame.FindNext(mSource)) {
      NS_WARNING("We should have found a point");
      break;
    }
    timeSeekedTo = frame.Time();

    LOGV("FastSeek: interation:%u found:%f @ %" PRIu64, iterations,
         timeSeekedTo.ToSeconds(), frame.Offset());

    if (lastFoundOffset && lastFoundOffset.ref() == frame.Offset()) {
      // Same frame found twice. We're done.
      break;
    }
    lastFoundOffset = Some(frame.Offset());

    if (frame.Time() == aTime) {
      break;
    }
    if (aTime > frame.Time() &&
        aTime - frame.Time() <= TimeUnit::FromSeconds(GAP_THRESHOLD)) {
      // We're close enough to the target, experimentation shows that bisection
      // search doesn't help much after that.
      break;
    }
    if (frame.Time() > aTime) {
      last = pivot;
      pivot -= (pivot - first) / 2;
    } else {
      first = pivot;
      pivot += (last - pivot) / 2;
    }
  } while (true);

  if (lastFoundOffset) {
    mSource.Seek(SEEK_SET, lastFoundOffset.ref());
  }

  return timeSeekedTo;
}

TimeUnit FlacTrackDemuxer::ScanUntil(const TimeUnit& aTime) {
  LOG("ScanUntil(%f avgFrameLen=%f mParsedFramesDuration=%f offset=%" PRId64,
      aTime.ToSeconds(), AverageFrameLength(),
      mParsedFramesDuration.ToSeconds(), mParser->CurrentFrame().Offset());

  if (!mParser->FirstFrame().IsValid() ||
      aTime <= mParser->FirstFrame().Time()) {
    return FastSeek(aTime);
  }

  int64_t previousOffset = 0;
  TimeUnit previousTime;
  while (FindNextFrame().IsValid() && mParser->CurrentFrame().Time() < aTime) {
    previousOffset = mParser->CurrentFrame().Offset();
    previousTime = mParser->CurrentFrame().Time();
  }

  if (!mParser->CurrentFrame().IsValid()) {
    // We reached EOS.
    return Duration();
  }

  // Seek back to the last frame found prior the target.
  mParser->EndFrameSession();
  mSource.Seek(SEEK_SET, previousOffset);
  return previousTime;
}

RefPtr<FlacTrackDemuxer::SamplesPromise> FlacTrackDemuxer::GetSamples(
    int32_t aNumSamples) {
  LOGV("GetSamples(%d) Begin offset=%" PRId64
       " mParsedFramesDuration=%f"
       " mTotalFrameLen=%" PRIu64,
       aNumSamples, GetResourceOffset(), mParsedFramesDuration.ToSeconds(),
       mTotalFrameLen);

  if (!aNumSamples) {
    return SamplesPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_DEMUXER_ERR,
                                           __func__);
  }

  RefPtr<SamplesHolder> frames = new SamplesHolder();

  while (aNumSamples--) {
    RefPtr<MediaRawData> frame(GetNextFrame(FindNextFrame()));
    if (!frame) break;
    if (!frame->HasValidTime()) {
      return SamplesPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_DEMUXER_ERR,
                                             __func__);
    }
    frames->AppendSample(frame);
  }

  LOGV("GetSamples() End mSamples.Length=%zu aNumSamples=%d offset=%" PRId64
       " mParsedFramesDuration=%f mTotalFrameLen=%" PRIu64,
       frames->GetSamples().Length(), aNumSamples, GetResourceOffset(),
       mParsedFramesDuration.ToSeconds(), mTotalFrameLen);

  if (frames->GetSamples().IsEmpty()) {
    return SamplesPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_END_OF_STREAM,
                                           __func__);
  }

  return SamplesPromise::CreateAndResolve(frames, __func__);
}

void FlacTrackDemuxer::Reset() {
  LOG("Reset()");
  MOZ_ASSERT(mParser);
  if (mParser->FirstFrame().IsValid()) {
    mSource.Seek(SEEK_SET, mParser->FirstFrame().Offset());
  } else {
    mSource.Seek(SEEK_SET, 0);
  }
  mParser->EndFrameSession();
}

RefPtr<FlacTrackDemuxer::SkipAccessPointPromise>
FlacTrackDemuxer::SkipToNextRandomAccessPoint(const TimeUnit& aTimeThreshold) {
  // Will not be called for audio-only resources.
  return SkipAccessPointPromise::CreateAndReject(
      SkipFailureHolder(NS_ERROR_DOM_MEDIA_DEMUXER_ERR, 0), __func__);
}

int64_t FlacTrackDemuxer::GetResourceOffset() const { return mSource.Tell(); }

TimeIntervals FlacTrackDemuxer::GetBuffered() {
  TimeUnit duration = Duration();

  if (duration <= TimeUnit()) {
    return TimeIntervals();
  }

  // We could simply parse the cached data instead and read the timestamps.
  // However, for now this will do.
  AutoPinned<MediaResource> stream(mSource.GetResource());
  return GetEstimatedBufferedTimeRanges(stream, duration.ToMicroseconds());
}

const flac::Frame& FlacTrackDemuxer::FindNextFrame() {
  LOGV("FindNext() Begin offset=%" PRId64
       " mParsedFramesDuration=%f"
       " mTotalFrameLen=%" PRIu64,
       GetResourceOffset(), mParsedFramesDuration.ToSeconds(), mTotalFrameLen);

  if (mParser->FindNextFrame(mSource)) {
    // Update our current progress stats.
    mParsedFramesDuration =
        std::max(mParsedFramesDuration, mParser->CurrentFrame().Time() -
                                            mParser->FirstFrame().Time() +
                                            mParser->CurrentFrame().Duration());
    mTotalFrameLen =
        std::max<uint64_t>(mTotalFrameLen, mParser->CurrentFrame().Offset() -
                                               mParser->FirstFrame().Offset() +
                                               mParser->CurrentFrame().Size());

    LOGV("FindNext() End time=%f offset=%" PRId64
         " mParsedFramesDuration=%f"
         " mTotalFrameLen=%" PRIu64,
         mParser->CurrentFrame().Time().ToSeconds(), GetResourceOffset(),
         mParsedFramesDuration.ToSeconds(), mTotalFrameLen);
  }

  return mParser->CurrentFrame();
}

already_AddRefed<MediaRawData> FlacTrackDemuxer::GetNextFrame(
    const flac::Frame& aFrame) {
  if (!aFrame.IsValid()) {
    LOG("GetNextFrame() EOS");
    return nullptr;
  }

  LOG("GetNextFrame() Begin(time=%f offset=%" PRId64 " size=%u)",
      aFrame.Time().ToSeconds(), aFrame.Offset(), aFrame.Size());

  const uint64_t offset = aFrame.Offset();
  const uint32_t size = aFrame.Size();

  RefPtr<MediaRawData> frame = new MediaRawData();
  frame->mOffset = offset;

  UniquePtr<MediaRawDataWriter> frameWriter(frame->CreateWriter());
  if (!frameWriter->SetSize(size)) {
    LOG("GetNext() Exit failed to allocated media buffer");
    return nullptr;
  }

  const uint32_t read = Read(frameWriter->Data(), offset, size);
  if (read != size) {
    LOG("GetNextFrame() Exit read=%u frame->Size=%zu", read, frame->Size());
    return nullptr;
  }

  frame->mTime = aFrame.Time();
  frame->mDuration = aFrame.Duration();
  frame->mTimecode = frame->mTime;
  frame->mOffset = aFrame.Offset();
  frame->mKeyframe = true;

  MOZ_ASSERT(!frame->mTime.IsNegative());
  MOZ_ASSERT(!frame->mDuration.IsNegative());

  return frame.forget();
}

uint32_t FlacTrackDemuxer::Read(uint8_t* aBuffer, int64_t aOffset,
                                int32_t aSize) {
  uint32_t read = 0;
  const nsresult rv = mSource.ReadAt(aOffset, reinterpret_cast<char*>(aBuffer),
                                     static_cast<uint32_t>(aSize), &read);
  NS_ENSURE_SUCCESS(rv, 0);
  return read;
}

double FlacTrackDemuxer::AverageFrameLength() const {
  if (mParsedFramesDuration.ToMicroseconds()) {
    return mTotalFrameLen / mParsedFramesDuration.ToSeconds();
  }

  return 0.0;
}

TimeUnit FlacTrackDemuxer::Duration() const {
  return std::max(mParsedFramesDuration, mParser->Info().mDuration);
}

TimeUnit FlacTrackDemuxer::TimeAtEnd() {
  // Scan the last 128kB if available to determine the last frame.
  static const int OFFSET_FROM_END = 128 * 1024;

  // Seek to the end of the file and attempt to find the last frame.
  MediaResourceIndex source(mSource.GetResource());
  TimeUnit previousDuration;
  TimeUnit previousTime;

  const int64_t streamLen = mSource.GetLength();
  if (streamLen < 0) {
    return TimeUnit::FromInfinity();
  }

  flac::FrameParser parser;

  source.Seek(SEEK_SET, std::max<int64_t>(0LL, streamLen - OFFSET_FROM_END));
  while (parser.FindNextFrame(source)) {
    // FFmpeg flac muxer can generate a last frame with earlier than the others.
    previousTime = std::max(previousTime, parser.CurrentFrame().Time());
    if (parser.CurrentFrame().Duration() > TimeUnit()) {
      // The last frame doesn't have a duration, so only update our duration
      // if we do have one.
      previousDuration = parser.CurrentFrame().Duration();
    }
    if (source.Tell() >= streamLen) {
      // Limit the read, in case the length change half-way.
      break;
    }
  }

  // Update our current progress stats.
  mParsedFramesDuration =
      previousTime + previousDuration - mParser->FirstFrame().Time();

  mTotalFrameLen =
      static_cast<uint64_t>(streamLen) - mParser->FirstFrame().Offset();

  return mParsedFramesDuration;
}

/* static */
bool FlacDemuxer::FlacSniffer(const uint8_t* aData, const uint32_t aLength) {
  if (aLength < FLAC_MIN_FRAME_SIZE) {
    return false;
  }

  flac::Frame frame;
  return frame.FindNext(aData, aLength) >= 0;
}

}  // namespace mozilla
