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

#include <comdef.h>
#include <dxgi.h>
#include <dxgi1_2.h>
#include <wrl/client.h>

#include <memory>
#include <string>
#include <vector>

#include "modules/desktop_capture/desktop_frame_rotation.h"
#include "modules/desktop_capture/desktop_geometry.h"
#include "modules/desktop_capture/desktop_region.h"
#include "modules/desktop_capture/shared_desktop_frame.h"
#include "modules/desktop_capture/win/d3d_device.h"
#include "modules/desktop_capture/win/dxgi_context.h"
#include "modules/desktop_capture/win/dxgi_texture.h"
#include "rtc_base/thread_annotations.h"

namespace webrtc {

// Duplicates the content on one IDXGIOutput, i.e. one monitor attached to one
// video card. None of functions in this class is thread-safe.
class DxgiOutputDuplicator {
 public:
  using Context = DxgiOutputContext;

  // Creates an instance of DxgiOutputDuplicator from a D3dDevice and one of its
  // IDXGIOutput1. Caller must maintain the lifetime of device, to make sure it
  // outlives this instance. Only DxgiAdapterDuplicator can create an instance.
  DxgiOutputDuplicator(const D3dDevice& device,
                       const Microsoft::WRL::ComPtr<IDXGIOutput1>& output,
                       const DXGI_OUTPUT_DESC& desc);

  // To allow this class to work with vector.
  DxgiOutputDuplicator(DxgiOutputDuplicator&& other);

  // Destructs this instance. We need to make sure texture_ has been released
  // before duplication_.
  ~DxgiOutputDuplicator();

  // Initializes duplication_ object.
  bool Initialize();

  // Copies the content of current IDXGIOutput to the `target`. To improve the
  // performance, this function copies only regions merged from
  // `context`->updated_region and DetectUpdatedRegion(). The `offset` decides
  // the offset in the `target` where the content should be copied to. i.e. this
  // function copies the content to the rectangle of (offset.x(), offset.y()) to
  // (offset.x() + desktop_rect_.width(), offset.y() + desktop_rect_.height()).
  // Returns false in case of a failure.
  // May retain a reference to `target` so that a "captured" frame can be
  // returned in the event that a new frame is not ready to be captured yet.
  // (Or in other words, if the call to IDXGIOutputDuplication::AcquireNextFrame
  // indicates that there is not yet a new frame, this is usually because no
  // updates have occurred to the frame).
  bool Duplicate(Context* context,
                 DesktopVector offset,
                 SharedDesktopFrame* target);

  // Returns the desktop rect covered by this DxgiOutputDuplicator.
  DesktopRect desktop_rect() const { return desktop_rect_; }

  // Returns the device name from DXGI_OUTPUT_DESC in utf8 encoding.
  const std::string& device_name() const { return device_name_; }

  void Setup(Context* context);

  void Unregister(const Context* const context);

  // How many frames have been captured by this DxigOutputDuplicator.
  int64_t num_frames_captured() const;

  // Moves `desktop_rect_`. See DxgiDuplicatorController::TranslateRect().
  void TranslateRect(const DesktopVector& position);

 private:
  // Calls DoDetectUpdatedRegion(). If it fails, this function sets the
  // `updated_region` as entire UntranslatedDesktopRect().
  void DetectUpdatedRegion(const DXGI_OUTDUPL_FRAME_INFO& frame_info,
                           DesktopRegion* updated_region);

  // Returns untranslated updated region, which are directly returned by Windows
  // APIs. Returns false in case of a failure.
  bool DoDetectUpdatedRegion(const DXGI_OUTDUPL_FRAME_INFO& frame_info,
                             DesktopRegion* updated_region);

  bool ReleaseFrame();

  // Initializes duplication_ instance. Expects duplication_ is in empty status.
  // Returns false if system does not support IDXGIOutputDuplication.
  bool DuplicateOutput();

  // Returns a DesktopRect with the same size of desktop_size(), but translated
  // by offset.
  DesktopRect GetTranslatedDesktopRect(DesktopVector offset) const;

  // Returns a DesktopRect with the same size of desktop_size(), but starts from
  // (0, 0).
  DesktopRect GetUntranslatedDesktopRect() const;

  // Spreads changes from `context` to other registered Context(s) in
  // contexts_.
  void SpreadContextChange(const Context* const context);

  // Returns the size of desktop rectangle current instance representing.
  DesktopSize desktop_size() const;

  const D3dDevice device_;
  const Microsoft::WRL::ComPtr<IDXGIOutput1> output_;
  const std::string device_name_;
  DesktopRect desktop_rect_;
  Microsoft::WRL::ComPtr<IDXGIOutputDuplication> duplication_;
  DXGI_OUTDUPL_DESC desc_;
  std::vector<uint8_t> metadata_;
  std::unique_ptr<DxgiTexture> texture_;
  Rotation rotation_;
  DesktopSize unrotated_size_;

  // After each AcquireNextFrame() function call, updated_region_(s) of all
  // active Context(s) need to be updated. Since they have missed the
  // change this time. And during next Duplicate() function call, their
  // updated_region_ will be merged and copied.
  std::vector<Context*> contexts_;

  // The last full frame of this output and its offset. If on AcquireNextFrame()
  // failed because of timeout, i.e. no update, we can copy content from
  // `last_frame_`.
  std::unique_ptr<SharedDesktopFrame> last_frame_;
  DesktopVector last_frame_offset_;

  int64_t num_frames_captured_ = 0;
};

}  // namespace webrtc

#endif  // MODULES_DESKTOP_CAPTURE_WIN_DXGI_OUTPUT_DUPLICATOR_H_
