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

#ifndef imgStatusTracker_h__
#define imgStatusTracker_h__

class imgDecoderObserver;
class imgIContainer;
class imgStatusNotifyRunnable;
class imgRequestNotifyRunnable;
class imgStatusTrackerObserver;
class nsIRunnable;

#include "mozilla/RefPtr.h"
#include "mozilla/WeakPtr.h"
#include "nsCOMPtr.h"
#include "nsTObserverArray.h"
#include "nsThreadUtils.h"
#include "nsRect.h"
#include "imgRequestProxy.h"

namespace mozilla {
namespace image {

class Image;

struct ImageStatusDiff
{
  ImageStatusDiff()
    : invalidRect()
    , diffState(0)
    , diffImageStatus(0)
    , unblockedOnload(false)
    , unsetDecodeStarted(false)
    , foundError(false)
    , foundIsMultipart(false)
    , foundLastPart(false)
    , gotDecoded(false)
  { }

  static ImageStatusDiff NoChange() { return ImageStatusDiff(); }
  bool IsNoChange() const { return *this == NoChange(); }

  bool operator!=(const ImageStatusDiff& aOther) const { return !(*this == aOther); }
  bool operator==(const ImageStatusDiff& aOther) const {
    return aOther.invalidRect == invalidRect
        && aOther.diffState == diffState
        && aOther.diffImageStatus == diffImageStatus
        && aOther.unblockedOnload == unblockedOnload
        && aOther.unsetDecodeStarted == unsetDecodeStarted
        && aOther.foundError == foundError
        && aOther.foundIsMultipart == foundIsMultipart
        && aOther.foundLastPart == foundLastPart
        && aOther.gotDecoded == gotDecoded;
  }

  void Combine(const ImageStatusDiff& aOther) {
    invalidRect = invalidRect.Union(aOther.invalidRect);
    diffState |= aOther.diffState;
    diffImageStatus |= aOther.diffImageStatus;
    unblockedOnload = unblockedOnload || aOther.unblockedOnload;
    unsetDecodeStarted = unsetDecodeStarted || aOther.unsetDecodeStarted;
    foundError = foundError || aOther.foundError;
    foundIsMultipart = foundIsMultipart || aOther.foundIsMultipart;
    foundLastPart = foundLastPart || aOther.foundLastPart;
    gotDecoded = gotDecoded || aOther.gotDecoded;
  }

  nsIntRect invalidRect;
  uint32_t  diffState;
  uint32_t  diffImageStatus;
  bool      unblockedOnload    : 1;
  bool      unsetDecodeStarted : 1;
  bool      foundError         : 1;
  bool      foundIsMultipart   : 1;
  bool      foundLastPart      : 1;
  bool      gotDecoded         : 1;
};

enum {
  stateRequestStarted    = 1u << 0,
  stateHasSize           = 1u << 1,
  stateDecodeStarted     = 1u << 2,
  stateDecodeStopped     = 1u << 3,
  stateFrameStopped      = 1u << 4,
  stateRequestStopped    = 1u << 5,
  stateBlockingOnload    = 1u << 6,
  stateImageIsAnimated   = 1u << 7
};

} // namespace image
} // namespace mozilla

/*
 * The image status tracker is a class that encapsulates all the loading and
 * decoding status about an Image, and makes it possible to send notifications
 * to imgRequestProxys, both synchronously (i.e., the status now) and
 * asynchronously (the status later).
 *
 * When a new proxy needs to be notified of the current state of an image, call
 * the Notify() method on this class with the relevant proxy as its argument,
 * and the notifications will be replayed to the proxy asynchronously.
 */


class imgStatusTracker : public mozilla::SupportsWeakPtr<imgStatusTracker>
{
public:
  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(imgStatusTracker)

  // aImage is the image that this status tracker will pass to the
  // imgRequestProxys in SyncNotify() and EmulateRequestFinished(), and must be
  // alive as long as this instance is, because we hold a weak reference to it.
  imgStatusTracker(mozilla::image::Image* aImage);
  virtual ~imgStatusTracker();

  // Image-setter, for imgStatusTrackers created by imgRequest::Init, which
  // are created before their Image is created.  This method should only
  // be called once, and only on an imgStatusTracker that was initialized
  // without an image.
  void SetImage(mozilla::image::Image* aImage);

  // Image resetter, for when mImage is about to go out of scope. mImage is a
  // weak reference, and thus must be set to null when it's object is deleted.
  void ResetImage();

