/*
 *  Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.
 *
 *  Use of this source code is governed by a BSD-style license
 *  that can be found in the LICENSE file in the root of the source
 *  tree. An additional intellectual property rights grant can be found
 *  in the file PATENTS.  All contributing project authors may
 *  be found in the AUTHORS file in the root of the source tree.
 */

#ifndef WEBRTC_MODULES_DESKTOP_CAPTURE_WIN_DXGI_DUPLICATOR_CONTROLLER_H_
#define WEBRTC_MODULES_DESKTOP_CAPTURE_WIN_DXGI_DUPLICATOR_CONTROLLER_H_

#include <D3DCommon.h>

#include <memory>
#include <vector>

#include "webrtc/base/criticalsection.h"
#include "webrtc/modules/desktop_capture/desktop_geometry.h"
#include "webrtc/modules/desktop_capture/desktop_region.h"
#include "webrtc/modules/desktop_capture/shared_desktop_frame.h"
#include "webrtc/modules/desktop_capture/win/d3d_device.h"
#include "webrtc/modules/desktop_capture/win/dxgi_adapter_duplicator.h"

namespace webrtc {

// A controller for all the objects we need to call Windows DirectX capture APIs
// It's a singleton because only one IDXGIOutputDuplication instance per monitor
// is allowed per application.
//
// Consumers should create a DxgiDuplicatorController::Context and keep it
// throughout their lifetime, and pass it when calling Duplicate(). Consumers
// can also call IsSupported() to determine whether the system supports DXGI
// duplicator or not. If a previous IsSupported() function call returns true,
// but a later Duplicate() returns false, this usually means the display mode is
// changing. Consumers should retry after a while. (Typically 50 milliseconds,
// but according to hardware performance, this time may vary.)
class DxgiDuplicatorController {
 public:
  // A context to store the status of a single consumer of
  // DxgiDuplicatorController.
  class Context {
   public:
    Context();
    // Unregister this Context instance from all Dxgi duplicators during
    // destructing.
    ~Context();

   private:
    friend class DxgiDuplicatorController;

    // A Context will have an exactly same |identity_| as
    // DxgiDuplicatorController, to ensure it has been correctly setted up after
    // each DxgiDuplicatorController::Initialize().
    int identity_ = 0;

    // Child DxgiAdapterDuplicator::Context belongs to this Context.
    std::vector<DxgiAdapterDuplicator::Context> contexts_;
  };

  // A collection of D3d information we are interested on, which may impact
  // capturer performance or reliability.
  struct D3dInfo {
    // Each video adapter has its own D3D_FEATURE_LEVEL, so this structure
    // contains the minimum and maximium D3D_FEATURE_LEVELs current system
    // supports.
    // Both fields can be 0, which is the default value to indicate no valid
    // D3D_FEATURE_LEVEL has been retrieved from underlying OS APIs.
    D3D_FEATURE_LEVEL min_feature_level;
    D3D_FEATURE_LEVEL max_feature_level;

    // TODO(zijiehe): Add more fields, such as manufacturer name, mode, driver
    // version.
  };

  // Returns the singleton instance of DxgiDuplicatorController.
  static DxgiDuplicatorController* Instance();

  // Destructs current instance. We need to make sure COM components and their
  // containers are destructed in correct order.
  ~DxgiDuplicatorController();

  // All the following functions implicitly call Initialize() function if
  // current instance has not been initialized.

  // Detects whether the system supports DXGI based capturer.
  bool IsSupported();

  // Returns a copy of D3dInfo composed by last Initialize() function call.
  bool RetrieveD3dInfo(D3dInfo* info);

  // Captures current screen and writes into target. Since we are using double
  // buffering, |last_frame|.updated_region() is used to represent the not
  // updated regions in current |target| frame, which should also be copied this
  // time.
  // TODO(zijiehe): Windows cannot guarantee the frames returned by each
  // IDXGIOutputDuplication are synchronized. But we are using a totally
  // different threading model than the way Windows suggested, it's hard to
  // synchronize them manually. We should find a way to do it.
  bool Duplicate(Context* context, SharedDesktopFrame* target);

  // Captures one monitor and writes into target. |monitor_id| should >= 0. If
  // |monitor_id| is greater than the total screen count of all the Duplicators,
  // this function returns false.
  bool DuplicateMonitor(Context* context,
                        int monitor_id,
                        SharedDesktopFrame* target);

  // Returns dpi of current system. Returns an empty DesktopVector if system
  // does not support DXGI based capturer.
  DesktopVector dpi();

  // Returns entire desktop size. Returns an empty DesktopRect if system does
  // not support DXGI based capturer.
  DesktopRect desktop_rect();

  // Returns a DesktopSize to cover entire desktop_rect. This may be different
  // than desktop_rect().size(), since top-left screen does not need to start
  // from (0, 0).
  DesktopSize desktop_size();

  // Returns the size of one screen. |monitor_id| should be >= 0. If system does
  // not support DXGI based capturer, or |monitor_id| is greater than the total
  // screen count of all the Duplicators, this function returns an empty
  // DesktopRect.
  DesktopRect ScreenRect(int id);

  // Returns the count of screens on the system. These screens can be retrieved
  // by an integer in the range of [0, ScreenCount()). If system does not
  // support DXGI based capturer, this function returns 0.
  int ScreenCount();

 private:
  // Context calls private Unregister(Context*) function during
  // destructing.
  friend class Context;

  // A private constructor to ensure consumers to use
  // DxgiDuplicatorController::Instance().
  DxgiDuplicatorController();

  // Unregisters Context from this instance and all DxgiAdapterDuplicator(s)
  // it owns.
  void Unregister(const Context* const context);

  // All functions below should be called in |lock_| locked scope.

  // If current instance has not been initialized, executes DoInitialize
  // function, and returns initialize result. Otherwise directly returns true.
  bool Initialize();

  bool DoInitialize();

  // Clears all COM components referred by this instance. So next Duplicate()
  // call will eventually initialize this instance again.
  void Deinitialize();

  // A helper function to check whether a Context has been expired.
  bool ContextExpired(const Context* const context) const;

  // Updates Context if needed.
  void Setup(Context* context);

  // Do the real duplication work. |monitor_id < 0| to capture entire screen.
  bool DoDuplicate(Context* context,
                   int monitor_id,
                   SharedDesktopFrame* target);

  // This lock must be locked whenever accessing any of the following objects.
  rtc::CriticalSection lock_;

  // A self-incremented integer to compare with the one in Context, to
  // ensure a Context has been initialized after DxgiDuplicatorController.
  int identity_ = 0;
  DesktopRect desktop_rect_;
  DesktopVector dpi_;
  std::vector<DxgiAdapterDuplicator> duplicators_;
  D3dInfo d3d_info_;
};

}  // namespace webrtc

#endif  // WEBRTC_MODULES_DESKTOP_CAPTURE_WIN_DXGI_DUPLICATOR_CONTROLLER_H_
