/* 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 "mozilla/dom/ImageTrackList.h"

#include "MediaResult.h"
#include "mozilla/dom/ImageDecoder.h"
#include "mozilla/dom/ImageTrack.h"
#include "mozilla/dom/Promise.h"
#include "mozilla/image/ImageUtils.h"

namespace mozilla::dom {

NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(ImageTrackList, mParent, mDecoder,
                                      mReadyPromise, mTracks)
NS_IMPL_CYCLE_COLLECTING_ADDREF(ImageTrackList)
NS_IMPL_CYCLE_COLLECTING_RELEASE(ImageTrackList)
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ImageTrackList)
  NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
  NS_INTERFACE_MAP_ENTRY(nsISupports)
NS_INTERFACE_MAP_END

ImageTrackList::ImageTrackList(nsIGlobalObject* aParent, ImageDecoder* aDecoder)
    : mParent(aParent), mDecoder(aDecoder) {}

ImageTrackList::~ImageTrackList() = default;

JSObject* ImageTrackList::WrapObject(JSContext* aCx,
                                     JS::Handle<JSObject*> aGivenProto) {
  AssertIsOnOwningThread();
  return ImageTrackList_Binding::Wrap(aCx, this, aGivenProto);
}

void ImageTrackList::Initialize(ErrorResult& aRv) {
  mReadyPromise = Promise::Create(mParent, aRv);
  if (NS_WARN_IF(aRv.Failed())) {
    return;
  }
}

void ImageTrackList::Destroy() {
  if (!mIsReady && mReadyPromise && mReadyPromise->PromiseObj()) {
    mReadyPromise->MaybeRejectWithAbortError("ImageTrackList destroyed");
    mIsReady = true;
  }

  for (auto& track : mTracks) {
    track->Destroy();
  }
  mTracks.Clear();

  mDecoder = nullptr;
  mSelectedIndex = -1;
}

void ImageTrackList::MaybeRejectReady(const MediaResult& aResult) {
  if (mIsReady || !mReadyPromise || !mReadyPromise->PromiseObj()) {
    return;
  }
  aResult.RejectTo(mReadyPromise);
  mIsReady = true;
}

void ImageTrackList::OnMetadataSuccess(
    const image::DecodeMetadataResult& aMetadata) {
  // 10.2.5. Establish Tracks
  //
  // Note that our implementation only supports one track, so many of these
  // steps are simplified.

  // 4. Let newTrackList be a new list.
  MOZ_ASSERT(mTracks.IsEmpty());

  // Mozilla-internal-only addition
  nsTArray<ImageSize> imageSizes;
  for (const OrientedIntSize& nativeSize : aMetadata.mNativeSizes) {
    ImageSize* imageSize = imageSizes.AppendElement();
    imageSize->mWidth = nativeSize.width;
    imageSize->mHeight = nativeSize.height;
  }

  // 5. For each image track found in [[encoded data]]:
  // 5.1. Let newTrack be a new ImageTrack, initialized as follows:
  // 5.1.1. Assign this to [[ImageDecoder]].
  // 5.1.2. Assign tracks to [[ImageTrackList]].
  // 5.1.3. If image track is found to be animated, assign true to newTrack's
  //        [[animated]] internal slot. Otherwise, assign false.
  // 5.1.4. If image track is found to describe a frame count, assign that
  //        count to newTrack's [[frame count]] internal slot. Otherwise, assign
  //        0.
  // 5.1.5. If image track is found to describe a repetition count, assign that
  //        count to [[repetition count]] internal slot. Otherwise, assign 0.
  // 5.1.6. Assign false to newTrack’s [[selected]] internal slot.
  // 5.2. Append newTrack to newTrackList.
  // 6. Let selectedTrackIndex be the result of running the Get Default Selected
  //    Track Index algorithm with newTrackList.
  // 7. Let selectedTrack be the track at position selectedTrackIndex within
  //    newTrackList.
  // 8. Assign true to selectedTrack’s [[selected]] internal slot.
  // 9. Assign selectedTrackIndex to [[internal selected track index]].
  const float repetitions = aMetadata.mRepetitions < 0
                                ? std::numeric_limits<float>::infinity()
                                : static_cast<float>(aMetadata.mRepetitions);
  auto track = MakeRefPtr<ImageTrack>(
      this, /* aIndex */ 0, std::move(imageSizes), /* aSelected */ true,
      aMetadata.mAnimated, aMetadata.mFrameCount, aMetadata.mFrameCountComplete,
      repetitions);

  // 11. Queue a task to perform the following steps:
  //
  // Note that we were already dispatched by the image decoder.

  // 11.1. Assign newTrackList to the tracks [[track list]] internal slot.
  mTracks.AppendElement(std::move(track));

  // 11.2. Assign selectedTrackIndex to tracks [[selected index]].
  mSelectedIndex = 0;

  // 11.3. Resolve [[ready promise]].
  MOZ_ASSERT(!mIsReady);
  mReadyPromise->MaybeResolveWithUndefined();
  mIsReady = true;
}

