// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef FLUTTER_SHELL_PLATFORM_WINDOWS_DIRECT_MANIPULATION_H_
#define FLUTTER_SHELL_PLATFORM_WINDOWS_DIRECT_MANIPULATION_H_

#include "flutter/fml/memory/ref_counted.h"

#include <wrl/client.h>
#include "directmanipulation.h"

namespace flutter {

class Window;
class WindowBindingHandlerDelegate;

class DirectManipulationEventHandler;

// Owner for a DirectManipulation event handler, contains the link between
// DirectManipulation and WindowBindingHandlerDelegate.
class DirectManipulationOwner {
 public:
  explicit DirectManipulationOwner(Window* window);
  virtual ~DirectManipulationOwner() = default;
  // Initialize a DirectManipulation viewport with specified width and height.
  // These should match the width and height of the application window.
  int Init(unsigned int width, unsigned int height);
  // Resize the DirectManipulation viewport. Should be called when the
  // application window is resized.
  void ResizeViewport(unsigned int width, unsigned int height);
  // Set the WindowBindingHandlerDelegate which will receive callbacks based on
  // DirectManipulation updates.
  void SetBindingHandlerDelegate(
      WindowBindingHandlerDelegate* binding_handler_delegate);
  // Called when DM_POINTERHITTEST occurs with an acceptable pointer type. Will
  // start DirectManipulation for that interaction.
  virtual void SetContact(UINT contactId);
  // Called to get updates from DirectManipulation. Should be called frequently
  // to provide smooth updates.
  void Update();
  // Release child event handler and OS resources.
  void Destroy();
  // The target that should be updated when DirectManipulation provides a new
  // pan/zoom transformation.
  WindowBindingHandlerDelegate* binding_handler_delegate;

 private:
  // The window gesture input is occuring on.
  Window* window_;
  // Cookie needed to register child event handler with viewport.
  DWORD viewportHandlerCookie_;
  // Object needed for operation of the DirectManipulation API.
  Microsoft::WRL::ComPtr<IDirectManipulationManager> manager_;
  // Object needed for operation of the DirectManipulation API.
  Microsoft::WRL::ComPtr<IDirectManipulationUpdateManager> updateManager_;
  // Object needed for operation of the DirectManipulation API.
  Microsoft::WRL::ComPtr<IDirectManipulationViewport> viewport_;
  // Child needed for operation of the DirectManipulation API.
  fml::RefPtr<DirectManipulationEventHandler> handler_;

  FML_DISALLOW_COPY_AND_ASSIGN(DirectManipulationOwner);
};

// Implements DirectManipulation event handling interfaces, receives calls from
// system when gesture events occur.
class DirectManipulationEventHandler
    : public fml::RefCountedThreadSafe<DirectManipulationEventHandler>,
      public IDirectManipulationViewportEventHandler,
      public IDirectManipulationInteractionEventHandler {
  friend class DirectManipulationOwner;
  FML_FRIEND_REF_COUNTED_THREAD_SAFE(DirectManipulationEventHandler);
  FML_FRIEND_MAKE_REF_COUNTED(DirectManipulationEventHandler);

 public:
  explicit DirectManipulationEventHandler(DirectManipulationOwner* owner)
      : owner_(owner) {}

  // |IUnknown|
  STDMETHODIMP QueryInterface(REFIID iid, void** ppv) override;

  // |IUnknown|
  ULONG STDMETHODCALLTYPE AddRef() override;

  // |IUnknown|
  ULONG STDMETHODCALLTYPE Release() override;

  // |IDirectManipulationViewportEventHandler|
  HRESULT STDMETHODCALLTYPE
  OnViewportStatusChanged(IDirectManipulationViewport* viewport,
                          DIRECTMANIPULATION_STATUS current,
                          DIRECTMANIPULATION_STATUS previous) override;

  // |IDirectManipulationViewportEventHandler|
  HRESULT STDMETHODCALLTYPE
  OnViewportUpdated(IDirectManipulationViewport* viewport) override;

  // |IDirectManipulationViewportEventHandler|
  HRESULT STDMETHODCALLTYPE
  OnContentUpdated(IDirectManipulationViewport* viewport,
                   IDirectManipulationContent* content) override;

  // |IDirectManipulationInteractionEventHandler|
  HRESULT STDMETHODCALLTYPE
  OnInteraction(IDirectManipulationViewport2* viewport,
                DIRECTMANIPULATION_INTERACTION_TYPE interaction) override;

 private:
  struct GestureData {
    float scale;
    float pan_x;
    float pan_y;
  };
  // Convert transform array to Flutter-usable values.
  GestureData ConvertToGestureData(float transform[6]);
  // Unique identifier to associate with all gesture event updates.
  int32_t GetDeviceId();
  // Parent object, used to store the target for gesture event updates.
  DirectManipulationOwner* owner_;
  // We need to reset some parts of DirectManipulation after each gesture
  // A flag is needed to ensure that false events created as the reset occurs
  // are not sent to the flutter framework.
  bool during_synthesized_reset_ = false;
  // Store whether current events are from synthetic inertia rather than user
  // input.
  bool during_inertia_ = false;
  // The transform might not be able to be reset before the next gesture, so
  // the initial state needs to be stored for reference.
  GestureData initial_gesture_data_ = {
      1,  // scale
      0,  // pan_x
      0,  // pan_y
  };
  // Store the difference between the last pan offsets to determine if inertia
  // has been cancelled in the middle of an animation.
  float last_pan_x_ = 0.0;
  float last_pan_y_ = 0.0;
  float last_pan_delta_x_ = 0.0;
  float last_pan_delta_y_ = 0.0;
};

}  // namespace flutter

#endif  // FLUTTER_SHELL_PLATFORM_WINDOWS_DIRECT_MANIPULATION_H_
