/* -*- 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/. */

/* table of images used in a document, for batch locking/unlocking and
 * animating */

#include "ImageTracker.h"

#include "imgIContainer.h"
#include "imgIRequest.h"
#include "mozilla/Preferences.h"
#include "nsXULAppAPI.h"

namespace mozilla::dom {

ImageTracker::ImageTracker() = default;

ImageTracker::~ImageTracker() { SetLockingState(false); }

nsresult ImageTracker::Add(imgIRequest* aImage) {
  MOZ_ASSERT(aImage);

  const nsresult rv = mImages.WithEntryHandle(aImage, [&](auto&& entry) {
    nsresult rv = NS_OK;
    if (entry) {
      // The image is already in the hashtable.  Increment its count.
      uint32_t oldCount = entry.Data();
      MOZ_ASSERT(oldCount > 0, "Entry in the image tracker with count 0!");
      entry.Data() = oldCount + 1;
    } else {
      // A new entry was inserted - set the count to 1.
      entry.Insert(1);

      // If we're locking images, lock this image too.
      if (mLocking) {
        rv = aImage->LockImage();
      }

      // If we're animating images, request that this image be animated too.
      if (mAnimating) {
        nsresult rv2 = aImage->IncrementAnimationConsumers();
        rv = NS_SUCCEEDED(rv) ? rv2 : rv;
      }
    }

    return rv;
  });

  return rv;
}

nsresult ImageTracker::Remove(imgIRequest* aImage, uint32_t aFlags) {
  NS_ENSURE_ARG_POINTER(aImage);

  // Get the old count. It should exist and be > 0.
  if (auto entry = mImages.Lookup(aImage)) {
    MOZ_ASSERT(entry.Data() > 0, "Entry in the image tracker with count 0!");
    // If the count becomes zero, remove it from the tracker.
    if (--entry.Data() == 0) {
      entry.Remove();
    } else {
      return NS_OK;
    }
  } else {
    MOZ_ASSERT_UNREACHABLE("Removing image that wasn't in the tracker!");
    return NS_OK;
  }

  nsresult rv = NS_OK;

  // Now that we're no longer tracking this image, unlock it if we'd
  // previously locked it.
  if (mLocking) {
    rv = aImage->UnlockImage();
  }

  // If we're animating images, remove our request to animate this one.
  if (mAnimating) {
    nsresult rv2 = aImage->DecrementAnimationConsumers();
    rv = NS_SUCCEEDED(rv) ? rv2 : rv;
  }

  if (aFlags & REQUEST_DISCARD) {
    // Request that the image be discarded if nobody else holds a lock on it.
    // Do this even if !mLocking, because even if we didn't just unlock
    // this image, it might still be a candidate for discarding.
    aImage->RequestDiscard();
  }

  return rv;
}

void ImageTracker::SetLockingState(bool aLocked) {
  // If there's no change, there's nothing to do.
  if (mLocking == aLocked) {
    return;
  }

  // Otherwise, iterate over our images and perform the appropriate action.
  for (imgIRequest* image : mImages.Keys()) {
    if (aLocked) {
      image->LockImage();
    } else {
      image->UnlockImage();
    }
  }

  // Update state.
  mLocking = aLocked;
}

void ImageTracker::SetAnimatingState(bool aAnimating) {
  // If there's no change, there's nothing to do.
  if (mAnimating == aAnimating) return;

  // Otherwise, iterate over our images and perform the appropriate action.
  for (imgIRequest* image : mImages.Keys()) {
    if (aAnimating) {
      image->IncrementAnimationConsumers();
    } else {
      image->DecrementAnimationConsumers();
    }
  }

  // Update state.
  mAnimating = aAnimating;
}

void ImageTracker::RequestDiscardAll() {
  for (imgIRequest* image : mImages.Keys()) {
    image->RequestDiscard();
  }
}

void ImageTracker::MediaFeatureValuesChangedAllDocuments(
    const MediaFeatureChange& aChange) {
  // Inform every content image used in the document that media feature values
  // have changed.  If the same image is used in multiple places, then we can
  // end up informing them multiple times.  Theme changes are rare though and we
  // don't bother trying to ensure we only do this once per image.
  //
  // Pull the images out into an array and iterate over them, in case the
  // image notifications do something that ends up modifying the table.
  nsTArray<nsCOMPtr<imgIContainer>> images;
  for (imgIRequest* req : mImages.Keys()) {
    nsCOMPtr<imgIContainer> image;
    req->GetImage(getter_AddRefs(image));
    if (!image) {
      continue;
    }
    images.AppendElement(image->Unwrap());
  }
  for (imgIContainer* image : images) {
    image->MediaFeatureValuesChangedAllDocuments(aChange);
  }
}

}  // namespace mozilla::dom