void ImageTrackList::OnFrameCountSuccess(
    const image::DecodeFrameCountResult& aResult) {
  if (mTracks.IsEmpty()) {
    return;
  }

  // 10.2.5. Update Tracks
  //
  // Note that we were already dispatched from the decoding threads.

  // 3. Let trackList be a copy of tracks' [[track list]].
  // 4. For each track in trackList:
  // 4.1. Let trackIndex be the position of track in trackList.
  // 4.2. Let latestFrameCount be the frame count as indicated by
  //      [[encoded data]] for the track corresponding to track.
  // 4.3. Assert that latestFrameCount is greater than or equal to
  //      track.frameCount.
  // 4.4. If latestFrameCount is greater than track.frameCount:
  // 4.4.1. Let change be a track update struct whose track index is trackIndex
  //        and frame count is latestFrameCount.
  // 4.4.2. Append change to tracksChanges.
  // 5. If tracksChanges is empty, abort these steps.
  // 6. Queue a task to perform the following steps:
  // 6.1. For each update in trackChanges:
  // 6.1.1. Let updateTrack be the ImageTrack at position update.trackIndex
  //        within tracks' [[track list]].
  // 6.1.2. Assign update.frameCount to updateTrack’s [[frame count]].
  mTracks.LastElement()->OnFrameCountSuccess(aResult);
}

void ImageTrackList::SetSelectedIndex(int32_t aIndex, bool aSelected) {
  MOZ_ASSERT(aIndex >= 0);
  MOZ_ASSERT(uint32_t(aIndex) < mTracks.Length());

  // 10.7.2. Attributes - selected, of type boolean

  // 1. If [[ImageDecoder]]'s [[closed]] slot is true, abort these steps.
  if (!mDecoder) {
    return;
  }

  // 2. Let newValue be the given value.
  // 3. If newValue equals [[selected]], abort these steps.
  // 4. Assign newValue to [[selected]].
  // 5. Let parentTrackList be [[ImageTrackList]]
  // 6. Let oldSelectedIndex be the value of parentTrackList [[selected index]].
  // 7. If oldSelectedIndex is not -1:
  // 7.1. Let oldSelectedTrack be the ImageTrack in parentTrackList
  //      [[track list]] at the position of oldSelectedIndex.
  // 7.2. Assign false to oldSelectedTrack [[selected]]
  // 8. If newValue is true, let selectedIndex be the index of this ImageTrack
  //    within parentTrackList's [[track list]]. Otherwise, let selectedIndex be
  //    -1.
  // 9. Assign selectedIndex to parentTrackList [[selected index]].
  if (aSelected) {
    if (mSelectedIndex == -1) {
      MOZ_ASSERT(!mTracks[aIndex]->Selected());
      mTracks[aIndex]->MarkSelected();
      mSelectedIndex = aIndex;
    } else if (mSelectedIndex != aIndex) {
      MOZ_ASSERT(mTracks[mSelectedIndex]->Selected());
      MOZ_ASSERT(!mTracks[aIndex]->Selected());
      mTracks[mSelectedIndex]->ClearSelected();
      mTracks[aIndex]->MarkSelected();
      mSelectedIndex = aIndex;
    } else {
      MOZ_ASSERT(mTracks[mSelectedIndex]->Selected());
      return;
    }
  } else if (mSelectedIndex == aIndex) {
    mTracks[mSelectedIndex]->ClearSelected();
    mSelectedIndex = -1;
  } else {
    MOZ_ASSERT(!mTracks[aIndex]->Selected());
    return;
  }

  // 10. Run the Reset ImageDecoder algorithm on [[ImageDecoder]].
  RefPtr<ImageDecoder> decoder = mDecoder;
  decoder->ResetWithoutRef(
      MediaResult(NS_ERROR_DOM_ABORT_ERR, "Reset decoder (select index)"_ns));

  // 11. Queue a control message to [[ImageDecoder]]'s control message queue to
  //     update the internal selected track index with selectedIndex.
  decoder->QueueSelectTrackMessage(mSelectedIndex);

  // 12. Process the control message queue belonging to [[ImageDecoder]].
  decoder->ProcessControlMessageQueue();
}

}  // namespace mozilla::dom
