// Copyright (c) 2025, the Dart project authors.  Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

#ifndef RUNTIME_ENGINE_ENGINE_H_
#define RUNTIME_ENGINE_ENGINE_H_

#include <memory>
#include <unordered_map>
#include <vector>
#include "include/dart_engine.h"
#include "platform/synchronization.h"

namespace dart {
namespace engine {

// Manages Dart VM initialization/shutdown, isolates, and all state that needs
// to be disposed (e.g. buffers of loaded snapshots).
//
// Since Dart VM has a global state, there should be only one instance of this
// class (accessible through Engine::instance()).
class Engine {
 public:
  // Loads Kernel snapshot from given file path.
  //
  // Just reads all bytes into a single buffer, owned by this class.
  DartEngine_SnapshotData KernelFromFile(const char* path, char** error);

  // Loads AOT snapshot from given file path.
  //
  // Opens given AOT snapshot as a shared library and loads buffer symbols from
  // it.
  DartEngine_SnapshotData AotFromFile(const char* path, char** error);

  // Starts an isolate from a snapshot.
  //
  // Starting an isolate also routes message notify callback to NotifyMessage.
  Dart_Isolate StartIsolate(const DartEngine_SnapshotData snapshot,
                            char** error);

  // Acquires a lock for an isolate, so that the thread can enter it.
  void LockIsolate(Dart_Isolate isolate);

  // Releases the lock obtained by LockIsolate.
  void UnlockIsolate(Dart_Isolate isolate);

  // Called by Dart VM via Engine::MessageNotifyCallback.
  //
  // Calls isolate-specific message scheduler (if any), default message
  // scheduler, or ignores the message.
  void NotifyMessage(Dart_Isolate isolate);

  // Calls Dart_HandleMessage, managing an isolate lock and Dart scope.
  void HandleMessage(Dart_Isolate isolate);

  // Drains the microtasks queue, requires an active isolate.
  //
  // If a microtask throws, the error is returned to the caller,
  // and the queue may still contain more entries.
  // The caller should continue to drain the queue after handling the error.
  Dart_Handle DrainMicrotasksQueue();

  // Sets a callback to be called when Dart_HandleMessage returns an error.
  void SetHandleMessageErrorCallback(
      DartEngine_HandleMessageErrorCallback callback);

  // Sets a message scheduler for a given isolate.
  void SetDefaultMessageScheduler(DartEngine_MessageScheduler scheduler);

  // Sets default message scheduler for isolates without dedicated
  // message scheduler.
  void SetMessageScheduler(DartEngine_MessageScheduler scheduler,
                           Dart_Isolate isolate);

  // Initializes embedder and partially initializes Dart VM.
  //
  // Full initialization happens only when the user starts the first isolate, as
  // in case of AOT snapshots we don't have vm snapshot data and vm snapshot
  // instuructions before the user makes the first call to StartIsolate.
  bool Initialize(char** error);

  // Shuts down the engine.
  //
  // Once shut down, it is not supposed to be re-initialized.
  void Shutdown();

  static Engine* instance();
  // Passed to Dart_SetMessageNotifyCallback.
  static void MessageNotifyCallback(Dart_Isolate isolate);
  // Passed to user-previded message scheduler callback.
  static void HandleMessageCallback(Dart_Isolate isolate);

 private:
  // Engine's internal data for isolate.
  struct IsolateData {
    DartEngine_MessageScheduler scheduler;
    Mutex mutex;
    Dart_PersistentHandle isolate_library;
    Dart_PersistentHandle drain_microtasks_function_name;
  };

  // Set to false once shutdown starts.
  bool is_running_ = false;

  // Controls initalization and shutdown, and prevents deadlocks when delivering
  // messages during shutdown.
  Mutex engine_lifecycle_;

  // Guards access to engine state.
  Mutex engine_state_;

  // Whether Engine::Initialize is called.
  bool initialized_ = false;

  // Added because of AOT mode, because in AOT mode Dart VM cannot be fully
  // initialized until we get vm snapshot data, which we typically get when the
  // user starts the first isolate.
  bool first_isolate_started_ = false;

  // Snapshots, whose buffers are owned by the engine.
  // and are freed on Shutdown.
  std::vector<DartEngine_SnapshotData> owned_snapshots_;

  // Loaded dynamic libraries. Dynamic libraries are loaded
  // when reading AOT snapshots.
  std::vector<void*> loaded_libraries_;

  // All isolates, started via Engine::StartIsolate.
  std::vector<Dart_Isolate> isolates_;

  // Stores per-isolate engine state.
  std::unordered_map<Dart_Isolate, std::shared_ptr<IsolateData>> isolate_data_;

  // Default scheduler.
  DartEngine_MessageScheduler default_scheduler_;

  // Callback to notify about Dart_HandleMessage errors.
  DartEngine_HandleMessageErrorCallback handle_message_error_callback_;

  // Helper function to get an element from isolate_data_.
  std::shared_ptr<IsolateData> DataForIsolate(Dart_Isolate isolate);
};

}  // namespace engine
}  // namespace dart
#endif  // RUNTIME_ENGINE_ENGINE_H_