  // Inform this status tracker that it is associated with a multipart image.
  void SetIsMultipart() { mIsMultipart = true; }

  // Schedule an asynchronous "replaying" of all the notifications that would
  // have to happen to put us in the current state.
  // We will also take note of any notifications that happen between the time
  // Notify() is called and when we call SyncNotify on |proxy|, and replay them
  // as well.
  // Should be called on the main thread only, since imgRequestProxy and GetURI
  // are not threadsafe.
  void Notify(imgRequestProxy* proxy);

  // Schedule an asynchronous "replaying" of all the notifications that would
  // have to happen to put us in the state we are in right now.
  // Unlike Notify(), does *not* take into account future notifications.
  // This is only useful if you do not have an imgRequest, e.g., if you are a
  // static request returned from imgIRequest::GetStaticRequest().
  // Should be called on the main thread only, since imgRequestProxy and GetURI
  // are not threadsafe.
  void NotifyCurrentState(imgRequestProxy* proxy);

  // "Replay" all of the notifications that would have to happen to put us in
  // the state we're currently in.
  // Only use this if you're already servicing an asynchronous call (e.g.
  // OnStartRequest).
  // Should be called on the main thread only, since imgRequestProxy and GetURI
  // are not threadsafe.
  void SyncNotify(imgRequestProxy* proxy);

  // Send some notifications that would be necessary to make |proxy| believe
  // the request is finished downloading and decoding.  We only send
  // OnStopRequest and UnblockOnload, and only if necessary.
  void EmulateRequestFinished(imgRequestProxy* proxy, nsresult aStatus);

  // We manage a set of consumers that are using an image and thus concerned
  // with its status. Weak pointers.
  void AddConsumer(imgRequestProxy* aConsumer);
  bool RemoveConsumer(imgRequestProxy* aConsumer, nsresult aStatus);
  size_t ConsumerCount() const {
    MOZ_ASSERT(NS_IsMainThread(), "Use mConsumers on main thread only");
    return mConsumers.Length();
  }

  // This is intentionally non-general because its sole purpose is to support an
  // some obscure network priority logic in imgRequest. That stuff could probably
  // be improved, but it's too scary to mess with at the moment.
  bool FirstConsumerIs(imgRequestProxy* aConsumer);

  void AdoptConsumers(imgStatusTracker* aTracker) {
    MOZ_ASSERT(NS_IsMainThread(), "Use mConsumers on main thread only");
    MOZ_ASSERT(aTracker);
    mConsumers = aTracker->mConsumers;
  }

  // Returns whether we are in the process of loading; that is, whether we have
  // not received OnStopRequest.
  bool IsLoading() const;

  // Get the current image status (as in imgIRequest).
  uint32_t GetImageStatus() const;

  // Following are all the notification methods. You must call the Record
  // variant on this status tracker, then call the Send variant for each proxy
  // you want to notify.

  // Call when the request is being cancelled.
  void RecordCancel();

  // Shorthand for recording all the load notifications: StartRequest,
  // StartContainer, StopRequest.
  void RecordLoaded();

  // Shorthand for recording all the decode notifications: StartDecode,
  // StartFrame, DataAvailable, StopFrame, StopDecode.
  void RecordDecoded();

  /* non-virtual imgDecoderObserver methods */
  // Functions with prefix Send- are main thread only, since they contain calls
  // to imgRequestProxy functions, which are expected on the main thread.
  void RecordStartDecode();
  void SendStartDecode(imgRequestProxy* aProxy);
  void RecordStartContainer(imgIContainer* aContainer);
  void SendStartContainer(imgRequestProxy* aProxy);
  void RecordStartFrame();
  // No SendStartFrame since it's not observed below us.
  void RecordFrameChanged(const nsIntRect* aDirtyRect);
  void SendFrameChanged(imgRequestProxy* aProxy, const nsIntRect* aDirtyRect);
  void RecordStopFrame();
  void SendStopFrame(imgRequestProxy* aProxy);
  void RecordStopDecode(nsresult statusg);
  void SendStopDecode(imgRequestProxy* aProxy, nsresult aStatus);
  void RecordDiscard();
  void SendDiscard(imgRequestProxy* aProxy);
  void RecordUnlockedDraw();
  void SendUnlockedDraw(imgRequestProxy* aProxy);
  void RecordImageIsAnimated();
  void SendImageIsAnimated(imgRequestProxy *aProxy);

