// 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_IMPELLER_TOOLKIT_EGL_CONTEXT_H_
#define FLUTTER_IMPELLER_TOOLKIT_EGL_CONTEXT_H_

#include <functional>

#include "impeller/base/comparable.h"
#include "impeller/base/thread.h"
#include "impeller/toolkit/egl/egl.h"

namespace impeller {
namespace egl {

class Surface;
class Display;

//------------------------------------------------------------------------------
/// @brief      An instance of an EGL context.
///
///             An EGL context can only be used on a single thread at a given
///             time. A thread can only have a single context current at any
///             given time.
///
///             Context cannot be created directly. Only a valid instance of an
///             egl::Display can create a context.
///
class Context {
 public:
  ~Context();

  //----------------------------------------------------------------------------
  /// @brief      Determines if a valid context could be created. The context
  ///             still needs to be made current on the thread for it to be
  ///             useful.
  ///
  /// @return     True if valid, False otherwise.
  ///
  bool IsValid() const;

  //----------------------------------------------------------------------------
  /// @brief      Get the underlying handle to the EGL context.
  ///
  /// @return     The handle.
  ///
  const EGLContext& GetHandle() const;

  //----------------------------------------------------------------------------
  /// @brief      Make the context current on the calling thread. It is the
  ///             caller responsibility to ensure that any context previously
  ///             current on the thread must be cleared via `ClearCurrent`.
  ///
  /// @important  The config used to create the surface must match the config
  ///             used to create this context instance.
  ///
  /// @param[in]  surface  The surface to use to make the context current.
  ///
  /// @return     If the context could be made current on the callers thread.
  ///
  bool MakeCurrent(const Surface& surface) const;

  //----------------------------------------------------------------------------
  /// @brief      Clear the thread association of this context.
  ///
  /// @return     If the thread association could be cleared.
  ///
  bool ClearCurrent() const;

  enum class LifecycleEvent {
    kDidMakeCurrent,
    kWillClearCurrent,
  };
  using LifecycleListener = std::function<void(LifecycleEvent)>;
  //----------------------------------------------------------------------------
  /// @brief      Add a listener that gets invoked when the context is made and
  ///             cleared current from the thread. Applications typically use
  ///             this to manage workers that schedule OpenGL API calls that
  ///             need to be careful about the context being current when
  ///             called.
  ///
  /// @param[in]  listener  The listener
  ///
  /// @return     A unique ID for the listener that can used used in
  ///             `RemoveLifecycleListener` to remove a previously added
  ///             listener.
  ///
  std::optional<UniqueID> AddLifecycleListener(
      const LifecycleListener& listener);

  //----------------------------------------------------------------------------
  /// @brief      Remove a previously added context listener.
  ///
  /// @param[in]  id    The identifier obtained via a previous call to
  ///                   `AddLifecycleListener`.
  ///
  /// @return     True if the listener could be removed.
  ///
  bool RemoveLifecycleListener(UniqueID id);

  //----------------------------------------------------------------------------
  /// @return     True if the context is current and attached to any surface,
  ///             False otherwise.
  ///
  bool IsCurrent() const;

 private:
  friend class Display;

  EGLDisplay display_ = EGL_NO_DISPLAY;
  EGLContext context_ = EGL_NO_CONTEXT;
  mutable RWMutex listeners_mutex_;
  std::map<UniqueID, LifecycleListener> listeners_
      IPLR_GUARDED_BY(listeners_mutex_);

  Context(EGLDisplay display, EGLContext context);

  void DispatchLifecyleEvent(LifecycleEvent event) const;

  Context(const Context&) = delete;

  Context& operator=(const Context&) = delete;
};

}  // namespace egl
}  // namespace impeller

#endif  // FLUTTER_IMPELLER_TOOLKIT_EGL_CONTEXT_H_
