// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef CHROME_BROWSER_UI_VIEWS_ACCESSIBILITY_UIA_ACCESSIBILITY_EVENT_WAITER_H_
#define CHROME_BROWSER_UI_VIEWS_ACCESSIBILITY_UIA_ACCESSIBILITY_EVENT_WAITER_H_

#include <ole2.h>

#include <stdint.h>
#include <wrl/client.h>
#include <wrl/implements.h>

#include <string>
#include <vector>

#include "base/memory/raw_ptr.h"
#include "base/process/process_handle.h"
#include "base/run_loop.h"
#include "base/synchronization/waitable_event.h"
#include "base/threading/platform_thread.h"
#include "base/time/time.h"
#include "ui/views/accessibility/view_accessibility.h"

#include <uiautomation.h>

struct UiaAccessibilityWaiterInfo {
  HWND hwnd;
  std::wstring role;
  std::wstring name;
  ax::mojom::Event event;
  // Optional: specify a direct UIA event ID. If set to a non-zero value,
  // this takes precedence over the ax::mojom::Event mapping.
  EVENTID uia_event_id = 0;
};

class UiaAccessibilityEventWaiter {
 public:
  explicit UiaAccessibilityEventWaiter(UiaAccessibilityWaiterInfo info);
  ~UiaAccessibilityEventWaiter();

  void Wait();
  void WaitWithTimeout(base::TimeDelta timeout);

  // Returns the notification string captured from the last notification event.
  // Only valid after Wait() returns when waiting for UIA_NotificationEventId.
  const std::wstring& GetNotificationString() const {
    return notification_string_;
  }

 private:
  // All UIA calls need to be made on a secondary MTA thread to avoid sporadic
  // test hangs / timeouts.
  class Thread : public base::PlatformThread::Delegate {
   public:
    Thread();
    ~Thread() override;

    void Init(UiaAccessibilityEventWaiter* owner,
              const UiaAccessibilityWaiterInfo& info,
              base::OnceClosure initialization_loop,
              base::OnceClosure shutdown_loop);

    void SendShutdownSignal();
    void SetNotificationString(const std::wstring& str);

    void ThreadMain() override;

   protected:
    UiaAccessibilityWaiterInfo info_;

   private:
    raw_ptr<UiaAccessibilityEventWaiter> owner_ = nullptr;

    Microsoft::WRL::ComPtr<IUIAutomation5> uia_;
    Microsoft::WRL::ComPtr<IUIAutomationElement> root_;
    Microsoft::WRL::ComPtr<IUIAutomationCacheRequest> cache_request_;

    // Thread synchronization members.
    base::OnceClosure initialization_complete_;
    base::OnceClosure shutdown_complete_;
    base::WaitableEvent shutdown_signal_;

    // An implementation of various UIA interfaces that forward event
    // notifications to the waiter.
    class EventHandler final
        : public Microsoft::WRL::RuntimeClass<
              Microsoft::WRL::RuntimeClassFlags<Microsoft::WRL::ClassicCom>,
              IUIAutomationFocusChangedEventHandler,
              IUIAutomationPropertyChangedEventHandler,
              IUIAutomationStructureChangedEventHandler,
              IUIAutomationEventHandler,
              IUIAutomationNotificationEventHandler> {
     public:
      EventHandler();

      EventHandler(const EventHandler&) = delete;
      EventHandler& operator=(const EventHandler&) = delete;

      ~EventHandler() override;

      void Init(UiaAccessibilityEventWaiter::Thread* owner,
                Microsoft::WRL::ComPtr<IUIAutomationElement> root);
      void CleanUp();

      // IUIAutomationFocusChangedEventHandler interface.
      IFACEMETHODIMP HandleFocusChangedEvent(
          IUIAutomationElement* sender) override;

      // IUIAutomationPropertyChangedEventHandler interface.
      IFACEMETHODIMP HandlePropertyChangedEvent(IUIAutomationElement* sender,
                                                PROPERTYID property_id,
                                                VARIANT new_value) override;

      // IUIAutomationStructureChangedEventHandler interface.
      IFACEMETHODIMP HandleStructureChangedEvent(
          IUIAutomationElement* sender,
          StructureChangeType change_type,
          SAFEARRAY* runtime_id) override;

      // IUIAutomationEventHandler interface.
      IFACEMETHODIMP HandleAutomationEvent(IUIAutomationElement* sender,
                                           EVENTID event_id) override;

      // IUIAutomationNotificationEventHandler interface.
      IFACEMETHODIMP HandleNotificationEvent(
          IUIAutomationElement* sender,
          NotificationKind notification_kind,
          NotificationProcessing notification_processing,
          BSTR display_string,
          BSTR activity_id) override;

      // Points to the waiter to receive notifications.
      raw_ptr<UiaAccessibilityEventWaiter::Thread> owner_ = nullptr;

     private:
      bool MatchesNameRole(IUIAutomationElement* sender);

      Microsoft::WRL::ComPtr<IUIAutomationElement> root_;
    };
    Microsoft::WRL::ComPtr<EventHandler> uia_event_handler_;
  };

  Thread thread_;
  base::RunLoop shutdown_loop_;
  base::PlatformThreadHandle thread_handle_;
  std::wstring notification_string_;
};

#endif  // CHROME_BROWSER_UI_VIEWS_ACCESSIBILITY_UIA_ACCESSIBILITY_EVENT_WAITER_H_
