/* 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 MediaEngineSource_h
#define MediaEngineSource_h

#include "MediaSegment.h"
#include "MediaTrackConstraints.h"
#include "PerformanceRecorder.h"
#include "mozilla/RefPtr.h"
#include "mozilla/dom/MediaStreamTrackBinding.h"
#include "mozilla/media/MediaUtils.h"
#include "nsStringFwd.h"

namespace mozilla {

namespace dom {
class Blob;
struct MediaTrackCapabilities;
struct MediaTrackSettings;
}  // namespace dom

namespace ipc {
class PrincipalInfo;
}  // namespace ipc

class MediaEnginePhotoCallback;
class MediaEnginePrefs;
class MediaTrack;

/**
 * Callback interface for TakePhoto(). Either PhotoComplete() or PhotoError()
 * should be called.
 */
class MediaEnginePhotoCallback {
 public:
  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(MediaEnginePhotoCallback)

  // aBlob is the image captured by MediaEngineSource. It is
  // called on main thread.
  virtual nsresult PhotoComplete(already_AddRefed<dom::Blob> aBlob) = 0;

  // It is called on main thread. aRv is the error code.
  virtual nsresult PhotoError(nsresult aRv) = 0;

 protected:
  virtual ~MediaEnginePhotoCallback() = default;
};

/**
 * Lifecycle state of MediaEngineSource.
 */
enum MediaEngineSourceState {
  kAllocated,  // Allocated, not yet started.
  kStarted,    // Previously allocated or stopped, then started.
  kStopped,    // Previously started, then stopped.
  kReleased    // Not allocated.
};

/**
 * The pure interface of a MediaEngineSource.
 *
 * Most sources are helped by the defaults implemented in MediaEngineSource.
 */
class MediaEngineSourceInterface {
 public:
  /**
   * Return true if this is a fake source. I.e., if it is generating media
   * itself rather than being an interface to underlying hardware.
   */
  virtual bool IsFake() const = 0;

  /**
   * Override w/a promise if source has frames, in order to potentially allow
   * deferring success of source acquisition until first frame has arrived.
   */
  virtual RefPtr<GenericNonExclusivePromise> GetFirstFramePromise() const {
    return nullptr;
  }

  /**
   * Get an id uniquely identifying the source of video frames that this
   * MediaEngineSource represents. This can be used in profiler markers to
   * separate markers from different sources into different lanes.
   */
  virtual const TrackingId& GetTrackingId() const = 0;

  /**
   * Called by MediaEngine to allocate an instance of this source.
   */
  virtual nsresult Allocate(const dom::MediaTrackConstraints& aConstraints,
                            const MediaEnginePrefs& aPrefs, uint64_t aWindowID,
                            const char** aOutBadConstraint) = 0;

  /**
   * Called by MediaEngine when a MediaTrack has been provided for the source to
   * feed data to.
   *
   * This must be called before Start.
   */
  virtual void SetTrack(const RefPtr<MediaTrack>& aTrack,
                        const PrincipalHandle& aPrincipal) = 0;

  /**
   * Called by MediaEngine to start feeding data to the track.
   *
   * NB: Audio sources handle the enabling of pulling themselves.
   */
  virtual nsresult Start() = 0;

  /**
   * This brings focus to the selected source, e.g. to bring a captured window
   * to the front.
   *
   * We return one of the following:
   * NS_OK                    - Success.
   * NS_ERROR_NOT_AVAILABLE   - For backends where focusing does not make sense.
   * NS_ERROR_NOT_IMPLEMENTED - For backends where focusing makes sense, but
   *                            is not yet implemented.
   * NS_ERROR_FAILURE         - Failures reported from underlying code.
   */
  virtual nsresult FocusOnSelectedSource() = 0;

  /**
   * Applies new constraints to the capability selection for the underlying
   * device.
   *
   * Should the constraints lead to choosing a new capability while the device
   * is actively being captured, the device will restart using the new
   * capability.
   *
   * We return one of the following:
   * NS_OK                - Successful reconfigure.
   * NS_ERROR_INVALID_ARG - Couldn't find a capability fitting aConstraints.
   *                        See aBadConstraint for details.
   * NS_ERROR_UNEXPECTED  - Reconfiguring the underlying device failed
   *                        unexpectedly. This leaves the device in a stopped
   *                        state.
   */
  virtual nsresult Reconfigure(const dom::MediaTrackConstraints& aConstraints,
                               const MediaEnginePrefs& aPrefs,
                               const char** aOutBadConstraint) = 0;

