/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-*/
/* 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 <climits>
#include "TrackMetadataBase.h"
#include "ISOMediaBoxes.h"
#include "ISOControl.h"
#include "ISOMediaWriter.h"
#include "EncodedFrameContainer.h"
#include "ISOTrackMetadata.h"
#include "MP4ESDS.h"
#include "AMRBox.h"
#include "AVCBox.h"
#include "EVRCBox.h"
#include "VideoUtils.h"

namespace mozilla {

// 14496-12 6.2.2 'Data Types and fields'
const uint32_t iso_matrix[] = { 0x00010000, 0,          0,
                                0,          0x00010000, 0,
                                0,          0,          0x40000000 };

uint32_t
set_sample_flags(bool aSync)
{
  std::bitset<32> flags;
  flags.set(16, !aSync);
  return flags.to_ulong();
}

Box::BoxSizeChecker::BoxSizeChecker(ISOControl* aControl, uint32_t aSize)
{
  mControl = aControl;
  ori_size = mControl->GetBufPos();
  box_size = aSize;
  MOZ_COUNT_CTOR(BoxSizeChecker);
}

Box::BoxSizeChecker::~BoxSizeChecker()
{
  uint32_t cur_size = mControl->GetBufPos();
  if ((cur_size - ori_size) != box_size) {
    MOZ_ASSERT(false);
  }

  MOZ_COUNT_DTOR(BoxSizeChecker);
}

nsresult
MediaDataBox::Generate(uint32_t* aBoxSize)
{
  mFirstSampleOffset = size;
  mAllSampleSize = 0;

  if (mTrackType & Audio_Track) {
    FragmentBuffer* frag = mControl->GetFragment(Audio_Track);
    mAllSampleSize += frag->GetFirstFragmentSampleSize();
  }
  if (mTrackType & Video_Track) {
    FragmentBuffer* frag = mControl->GetFragment(Video_Track);
    mAllSampleSize += frag->GetFirstFragmentSampleSize();
  }

  size += mAllSampleSize;
  *aBoxSize = size;
  return NS_OK;
}

nsresult
MediaDataBox::Write()
{
  nsresult rv;
  BoxSizeChecker checker(mControl, size);
  Box::Write();
  nsTArray<uint32_t> types;
  types.AppendElement(Audio_Track);
  types.AppendElement(Video_Track);

  for (uint32_t l = 0; l < types.Length(); l++) {
    if (mTrackType & types[l]) {
      FragmentBuffer* frag = mControl->GetFragment(types[l]);
      nsTArray<RefPtr<EncodedFrame>> frames;

      // Here is the last time we get fragment frames, flush it!
      rv = frag->GetFirstFragment(frames, true);
      NS_ENSURE_SUCCESS(rv, rv);

      uint32_t len = frames.Length();
      for (uint32_t i = 0; i < len; i++) {
        nsTArray<uint8_t> frame_buffer;
        frames.ElementAt(i)->SwapOutFrameData(frame_buffer);
        mControl->WriteAVData(frame_buffer);
      }
    }
  }

  return NS_OK;
}

MediaDataBox::MediaDataBox(uint32_t aTrackType, ISOControl* aControl)
  : Box(NS_LITERAL_CSTRING("mdat"), aControl)
  , mAllSampleSize(0)
  , mFirstSampleOffset(0)
  , mTrackType(aTrackType)
{
  MOZ_COUNT_CTOR(MediaDataBox);
}

MediaDataBox::~MediaDataBox()
{
  MOZ_COUNT_DTOR(MediaDataBox);
}

uint32_t
TrackRunBox::fillSampleTable()
{
  uint32_t table_size = 0;
  nsresult rv;
  nsTArray<RefPtr<EncodedFrame>> frames;
  FragmentBuffer* frag = mControl->GetFragment(mTrackType);

  rv = frag->GetFirstFragment(frames);
  if (NS_FAILED(rv)) {
    return 0;
  }
  uint32_t len = frames.Length();
  sample_info_table = MakeUnique<tbl[]>(len);
  // Create sample table according to 14496-12 8.8.8.2.
  for (uint32_t i = 0; i < len; i++) {
    // Sample size.
    sample_info_table[i].sample_size = 0;
    if (flags.to_ulong() & flags_sample_size_present) {
      sample_info_table[i].sample_size = frames.ElementAt(i)->GetFrameData().Length();
      mAllSampleSize += sample_info_table[i].sample_size;
      table_size += sizeof(uint32_t);
    }

    // Sample flags.
    sample_info_table[i].sample_flags = 0;
    if (flags.to_ulong() & flags_sample_flags_present) {
      sample_info_table[i].sample_flags =
        set_sample_flags(
          (frames.ElementAt(i)->GetFrameType() == EncodedFrame::AVC_I_FRAME));
      table_size += sizeof(uint32_t);
    }

    // Sample duration.
    sample_info_table[i].sample_duration = 0;
    if (flags.to_ulong() & flags_sample_duration_present) {
      // Calculate each frame's duration, it is decided by "current frame
      // timestamp - last frame timestamp".
      uint64_t frame_time = 0;
      if (i == 0) {
        frame_time = frames.ElementAt(i)->GetTimeStamp() -
                     frag->GetLastFragmentLastFrameTime();
      } else {
        frame_time = frames.ElementAt(i)->GetTimeStamp() -
                     frames.ElementAt(i - 1)->GetTimeStamp();
        // Keep the last frame time of current fagment, it will be used to calculate
        // the first frame duration of next fragment.
        if ((len - 1) == i) {
          frag->SetLastFragmentLastFrameTime(frames.ElementAt(i)->GetTimeStamp());
        }
      }

      // In TrackRunBox, there should be exactly one type, either audio or video.
      MOZ_ASSERT((mTrackType & Video_Track) ^ (mTrackType & Audio_Track));
      sample_info_table[i].sample_duration = (mTrackType & Video_Track ?
        frame_time * mVideoMeta->GetVideoClockRate() / USECS_PER_S :
        frame_time * mAudioMeta->GetAudioSampleRate() / USECS_PER_S);

      table_size += sizeof(uint32_t);
    }

    sample_info_table[i].sample_composition_time_offset = 0;
  }
  return table_size;
}

nsresult
TrackRunBox::Generate(uint32_t* aBoxSize)
{
  FragmentBuffer* frag = mControl->GetFragment(mTrackType);
  sample_count = frag->GetFirstFragmentSampleNumber();
  size += sizeof(sample_count);

  // data_offset needs to be updated if there is other
  // TrackRunBox before this one.
  if (flags.to_ulong() & flags_data_offset_present) {
    data_offset = 0;
    size += sizeof(data_offset);
  }
  size += fillSampleTable();

  *aBoxSize = size;

  return NS_OK;
}

nsresult
TrackRunBox::SetDataOffset(uint32_t aOffset)
{
  data_offset = aOffset;
  return NS_OK;
}

nsresult
TrackRunBox::Write()
{
  WRITE_FULLBOX(mControl, size)
  mControl->Write(sample_count);
  if (flags.to_ulong() & flags_data_offset_present) {
    mControl->Write(data_offset);
  }
  for (uint32_t i = 0; i < sample_count; i++) {
    if (flags.to_ulong() & flags_sample_duration_present) {
      mControl->Write(sample_info_table[i].sample_duration);
    }
    if (flags.to_ulong() & flags_sample_size_present) {
      mControl->Write(sample_info_table[i].sample_size);
    }
    if (flags.to_ulong() & flags_sample_flags_present) {
      mControl->Write(sample_info_table[i].sample_flags);
    }
  }

  return NS_OK;
}

TrackRunBox::TrackRunBox(uint32_t aType, uint32_t aFlags, ISOControl* aControl)
  : FullBox(NS_LITERAL_CSTRING("trun"), 0, aFlags, aControl)
  , sample_count(0)
  , data_offset(0)
  , first_sample_flags(0)
  , mAllSampleSize(0)
  , mTrackType(aType)
{
  MOZ_COUNT_CTOR(TrackRunBox);
}

TrackRunBox::~TrackRunBox()
{
  MOZ_COUNT_DTOR(TrackRunBox);
}

nsresult
TrackFragmentHeaderBox::UpdateBaseDataOffset(uint64_t aOffset)
{
  base_data_offset = aOffset;
  return NS_OK;
}

nsresult
TrackFragmentHeaderBox::Generate(uint32_t* aBoxSize)
{
  track_ID = (mTrackType == Audio_Track ?
                mControl->GetTrackID(mAudioMeta->GetKind()) :
                mControl->GetTrackID(mVideoMeta->GetKind()));
  size += sizeof(track_ID);

  if (flags.to_ulong() & base_data_offset_present) {
    // base_data_offset needs to add size of 'trun', 'tfhd' and
    // header of 'mdat' later.
    base_data_offset = 0;
    size += sizeof(base_data_offset);
  }
  if (flags.to_ulong() & default_sample_duration_present) {
    if (mTrackType == Video_Track) {
      if (!mVideoMeta->GetVideoFrameRate()) {
        // 0 means frame rate is variant, so it is wrong to write
        // default_sample_duration.
        MOZ_ASSERT(0);
        default_sample_duration = 0;
      } else {
        default_sample_duration = mVideoMeta->GetVideoClockRate() / mVideoMeta->GetVideoFrameRate();
      }
    } else if (mTrackType == Audio_Track) {
      default_sample_duration = mAudioMeta->GetAudioFrameDuration();
    } else {
      MOZ_ASSERT(0);
      return NS_ERROR_FAILURE;
    }
    size += sizeof(default_sample_duration);
  }
  *aBoxSize = size;
  return NS_OK;
}

nsresult
TrackFragmentHeaderBox::Write()
{
  WRITE_FULLBOX(mControl, size)
  mControl->Write(track_ID);
  if (flags.to_ulong() & base_data_offset_present) {
    mControl->Write(base_data_offset);
  }
  if (flags.to_ulong() & default_sample_duration_present) {
    mControl->Write(default_sample_duration);
  }
  return NS_OK;
}

TrackFragmentHeaderBox::TrackFragmentHeaderBox(uint32_t aType,
                                               uint32_t aFlags,
                                               ISOControl* aControl)
  : FullBox(NS_LITERAL_CSTRING("tfhd"), 0, aFlags, aControl)
  , track_ID(0)
  , base_data_offset(0)
  , default_sample_duration(0)
{
  mTrackType = aType;
  MOZ_COUNT_CTOR(TrackFragmentHeaderBox);
}

TrackFragmentHeaderBox::~TrackFragmentHeaderBox()
{
  MOZ_COUNT_DTOR(TrackFragmentHeaderBox);
}

TrackFragmentBox::TrackFragmentBox(uint32_t aType, ISOControl* aControl)
  : DefaultContainerImpl(NS_LITERAL_CSTRING("traf"), aControl)
  , mTrackType(aType)
{
  // Flags in TrackFragmentHeaderBox.
  uint32_t tf_flags = base_data_offset_present;

  // Ideally, audio encoder generates audio frame in const rate. However, some
  // audio encoders don't do it so the audio frame duration needs to be checked
  // here.
  if ((mTrackType & Audio_Track) && mAudioMeta->GetAudioFrameDuration()) {
    tf_flags |= default_sample_duration_present;
  }

  boxes.AppendElement(new TrackFragmentHeaderBox(aType, tf_flags, aControl));

  // Always adds flags_data_offset_present in each TrackRunBox, Android
  // parser requires this flag to calculate the correct bitstream offset.
  uint32_t tr_flags = flags_sample_size_present | flags_data_offset_present;

  // Flags in TrackRunBox.
  // If there is no default sample duration exists, each frame duration needs to
  // be recored in the TrackRunBox.
  tr_flags |= (tf_flags & default_sample_duration_present ? 0 : flags_sample_duration_present);

  // For video, add sample_flags to record I frame.
  tr_flags |= (mTrackType & Video_Track ? flags_sample_flags_present : 0);

  boxes.AppendElement(new TrackRunBox(mTrackType, tr_flags, aControl));
  MOZ_COUNT_CTOR(TrackFragmentBox);
}

TrackFragmentBox::~TrackFragmentBox()
{
  MOZ_COUNT_DTOR(TrackFragmentBox);
}

nsresult
MovieFragmentHeaderBox::Generate(uint32_t* aBoxSize)
{
  sequence_number = mControl->GetCurFragmentNumber();
  size += sizeof(sequence_number);
  *aBoxSize = size;
  return NS_OK;
}

nsresult
MovieFragmentHeaderBox::Write()
{
  WRITE_FULLBOX(mControl, size)
  mControl->Write(sequence_number);
  return NS_OK;
}

MovieFragmentHeaderBox::MovieFragmentHeaderBox(uint32_t aTrackType,
                                               ISOControl* aControl)
  : FullBox(NS_LITERAL_CSTRING("mfhd"), 0, 0, aControl)
  , sequence_number(0)
  , mTrackType(aTrackType)
{
  MOZ_COUNT_CTOR(MovieFragmentHeaderBox);
}

MovieFragmentHeaderBox::~MovieFragmentHeaderBox()
{
  MOZ_COUNT_DTOR(MovieFragmentHeaderBox);
}

MovieFragmentBox::MovieFragmentBox(uint32_t aType, ISOControl* aControl)
  : DefaultContainerImpl(NS_LITERAL_CSTRING("moof"), aControl)
  , mTrackType(aType)
{
  boxes.AppendElement(new MovieFragmentHeaderBox(mTrackType, aControl));

  if (mTrackType & Audio_Track) {
    boxes.AppendElement(
      new TrackFragmentBox(Audio_Track, aControl));
  }
  if (mTrackType & Video_Track) {
    boxes.AppendElement(
      new TrackFragmentBox(Video_Track, aControl));
  }
  MOZ_COUNT_CTOR(MovieFragmentBox);
}

MovieFragmentBox::~MovieFragmentBox()
{
  MOZ_COUNT_DTOR(MovieFragmentBox);
}

nsresult
MovieFragmentBox::Generate(uint32_t* aBoxSize)
{
  nsresult rv = DefaultContainerImpl::Generate(aBoxSize);
  NS_ENSURE_SUCCESS(rv, rv);

  // Correct data_offset if there are both audio and video track in
  // this fragment. This offset means the offset in the MediaDataBox.
  if (mTrackType & (Audio_Track | Video_Track)) {
    nsTArray<RefPtr<MuxerOperation>> truns;
    rv = Find(NS_LITERAL_CSTRING("trun"), truns);
    NS_ENSURE_SUCCESS(rv, rv);
    uint32_t len = truns.Length();
    uint32_t data_offset = 0;
    for (uint32_t i = 0; i < len; i++) {
      TrackRunBox* trun = (TrackRunBox*) truns.ElementAt(i).get();
      rv = trun->SetDataOffset(data_offset);
      NS_ENSURE_SUCCESS(rv, rv);
      data_offset += trun->GetAllSampleSize();
    }
  }

  return NS_OK;
}

nsresult
TrackExtendsBox::Generate(uint32_t* aBoxSize)
{
  track_ID = (mTrackType == Audio_Track ?
                mControl->GetTrackID(mAudioMeta->GetKind()) :
                mControl->GetTrackID(mVideoMeta->GetKind()));

  if (mTrackType == Audio_Track) {
    default_sample_description_index = 1;
    default_sample_duration = mAudioMeta->GetAudioFrameDuration();
    default_sample_size = mAudioMeta->GetAudioFrameSize();
    default_sample_flags = set_sample_flags(1);
  } else if (mTrackType == Video_Track) {
    default_sample_description_index = 1;
    // Video meta data has assigned framerate, it implies that this video's
    // frame rate should be fixed.
    if (mVideoMeta->GetVideoFrameRate()) {
      default_sample_duration =
        mVideoMeta->GetVideoClockRate() / mVideoMeta->GetVideoFrameRate();
    }
    default_sample_size = 0;
    default_sample_flags = set_sample_flags(0);
  } else {
    MOZ_ASSERT(0);
    return NS_ERROR_FAILURE;
  }

  size += sizeof(track_ID) +
          sizeof(default_sample_description_index) +
          sizeof(default_sample_duration) +
          sizeof(default_sample_size) +
          sizeof(default_sample_flags);

  *aBoxSize = size;

  return NS_OK;
}

nsresult
TrackExtendsBox::Write()
{
  WRITE_FULLBOX(mControl, size)
  mControl->Write(track_ID);
  mControl->Write(default_sample_description_index);
  mControl->Write(default_sample_duration);
  mControl->Write(default_sample_size);
  mControl->Write(default_sample_flags);

  return NS_OK;
}

TrackExtendsBox::TrackExtendsBox(uint32_t aType, ISOControl* aControl)
  : FullBox(NS_LITERAL_CSTRING("trex"), 0, 0, aControl)
  , track_ID(0)
  , default_sample_description_index(0)
  , default_sample_duration(0)
  , default_sample_size(0)
  , default_sample_flags(0)
  , mTrackType(aType)
{
  MOZ_COUNT_CTOR(TrackExtendsBox);
}

TrackExtendsBox::~TrackExtendsBox()
{
  MOZ_COUNT_DTOR(TrackExtendsBox);
}

MovieExtendsBox::MovieExtendsBox(ISOControl* aControl)
  : DefaultContainerImpl(NS_LITERAL_CSTRING("mvex"), aControl)
{
  if (mAudioMeta) {
    boxes.AppendElement(new TrackExtendsBox(Audio_Track, aControl));
  }
  if (mVideoMeta) {
    boxes.AppendElement(new TrackExtendsBox(Video_Track, aControl));
  }
  MOZ_COUNT_CTOR(MovieExtendsBox);
}

MovieExtendsBox::~MovieExtendsBox()
{
  MOZ_COUNT_DTOR(MovieExtendsBox);
}

nsresult
ChunkOffsetBox::Generate(uint32_t* aBoxSize)
{
  // We don't need time to sample table in fragmented mp4.
  entry_count = 0;
  size += sizeof(entry_count);
  *aBoxSize = size;
  return NS_OK;
}

nsresult
ChunkOffsetBox::Write()
{
  WRITE_FULLBOX(mControl, size)
  mControl->Write(entry_count);
  return NS_OK;
}

ChunkOffsetBox::ChunkOffsetBox(uint32_t aType, ISOControl* aControl)
  : FullBox(NS_LITERAL_CSTRING("stco"), 0, 0, aControl)
  , entry_count(0)
{
  MOZ_COUNT_CTOR(ChunkOffsetBox);
}

ChunkOffsetBox::~ChunkOffsetBox()
{
  MOZ_COUNT_DTOR(ChunkOffsetBox);
}

nsresult
SampleToChunkBox::Generate(uint32_t* aBoxSize)
{
  // We don't need time to sample table in fragmented mp4
  entry_count = 0;
  size += sizeof(entry_count);
  *aBoxSize = size;
  return NS_OK;
}

nsresult
SampleToChunkBox::Write()
{
  WRITE_FULLBOX(mControl, size)
  mControl->Write(entry_count);
  return NS_OK;
}

SampleToChunkBox::SampleToChunkBox(uint32_t aType, ISOControl* aControl)
  : FullBox(NS_LITERAL_CSTRING("stsc"), 0, 0, aControl)
  , entry_count(0)
{
  MOZ_COUNT_CTOR(SampleToChunkBox);
}

SampleToChunkBox::~SampleToChunkBox()
{
  MOZ_COUNT_DTOR(SampleToChunkBox);
}

nsresult
TimeToSampleBox::Generate(uint32_t* aBoxSize)
{
  // We don't need time to sample table in fragmented mp4.
  entry_count = 0;
  size += sizeof(entry_count);
  *aBoxSize = size;
  return NS_OK;
}

nsresult
TimeToSampleBox::Write()
{
  WRITE_FULLBOX(mControl, size)
  mControl->Write(entry_count);
  return NS_OK;
}

TimeToSampleBox::TimeToSampleBox(uint32_t aType, ISOControl* aControl)
  : FullBox(NS_LITERAL_CSTRING("stts"), 0, 0, aControl)
  , entry_count(0)
{
  MOZ_COUNT_CTOR(TimeToSampleBox);
}

TimeToSampleBox::~TimeToSampleBox()
{
  MOZ_COUNT_DTOR(TimeToSampleBox);
}

nsresult
SampleDescriptionBox::Generate(uint32_t* aBoxSize)
{
  entry_count = 1;
  size += sizeof(entry_count);

  nsresult rv;
  uint32_t box_size;
  rv = sample_entry_box->Generate(&box_size);
  NS_ENSURE_SUCCESS(rv, rv);
  size += box_size;
  *aBoxSize = size;

  return NS_OK;
}

nsresult
SampleDescriptionBox::Write()
{
  WRITE_FULLBOX(mControl, size)
  nsresult rv;
  mControl->Write(entry_count);
  rv = sample_entry_box->Write();
  NS_ENSURE_SUCCESS(rv, rv);

  return NS_OK;
}

SampleDescriptionBox::SampleDescriptionBox(uint32_t aType, ISOControl* aControl)
  : FullBox(NS_LITERAL_CSTRING("stsd"), 0, 0, aControl)
  , entry_count(0)
{
  mTrackType = aType;

  switch (mTrackType) {
  case Audio_Track:
    {
      CreateAudioSampleEntry(sample_entry_box);
    }
    break;
  case Video_Track:
    {
      CreateVideoSampleEntry(sample_entry_box);
    }
    break;
  }
  MOZ_ASSERT(sample_entry_box);
  MOZ_COUNT_CTOR(SampleDescriptionBox);
}

nsresult
SampleDescriptionBox::CreateAudioSampleEntry(RefPtr<SampleEntryBox>& aSampleEntry)
{
  if (mAudioMeta->GetKind() == TrackMetadataBase::METADATA_AMR) {
    aSampleEntry = new AMRSampleEntry(mControl);
  } else if (mAudioMeta->GetKind() == TrackMetadataBase::METADATA_AAC) {
    aSampleEntry = new MP4AudioSampleEntry(mControl);
  } else if (mAudioMeta->GetKind() == TrackMetadataBase::METADATA_EVRC) {
    aSampleEntry = new EVRCSampleEntry(mControl);
  } else {
    MOZ_ASSERT(0);
  }
  return NS_OK;
}

nsresult
SampleDescriptionBox::CreateVideoSampleEntry(RefPtr<SampleEntryBox>& aSampleEntry)
{
  if (mVideoMeta->GetKind() == TrackMetadataBase::METADATA_AVC) {
    aSampleEntry = new AVCSampleEntry(mControl);
  } else {
    MOZ_ASSERT(0);
  }
  return NS_OK;
}

SampleDescriptionBox::~SampleDescriptionBox()
{
  MOZ_COUNT_DTOR(SampleDescriptionBox);
}

nsresult
SampleSizeBox::Generate(uint32_t* aBoxSize)
{
  size += sizeof(sample_size) +
          sizeof(sample_count);
  *aBoxSize = size;
  return NS_OK;
}

nsresult
SampleSizeBox::Write()
{
  WRITE_FULLBOX(mControl, size)
  mControl->Write(sample_size);
  mControl->Write(sample_count);
  return NS_OK;
}

SampleSizeBox::SampleSizeBox(ISOControl* aControl)
  : FullBox(NS_LITERAL_CSTRING("stsz"), 0, 0, aControl)
  , sample_size(0)
  , sample_count(0)
{
  MOZ_COUNT_CTOR(SampleSizeBox);
}

SampleSizeBox::~SampleSizeBox()
{
  MOZ_COUNT_DTOR(SampleSizeBox);
}

SampleTableBox::SampleTableBox(uint32_t aType, ISOControl* aControl)
  : DefaultContainerImpl(NS_LITERAL_CSTRING("stbl"), aControl)
{
  boxes.AppendElement(new SampleDescriptionBox(aType, aControl));
  boxes.AppendElement(new TimeToSampleBox(aType, aControl));
  boxes.AppendElement(new SampleToChunkBox(aType, aControl));
  boxes.AppendElement(new SampleSizeBox(aControl));
  boxes.AppendElement(new ChunkOffsetBox(aType, aControl));
  MOZ_COUNT_CTOR(SampleTableBox);
}

SampleTableBox::~SampleTableBox()
{
  MOZ_COUNT_DTOR(SampleTableBox);
}

nsresult
DataEntryUrlBox::Generate(uint32_t* aBoxSize)
{
  // location is null here, do nothing
  size += location.Length();
  *aBoxSize = size;

  return NS_OK;
}

nsresult
DataEntryUrlBox::Write()
{
  WRITE_FULLBOX(mControl, size)
  return NS_OK;
}

DataEntryUrlBox::DataEntryUrlBox()
  : FullBox(NS_LITERAL_CSTRING("url "), 0, 0, (ISOControl*) nullptr)
{
  MOZ_COUNT_CTOR(DataEntryUrlBox);
}

DataEntryUrlBox::DataEntryUrlBox(ISOControl* aControl)
  : FullBox(NS_LITERAL_CSTRING("url "), 0, flags_media_at_the_same_file, aControl)
{
  MOZ_COUNT_CTOR(DataEntryUrlBox);
}

DataEntryUrlBox::DataEntryUrlBox(const DataEntryUrlBox& aBox)
  : FullBox(aBox.boxType, aBox.version, aBox.flags.to_ulong(), aBox.mControl)
{
  location = aBox.location;
  MOZ_COUNT_CTOR(DataEntryUrlBox);
}

DataEntryUrlBox::~DataEntryUrlBox()
{
  MOZ_COUNT_DTOR(DataEntryUrlBox);
}

nsresult DataReferenceBox::Generate(uint32_t* aBoxSize)
{
  entry_count = 1;  // only allow on entry here
  size += sizeof(uint32_t);

  for (uint32_t i = 0; i < entry_count; i++) {
    uint32_t box_size = 0;
    DataEntryUrlBox* url = new DataEntryUrlBox(mControl);
    url->Generate(&box_size);
    size += box_size;
    urls.AppendElement(url);
  }

  *aBoxSize = size;

  return NS_OK;
}

nsresult DataReferenceBox::Write()
{
  WRITE_FULLBOX(mControl, size)
  mControl->Write(entry_count);

  for (uint32_t i = 0; i < entry_count; i++) {
    urls[i]->Write();
  }

  return NS_OK;
}

DataReferenceBox::DataReferenceBox(ISOControl* aControl)
  : FullBox(NS_LITERAL_CSTRING("dref"), 0, 0, aControl)
  , entry_count(0)
{
  MOZ_COUNT_CTOR(DataReferenceBox);
}

DataReferenceBox::~DataReferenceBox()
{
  MOZ_COUNT_DTOR(DataReferenceBox);
}

DataInformationBox::DataInformationBox(ISOControl* aControl)
  : DefaultContainerImpl(NS_LITERAL_CSTRING("dinf"), aControl)
{
  boxes.AppendElement(new DataReferenceBox(aControl));
  MOZ_COUNT_CTOR(DataInformationBox);
}

DataInformationBox::~DataInformationBox()
{
  MOZ_COUNT_DTOR(DataInformationBox);
}

nsresult
VideoMediaHeaderBox::Generate(uint32_t* aBoxSize)
{
  size += sizeof(graphicsmode) +
          sizeof(opcolor);

  *aBoxSize = size;

  return NS_OK;
}

nsresult
VideoMediaHeaderBox::Write()
{
  WRITE_FULLBOX(mControl, size)
  mControl->Write(graphicsmode);
  mControl->WriteArray(opcolor, 3);
  return NS_OK;
}

VideoMediaHeaderBox::VideoMediaHeaderBox(ISOControl* aControl)
  : FullBox(NS_LITERAL_CSTRING("vmhd"), 0, 1, aControl)
  , graphicsmode(0)
{
  memset(opcolor, 0 , sizeof(opcolor));
  MOZ_COUNT_CTOR(VideoMediaHeaderBox);
}

VideoMediaHeaderBox::~VideoMediaHeaderBox()
{
  MOZ_COUNT_DTOR(VideoMediaHeaderBox);
}

nsresult
SoundMediaHeaderBox::Generate(uint32_t* aBoxSize)
{
  balance = 0;
  reserved = 0;
  size += sizeof(balance) +
          sizeof(reserved);

  *aBoxSize = size;

  return NS_OK;
}

nsresult
SoundMediaHeaderBox::Write()
{
  WRITE_FULLBOX(mControl, size)
  mControl->Write(balance);
  mControl->Write(reserved);

  return NS_OK;
}

SoundMediaHeaderBox::SoundMediaHeaderBox(ISOControl* aControl)
  : FullBox(NS_LITERAL_CSTRING("smhd"), 0, 0, aControl)
{
  MOZ_COUNT_CTOR(SoundMediaHeaderBox);
}

SoundMediaHeaderBox::~SoundMediaHeaderBox()
{
  MOZ_COUNT_DTOR(SoundMediaHeaderBox);
}

MediaInformationBox::MediaInformationBox(uint32_t aType, ISOControl* aControl)
  : DefaultContainerImpl(NS_LITERAL_CSTRING("minf"), aControl)
{
  mTrackType = aType;

  if (mTrackType == Audio_Track) {
    boxes.AppendElement(new SoundMediaHeaderBox(aControl));
  } else if (mTrackType == Video_Track) {
    boxes.AppendElement(new VideoMediaHeaderBox(aControl));
  } else {
    MOZ_ASSERT(0);
  }

  boxes.AppendElement(new DataInformationBox(aControl));
  boxes.AppendElement(new SampleTableBox(aType, aControl));
  MOZ_COUNT_CTOR(MediaInformationBox);
}

MediaInformationBox::~MediaInformationBox()
{
  MOZ_COUNT_DTOR(MediaInformationBox);
}

nsresult
HandlerBox::Generate(uint32_t* aBoxSize)
{
  pre_defined = 0;
  if (mTrackType == Audio_Track) {
    handler_type = FOURCC('s', 'o', 'u', 'n');
  } else if (mTrackType == Video_Track) {
    handler_type = FOURCC('v', 'i', 'd', 'e');
  }

  size += sizeof(pre_defined) +
          sizeof(handler_type) +
          sizeof(reserved);

  *aBoxSize = size;

  return NS_OK;
}

nsresult
HandlerBox::Write()
{
  WRITE_FULLBOX(mControl, size)
  mControl->Write(pre_defined);
  mControl->Write(handler_type);
  mControl->WriteArray(reserved, 3);

  return NS_OK;
}

HandlerBox::HandlerBox(uint32_t aType, ISOControl* aControl)
  : FullBox(NS_LITERAL_CSTRING("hdlr"), 0, 0, aControl)
  , pre_defined(0)
  , handler_type(0)
{
  mTrackType = aType;
  memset(reserved, 0 , sizeof(reserved));
  MOZ_COUNT_CTOR(HandlerBox);
}

HandlerBox::~HandlerBox()
{
  MOZ_COUNT_DTOR(HandlerBox);
}

MediaHeaderBox::MediaHeaderBox(uint32_t aType, ISOControl* aControl)
  : FullBox(NS_LITERAL_CSTRING("mdhd"), 0, 0, aControl)
  , creation_time(0)
  , modification_time(0)
  , timescale(0)
  , duration(0)
  , pad(0)
  , lang1(0)
  , lang2(0)
  , lang3(0)
  , pre_defined(0)
{
  mTrackType = aType;
  MOZ_COUNT_CTOR(MediaHeaderBox);
}

MediaHeaderBox::~MediaHeaderBox()
{
  MOZ_COUNT_DTOR(MediaHeaderBox);
}

uint32_t
MediaHeaderBox::GetTimeScale()
{
  if (mTrackType == Audio_Track) {
    return mAudioMeta->GetAudioSampleRate();
  }

  return mVideoMeta->GetVideoClockRate();
}

nsresult
MediaHeaderBox::Generate(uint32_t* aBoxSize)
{
  creation_time = mControl->GetTime();
  modification_time = mControl->GetTime();
  timescale = GetTimeScale();
  duration = 0; // fragmented mp4

  pad = 0;
  lang1 = 'u' - 0x60; // "und" underdetermined language
  lang2 = 'n' - 0x60;
  lang3 = 'd' - 0x60;
  size += (pad.size() + lang1.size() + lang2.size() + lang3.size()) / CHAR_BIT;

  pre_defined = 0;
  size += sizeof(creation_time) +
          sizeof(modification_time) +
          sizeof(timescale) +
          sizeof(duration) +
          sizeof(pre_defined);

  *aBoxSize = size;

  return NS_OK;
}

nsresult
MediaHeaderBox::Write()
{
  WRITE_FULLBOX(mControl, size)
  mControl->Write(creation_time);
  mControl->Write(modification_time);
  mControl->Write(timescale);
  mControl->Write(duration);
  mControl->WriteBits(pad.to_ulong(), pad.size());
  mControl->WriteBits(lang1.to_ulong(), lang1.size());
  mControl->WriteBits(lang2.to_ulong(), lang2.size());
  mControl->WriteBits(lang3.to_ulong(), lang3.size());
  mControl->Write(pre_defined);

  return NS_OK;
}

MovieBox::MovieBox(ISOControl* aControl)
  : DefaultContainerImpl(NS_LITERAL_CSTRING("moov"), aControl)
{
  boxes.AppendElement(new MovieHeaderBox(aControl));
  if (aControl->HasAudioTrack()) {
    boxes.AppendElement(new TrackBox(Audio_Track, aControl));
  }
  if (aControl->HasVideoTrack()) {
    boxes.AppendElement(new TrackBox(Video_Track, aControl));
  }
  boxes.AppendElement(new MovieExtendsBox(aControl));
  MOZ_COUNT_CTOR(MovieBox);
}

MovieBox::~MovieBox()
{
  MOZ_COUNT_DTOR(MovieBox);
}

nsresult
MovieHeaderBox::Generate(uint32_t* aBoxSize)
{
  creation_time = mControl->GetTime();
  modification_time = mControl->GetTime();
  timescale = GetTimeScale();
  duration = 0;     // The duration is always 0 in fragmented mp4.
  next_track_ID = mControl->GetNextTrackID();

  size += sizeof(next_track_ID) +
          sizeof(creation_time) +
          sizeof(modification_time) +
          sizeof(timescale) +
          sizeof(duration) +
          sizeof(rate) +
          sizeof(volume) +
          sizeof(reserved16) +
          sizeof(reserved32) +
          sizeof(matrix) +
          sizeof(pre_defined);

  *aBoxSize = size;

  return NS_OK;
}

nsresult
MovieHeaderBox::Write()
{
  WRITE_FULLBOX(mControl, size)
  mControl->Write(creation_time);
  mControl->Write(modification_time);
  mControl->Write(timescale);
  mControl->Write(duration);
  mControl->Write(rate);
  mControl->Write(volume);
  mControl->Write(reserved16);
  mControl->WriteArray(reserved32, 2);
  mControl->WriteArray(matrix, 9);
  mControl->WriteArray(pre_defined, 6);
  mControl->Write(next_track_ID);

  return NS_OK;
}

uint32_t
MovieHeaderBox::GetTimeScale()
{
  // Only audio track in container.
  if (mAudioMeta && !mVideoMeta) {
    return mAudioMeta->GetAudioSampleRate();
  }

  // return video rate
  return mVideoMeta->GetVideoClockRate();
}

MovieHeaderBox::~MovieHeaderBox()
{
  MOZ_COUNT_DTOR(MovieHeaderBox);
}

MovieHeaderBox::MovieHeaderBox(ISOControl* aControl)
  : FullBox(NS_LITERAL_CSTRING("mvhd"), 0, 0, aControl)
  , creation_time(0)
  , modification_time(0)
  , timescale(90000)
  , duration(0)
  , rate(0x00010000)
  , volume(0x0100)
  , reserved16(0)
  , next_track_ID(1)
{
  memcpy(matrix, iso_matrix, sizeof(matrix));
  memset(reserved32, 0, sizeof(reserved32));
  memset(pre_defined, 0, sizeof(pre_defined));
  MOZ_COUNT_CTOR(MovieHeaderBox);
}

TrackHeaderBox::TrackHeaderBox(uint32_t aType, ISOControl* aControl)
  : FullBox(NS_LITERAL_CSTRING("tkhd"), 0,
            flags_track_enabled | flags_track_in_movie | flags_track_in_preview,
            aControl)
  , creation_time(0)
  , modification_time(0)
  , track_ID(0)
  , reserved(0)
  , duration(0)
  , layer(0)
  , alternate_group(0)
  , volume(0)
  , reserved3(0)
  , width(0)
  , height(0)
{
  mTrackType = aType;
  memcpy(matrix, iso_matrix, sizeof(matrix));
  memset(reserved2, 0, sizeof(reserved2));
  MOZ_COUNT_CTOR(TrackHeaderBox);
}

TrackHeaderBox::~TrackHeaderBox()
{
  MOZ_COUNT_DTOR(TrackHeaderBox);
}

nsresult
TrackHeaderBox::Generate(uint32_t* aBoxSize)
{
  creation_time = mControl->GetTime();
  modification_time = mControl->GetTime();
  track_ID = (mTrackType == Audio_Track ?
                mControl->GetTrackID(mAudioMeta->GetKind()) :
                mControl->GetTrackID(mVideoMeta->GetKind()));
  // fragmented mp4
  duration = 0;

  // volume, audiotrack is always 0x0100 in 14496-12 8.3.2.2
  volume = (mTrackType == Audio_Track ? 0x0100 : 0);

  if (mTrackType == Video_Track) {
    width = mVideoMeta->GetVideoDisplayWidth() << 16;
    height = mVideoMeta->GetVideoDisplayHeight() << 16;
    // Check display size, using the pixel size if any of them is invalid.
    if (!width || !height) {
      width = mVideoMeta->GetVideoWidth() << 16;
      height = mVideoMeta->GetVideoHeight() << 16;
    }
  }

  size += sizeof(creation_time) +
          sizeof(modification_time) +
          sizeof(track_ID) +
          sizeof(reserved) +
          sizeof(duration) +
          sizeof(reserved2) +
          sizeof(layer) +
          sizeof(alternate_group) +
          sizeof(volume) +
          sizeof(reserved3) +
          sizeof(matrix) +
          sizeof(width) +
          sizeof(height);

  *aBoxSize = size;

  return NS_OK;
}

nsresult
TrackHeaderBox::Write()
{
  WRITE_FULLBOX(mControl, size)
  mControl->Write(creation_time);
  mControl->Write(modification_time);
  mControl->Write(track_ID);
  mControl->Write(reserved);
  mControl->Write(duration);
  mControl->WriteArray(reserved2, 2);
  mControl->Write(layer);
  mControl->Write(alternate_group);
  mControl->Write(volume);
  mControl->Write(reserved3);
  mControl->WriteArray(matrix, 9);
  mControl->Write(width);
  mControl->Write(height);

  return NS_OK;
}

nsresult
FileTypeBox::Generate(uint32_t* aBoxSize)
{
  minor_version = 0;

  if (mControl->GetMuxingType() == ISOMediaWriter::TYPE_FRAG_MP4) {
    if (!mControl->HasVideoTrack() && mControl->HasAudioTrack()) {
      major_brand = "M4A ";
    } else {
      major_brand = "MP42";
    }
    compatible_brands.AppendElement("mp42");
    compatible_brands.AppendElement("isom");
  } else if (mControl->GetMuxingType() == ISOMediaWriter::TYPE_FRAG_3GP) {
    major_brand = "3gp9";
    // According to 3GPP TS 26.244 V12.2.0, section 5.3.4, it's recommended to
    // list all compatible brands here. 3GP spec supports fragment from '3gp6'.
    compatible_brands.AppendElement("3gp9");
    compatible_brands.AppendElement("3gp8");
    compatible_brands.AppendElement("3gp7");
    compatible_brands.AppendElement("3gp6");
    compatible_brands.AppendElement("isom");
  } else if (mControl->GetMuxingType() == ISOMediaWriter::TYPE_FRAG_3G2) {
    major_brand = "3g2a";
    // 3GPP2 Release 0 and A and 3GPP Release 6 allow movie fragmentation
    compatible_brands.AppendElement("3gp9");
    compatible_brands.AppendElement("3gp8");
    compatible_brands.AppendElement("3gp7");
    compatible_brands.AppendElement("3gp6");
    compatible_brands.AppendElement("isom");
    compatible_brands.AppendElement("3g2c");
    compatible_brands.AppendElement("3g2b");
    compatible_brands.AppendElement("3g2a");
  } else {
    MOZ_ASSERT(0);
  }

  size += major_brand.Length() +
          sizeof(minor_version) +
          compatible_brands.Length() * 4;

  *aBoxSize = size;

  return NS_OK;
}

nsresult
FileTypeBox::Write()
{
  BoxSizeChecker checker(mControl, size);
  Box::Write();
  mControl->WriteFourCC(major_brand.get());
  mControl->Write(minor_version);
  uint32_t len = compatible_brands.Length();
  for (uint32_t i = 0; i < len; i++) {
    mControl->WriteFourCC(compatible_brands[i].get());
  }

  return NS_OK;
}

FileTypeBox::FileTypeBox(ISOControl* aControl)
  : Box(NS_LITERAL_CSTRING("ftyp"), aControl)
  , minor_version(0)
{
  MOZ_COUNT_CTOR(FileTypeBox);
}

FileTypeBox::~FileTypeBox()
{
  MOZ_COUNT_DTOR(FileTypeBox);
}

MediaBox::MediaBox(uint32_t aType, ISOControl* aControl)
  : DefaultContainerImpl(NS_LITERAL_CSTRING("mdia"), aControl)
{
  mTrackType = aType;
  boxes.AppendElement(new MediaHeaderBox(aType, aControl));
  boxes.AppendElement(new HandlerBox(aType, aControl));
  boxes.AppendElement(new MediaInformationBox(aType, aControl));
  MOZ_COUNT_CTOR(MediaBox);
}

MediaBox::~MediaBox()
{
  MOZ_COUNT_DTOR(MediaBox);
}

nsresult
DefaultContainerImpl::Generate(uint32_t* aBoxSize)
{
  nsresult rv;
  uint32_t box_size;
  uint32_t len = boxes.Length();
  for (uint32_t i = 0; i < len; i++) {
    rv = boxes.ElementAt(i)->Generate(&box_size);
    NS_ENSURE_SUCCESS(rv, rv);
    size += box_size;
  }
  *aBoxSize = size;
  return NS_OK;
}

nsresult
DefaultContainerImpl::Find(const nsACString& aType,
                           nsTArray<RefPtr<MuxerOperation>>& aOperations)
{
  nsresult rv = Box::Find(aType, aOperations);
  NS_ENSURE_SUCCESS(rv, rv);

  uint32_t len = boxes.Length();
  for (uint32_t i = 0; i < len; i++) {
    rv = boxes.ElementAt(i)->Find(aType, aOperations);
    NS_ENSURE_SUCCESS(rv, rv);
  }
  return NS_OK;
}

nsresult
DefaultContainerImpl::Write()
{
  BoxSizeChecker checker(mControl, size);
  Box::Write();

  nsresult rv;
  uint32_t len = boxes.Length();
  for (uint32_t i = 0; i < len; i++) {
    rv = boxes.ElementAt(i)->Write();
    NS_ENSURE_SUCCESS(rv, rv);
  }

  return NS_OK;
}

DefaultContainerImpl::DefaultContainerImpl(const nsACString& aType,
                                           ISOControl* aControl)
  : Box(aType, aControl)
{
}

nsresult
Box::Write()
{
  mControl->Write(size);
  mControl->WriteFourCC(boxType.get());
  return NS_OK;
}

nsresult
Box::Find(const nsACString& aType, nsTArray<RefPtr<MuxerOperation>>& aOperations)
{
  if (boxType == aType) {
    aOperations.AppendElement(this);
  }
  return NS_OK;
}

Box::Box(const nsACString& aType, ISOControl* aControl)
  : size(8), mControl(aControl)
{
  MOZ_ASSERT(aType.Length() == 4);
  boxType = aType;
  aControl->GetAudioMetadata(mAudioMeta);
  aControl->GetVideoMetadata(mVideoMeta);
}

FullBox::FullBox(const nsACString& aType, uint8_t aVersion, uint32_t aFlags,
                 ISOControl* aControl)
  : Box(aType, aControl)
{
  std::bitset<24> tmp_flags(aFlags);
  version = aVersion;
  flags = tmp_flags;
  size += sizeof(version) + flags.size() / CHAR_BIT;
}

nsresult
FullBox::Write()
{
  Box::Write();
  mControl->Write(version);
  mControl->WriteBits(flags.to_ulong(), flags.size());
  return NS_OK;
}

TrackBox::TrackBox(uint32_t aTrackType, ISOControl* aControl)
  : DefaultContainerImpl(NS_LITERAL_CSTRING("trak"), aControl)
{
  boxes.AppendElement(new TrackHeaderBox(aTrackType, aControl));
  boxes.AppendElement(new MediaBox(aTrackType, aControl));
  MOZ_COUNT_CTOR(TrackBox);
}

TrackBox::~TrackBox()
{
  MOZ_COUNT_DTOR(TrackBox);
}

SampleEntryBox::SampleEntryBox(const nsACString& aFormat, ISOControl* aControl)
  : Box(aFormat, aControl)
  , data_reference_index(0)
{
  data_reference_index = 1; // There is only one data reference in each track.
  size += sizeof(reserved) +
          sizeof(data_reference_index);
  memset(reserved, 0, sizeof(reserved));
}

nsresult
SampleEntryBox::Write()
{
  Box::Write();
  mControl->Write(reserved, sizeof(reserved));
  mControl->Write(data_reference_index);
  return NS_OK;
}

nsresult
AudioSampleEntry::Write()
{
  SampleEntryBox::Write();
  mControl->Write(sound_version);
  mControl->Write(reserved2, sizeof(reserved2));
  mControl->Write(channels);
  mControl->Write(sample_size);
  mControl->Write(compressionId);
  mControl->Write(packet_size);
  mControl->Write(timeScale);
  return NS_OK;
}

AudioSampleEntry::AudioSampleEntry(const nsACString& aFormat, ISOControl* aControl)
  : SampleEntryBox(aFormat, aControl)
  , sound_version(0)
  , channels(2)
  , sample_size(16)
  , compressionId(0)
  , packet_size(0)
  , timeScale(0)
{
  memset(reserved2, 0 , sizeof(reserved2));
  channels = mAudioMeta->GetAudioChannels();
  timeScale = mAudioMeta->GetAudioSampleRate() << 16;

  size += sizeof(sound_version) +
          sizeof(reserved2) +
          sizeof(sample_size) +
          sizeof(channels) +
          sizeof(packet_size) +
          sizeof(compressionId) +
          sizeof(timeScale);

  MOZ_COUNT_CTOR(AudioSampleEntry);
}

AudioSampleEntry::~AudioSampleEntry()
{
  MOZ_COUNT_DTOR(AudioSampleEntry);
}

nsresult
VisualSampleEntry::Write()
{
  SampleEntryBox::Write();

  mControl->Write(reserved, sizeof(reserved));
  mControl->Write(width);
  mControl->Write(height);
  mControl->Write(horizresolution);
  mControl->Write(vertresolution);
  mControl->Write(reserved2);
  mControl->Write(frame_count);
  mControl->Write(compressorName, sizeof(compressorName));
  mControl->Write(depth);
  mControl->Write(pre_defined);

  return NS_OK;
}

VisualSampleEntry::VisualSampleEntry(const nsACString& aFormat, ISOControl* aControl)
  : SampleEntryBox(aFormat, aControl)
  , width(0)
  , height(0)
  , horizresolution(resolution_72_dpi)
  , vertresolution(resolution_72_dpi)
  , reserved2(0)
  , frame_count(1)
  , depth(video_depth)
  , pre_defined(-1)
{
  memset(reserved, 0 , sizeof(reserved));
  memset(compressorName, 0 , sizeof(compressorName));

  // both fields occupy 16 bits defined in 14496-2 6.2.3.
  width = mVideoMeta->GetVideoWidth();
  height = mVideoMeta->GetVideoHeight();

  size += sizeof(reserved) +
          sizeof(width) +
          sizeof(height) +
          sizeof(horizresolution) +
          sizeof(vertresolution) +
          sizeof(reserved2) +
          sizeof(frame_count) +
          sizeof(compressorName) +
          sizeof(depth) +
          sizeof(pre_defined);

  MOZ_COUNT_CTOR(VisualSampleEntry);
}

VisualSampleEntry::~VisualSampleEntry()
{
  MOZ_COUNT_DTOR(VisualSampleEntry);
}

}
