/* This Source Code Form is subject to the terms of the Mozilla Public
 * Licence, 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/. */

#ifndef WAV_DEMUXER_H_
#define WAV_DEMUXER_H_

#include "mozilla/Attributes.h"
#include "mozilla/Maybe.h"
#include "MediaDataDemuxer.h"
#include "MediaResource.h"
#include "mp4_demuxer/ByteReader.h"

using mp4_demuxer::ByteReader;

namespace mozilla {

static const uint32_t FRMT_CODE = 0x666d7420;
static const uint32_t DATA_CODE = 0x64617461;
static const uint32_t LIST_CODE = 0x4c495354;
static const uint32_t INFO_CODE = 0x494e464f;

static const uint8_t RIFF[4] = {'R', 'I', 'F', 'F'};
static const uint8_t WAVE[4] = {'W', 'A', 'V', 'E'};

static const uint16_t RIFF_CHUNK_SIZE = 12;
static const uint16_t CHUNK_HEAD_SIZE = 8;
static const uint16_t FMT_CHUNK_MIN_SIZE = 16;
static const uint16_t DATA_CHUNK_SIZE = 768;

class WAVTrackDemuxer;

class WAVDemuxer : public MediaDataDemuxer {
public:
  // MediaDataDemuxer interface.
  explicit WAVDemuxer(MediaResource* aSource);
  RefPtr<InitPromise> Init() override;
  bool HasTrackType(TrackInfo::TrackType aType) const override;
  uint32_t GetNumberTracks(TrackInfo::TrackType aType) const override;
  already_AddRefed<MediaTrackDemuxer> GetTrackDemuxer(
      TrackInfo::TrackType aType, uint32_t aTrackNumber) override;
  bool IsSeekable() const override;

private:
  // Synchronous Initialization.
  bool InitInternal();

  MediaResourceIndex mSource;
  RefPtr<WAVTrackDemuxer> mTrackDemuxer;
};

class RIFFParser {
private:
  class RIFFHeader;
public:
  const RIFFHeader& RiffHeader() const;

  uint32_t Parse(ByteReader& aReader);

  void Reset();

private:
  class RIFFHeader {
  public:
    RIFFHeader();
    void Reset();

    bool IsValid() const;
    bool IsValid(int aPos) const;

    bool ParseNext(uint8_t c);

  private:
    bool Update(uint8_t c);

    uint8_t mRaw[RIFF_CHUNK_SIZE];

    int mPos;
  };

  RIFFHeader mRiffHeader;
};

class HeaderParser {
private:
  class ChunkHeader;
public:
  const ChunkHeader& GiveHeader() const;

  uint32_t Parse(ByteReader& aReader);

  void Reset();

private:
  class ChunkHeader {
  public:
    ChunkHeader();
    void Reset();

    bool IsValid() const;

    uint32_t ChunkName() const;
    uint32_t ChunkSize() const;

    bool ParseNext(uint8_t c);

  private:
    void Update(uint8_t c);

    uint8_t mRaw[CHUNK_HEAD_SIZE];

    int mPos;
  };

  ChunkHeader mHeader;
};

class FormatParser {
private:
  class FormatChunk;
public:
  const FormatChunk& FmtChunk() const;

  uint32_t Parse(ByteReader& aReader);

  void Reset();

private:
  class FormatChunk {
  public:
    FormatChunk();
    void Reset();

    uint16_t WaveFormat() const;
    uint16_t Channels() const;
    uint32_t SampleRate() const;
    uint16_t FrameSize() const;
    uint16_t SampleFormat() const;

    bool IsValid() const;
    bool ParseNext(uint8_t c);

  private:
    void Update(uint8_t c);

    uint8_t mRaw[FMT_CHUNK_MIN_SIZE];

    int mPos;
  };

  FormatChunk mFmtChunk;
};

class DataParser {
private:
  class DataChunk;
public:
  DataParser();

  const DataChunk& CurrentChunk() const;

  void Reset();

private:
  class DataChunk {
  public:
    void Reset();
  private:
    int mPos; // To Check Alignment
  };

  DataChunk mChunk;
};

class WAVTrackDemuxer : public MediaTrackDemuxer {
public:
  explicit WAVTrackDemuxer(MediaResourceIndex aSource);

  bool Init();

  int64_t StreamLength() const;

  media::TimeUnit Duration() const;
  media::TimeUnit Duration(int64_t aNumDataChunks) const;
  media::TimeUnit DurationFromBytes(uint32_t aNumBytes) const;

  media::TimeUnit SeekPosition() const;

  RefPtr<MediaRawData> DemuxSample();

  // MediaTrackDemuxer interface.
  UniquePtr<TrackInfo> GetInfo() const override;
  RefPtr<SeekPromise> Seek(media::TimeUnit aTime) override;
  RefPtr<SamplesPromise> GetSamples(int32_t aNumSamples) override;
  void Reset() override;
  RefPtr<SkipAccessPointPromise> SkipToNextRandomAccessPoint(
    media::TimeUnit aTimeThreshold) override;
  int64_t GetResourceOffset() const override;
  media::TimeIntervals GetBuffered() override;

private:
  ~WAVTrackDemuxer() {}

  media::TimeUnit FastSeek(const media::TimeUnit& aTime);
  media::TimeUnit ScanUntil(const media::TimeUnit& aTime);

  MediaByteRange FindNextChunk();

  MediaByteRange FindChunkHeader();
  MediaByteRange FindRIFFHeader();
  MediaByteRange FindFmtChunk();
  MediaByteRange FindListChunk();
  MediaByteRange FindInfoTag();

  bool RIFFParserInit();
  bool HeaderParserInit();
  bool FmtChunkParserInit();
  bool ListChunkParserInit(uint32_t aChunkSize);

  bool SkipNextChunk(const MediaByteRange& aRange);

  already_AddRefed<MediaRawData> GetNextChunk(const MediaByteRange& aRange);
  already_AddRefed<MediaRawData> GetFileHeader(const MediaByteRange& aRange);

  void UpdateState(const MediaByteRange& aRange);

  int64_t OffsetFromChunkIndex(int64_t aChunkIndex) const;
  int64_t ChunkIndexFromOffset(int64_t aOffet) const;
  int64_t ChunkIndexFromTime(const media::TimeUnit& aTime) const;

  int32_t Read(uint8_t* aBuffer, int64_t aOffset, int32_t aSize);

  MediaResourceIndex mSource;

  DataParser mParser;
  RIFFParser mRIFFParser;
  HeaderParser mHeaderParser;

  FormatParser mFmtParser;
  // ListChunkParser mListChunkParser;

  int64_t mOffset;
  int64_t mFirstChunkOffset;

  uint32_t mNumParsedChunks;
  int32_t mChunkIndex;

  uint32_t mDataLength;
  uint64_t mTotalChunkLen;

  int32_t mSamplesPerChunk;
  int32_t mSamplesPerSecond;

  int32_t mChannels;
  int32_t mSampleFormat;

  UniquePtr<AudioInfo> mInfo;
};

} // namespace mozilla

#endif