  /**
   * Called by MediaEngine to stop feeding data to the track.
   *
   * Double-stopping is allowed and will return NS_OK. This is necessary
   * sometimes during shutdown.
   *
   * NB: Audio sources handle the disabling of pulling themselves.
   */
  virtual nsresult Stop() = 0;

  /**
   * Called by MediaEngine to deallocate an underlying device.
   */
  virtual nsresult Deallocate() = 0;

  /**
   * If implementation of MediaEngineSource supports TakePhoto(), the picture
   * should be returned via aCallback object. Otherwise, it returns
   * NS_ERROR_NOT_IMPLEMENTED.
   */
  virtual nsresult TakePhoto(MediaEnginePhotoCallback* aCallback) = 0;

  /**
   * GetBestFitnessDistance returns the best distance the capture device can
   * offer as a whole, given an accumulated number of ConstraintSets. Ideal
   * values are considered in the first ConstraintSet only. Plain values are
   * treated as Ideal in the first ConstraintSet. Plain values are treated as
   * Exact in subsequent ConstraintSets. Infinity = UINT32_MAX e.g. device
   * cannot satisfy accumulated ConstraintSets. A finite result may be used to
   * calculate this device's ranking as a choice.
   */
  virtual uint32_t GetBestFitnessDistance(
      const nsTArray<const NormalizedConstraintSet*>& aConstraintSets,
      const MediaEnginePrefs& aPrefs) const = 0;

  /**
   * Returns the current settings of the underlying device.
   *
   * Note that this might not be the settings of the underlying hardware.
   * In case of a camera where we intervene and scale frames to avoid
   * leaking information from other documents than the current one,
   * GetSettings() will return the scaled resolution. I.e., the
   * device settings as seen by js.
   */
  virtual void GetSettings(dom::MediaTrackSettings& aOutSettings) const = 0;

  virtual void GetCapabilities(
      dom::MediaTrackCapabilities& aOutCapabilities) const = 0;
};

/**
 * Abstract base class for MediaEngineSources.
 *
 * Implements defaults for some common MediaEngineSourceInterface methods below.
 * Also implements RefPtr support and an owning-thread model for thread safety
 * checks in subclasses.
 */
class MediaEngineSource : public MediaEngineSourceInterface {
 public:
  // code inside webrtc.org assumes these sizes; don't use anything smaller
  // without verifying it's ok
  static const unsigned int kMaxDeviceNameLength = 128;
  static const unsigned int kMaxUniqueIdLength = 256;

  /**
   * Returns true if the given source type is for video, false otherwise.
   * Only call with real types.
   */
  static bool IsVideo(dom::MediaSourceEnum aSource);

  /**
   * Returns true if the given source type is for audio, false otherwise.
   * Only call with real types.
   */
  static bool IsAudio(dom::MediaSourceEnum aSource);

  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(MediaEngineSource)
  NS_DECL_OWNINGEVENTTARGET

  void AssertIsOnOwningThread() const {
    NS_ASSERT_OWNINGTHREAD(MediaEngineSource);
  }

  const TrackingId& GetTrackingId() const override {
    static auto notImplementedId = TrackingId();
    return notImplementedId;
  }

  // Not fake by default.
  bool IsFake() const override;

  // Returns NS_ERROR_NOT_AVAILABLE by default.
  nsresult FocusOnSelectedSource() override;

  // TakePhoto returns NS_ERROR_NOT_IMPLEMENTED by default,
  // to tell the caller to fallback to other methods.
  nsresult TakePhoto(MediaEnginePhotoCallback* aCallback) override;

  // Returns a default distance of 0 for devices that don't have capabilities.
  uint32_t GetBestFitnessDistance(
      const nsTArray<const NormalizedConstraintSet*>& aConstraintSets,
      const MediaEnginePrefs& aPrefs) const override {
    return 0;
  }

  virtual MediaEventSource<void>* CaptureEndedEvent() { return nullptr; }

 protected:
  virtual ~MediaEngineSource();
};

}  // namespace mozilla

#endif /* MediaEngineSource_h */