  /* non-virtual sort-of-nsIRequestObserver methods */
  // Functions with prefix Send- are main thread only, since they contain calls
  // to imgRequestProxy functions, which are expected on the main thread.
  void RecordStartRequest();
  void SendStartRequest(imgRequestProxy* aProxy);
  void RecordStopRequest(bool aLastPart, nsresult aStatus);
  void SendStopRequest(imgRequestProxy* aProxy, bool aLastPart, nsresult aStatus);

  // All main thread only because they call functions (like SendStartRequest)
  // which are expected to be called on the main thread.
  void OnStartRequest();
  // OnDataAvailable will dispatch a call to itself onto the main thread if not
  // called there.
  void OnDataAvailable();
  void OnStopRequest(bool aLastPart, nsresult aStatus);
  void OnDiscard();
  void FrameChanged(const nsIntRect* aDirtyRect);
  void OnUnlockedDraw();
  // This is called only by VectorImage, and only to ensure tests work
  // properly. Do not use it.
  void OnStopFrame();

  /* non-virtual imgIOnloadBlocker methods */
  // NB: If UnblockOnload is sent, and then we are asked to replay the
  // notifications, we will not send a BlockOnload/UnblockOnload pair.  This
  // is different from all the other notifications.
  void RecordBlockOnload();
  void SendBlockOnload(imgRequestProxy* aProxy);
  void RecordUnblockOnload();
  void SendUnblockOnload(imgRequestProxy* aProxy);

  // Main thread only because mConsumers is not threadsafe.
  void MaybeUnblockOnload();

  void RecordError();

  bool IsMultipart() const { return mIsMultipart; }

  // Weak pointer getters - no AddRefs.
  inline already_AddRefed<mozilla::image::Image> GetImage() const {
    nsRefPtr<mozilla::image::Image> image = mImage;
    return image.forget();
  }
  inline bool HasImage() { return mImage; }

  inline imgDecoderObserver* GetDecoderObserver() { return mTrackerObserver.get(); }

  already_AddRefed<imgStatusTracker> CloneForRecording();

  // Compute the difference between this status tracker and aOther.
  mozilla::image::ImageStatusDiff Difference(imgStatusTracker* aOther) const;

  // Captures all of the decode notifications (i.e., not OnStartRequest /
  // OnStopRequest) so far as an ImageStatusDiff.
  mozilla::image::ImageStatusDiff DecodeStateAsDifference() const;

  // Update our state to incorporate the changes in aDiff.
  void ApplyDifference(const mozilla::image::ImageStatusDiff& aDiff);

  // Notify for the changes captured in an ImageStatusDiff. Because this may
  // result in recursive notifications, no decoding locks may be held.
  // Called on the main thread only.
  void SyncNotifyDifference(const mozilla::image::ImageStatusDiff& aDiff);

  nsIntRect GetInvalidRect() const { return mInvalidRect; }

private:
  typedef nsTObserverArray<mozilla::WeakPtr<imgRequestProxy> > ProxyArray;
  friend class imgStatusNotifyRunnable;
  friend class imgRequestNotifyRunnable;
  friend class imgStatusTrackerObserver;
  friend class imgStatusTrackerInit;
  imgStatusTracker(const imgStatusTracker& aOther);

  // Main thread only because it deals with the observer service.
  void FireFailureNotification();

  // Main thread only, since imgRequestProxy calls are expected on the main
  // thread, and mConsumers is not threadsafe.
  static void SyncNotifyState(ProxyArray& proxies,
                              bool hasImage, uint32_t state,
                              nsIntRect& dirtyRect, bool hadLastPart);

  nsCOMPtr<nsIRunnable> mRequestRunnable;

  // The invalid area of the most recent frame we know about. (All previous
  // frames are assumed to be fully valid.)
  nsIntRect mInvalidRect;

  // This weak ref should be set null when the image goes out of scope.
  mozilla::image::Image* mImage;

  // List of proxies attached to the image. Each proxy represents a consumer
  // using the image. Array and/or individual elements should only be accessed
  // on the main thread.
  ProxyArray mConsumers;

  mozilla::RefPtr<imgDecoderObserver> mTrackerObserver;

  uint32_t mState;
  uint32_t mImageStatus;
  bool mIsMultipart    : 1;
  bool mHadLastPart    : 1;
  bool mHasBeenDecoded : 1;
};

class imgStatusTrackerInit
{
public:
  imgStatusTrackerInit(mozilla::image::Image* aImage,
                       imgStatusTracker* aTracker);
  ~imgStatusTrackerInit();
private:
  imgStatusTracker* mTracker;
};

#endif
