//===-- ScriptInterpreter.h -------------------------------------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

#ifndef LLDB_INTERPRETER_SCRIPTINTERPRETER_H
#define LLDB_INTERPRETER_SCRIPTINTERPRETER_H

#include "lldb/API/SBAttachInfo.h"
#include "lldb/API/SBBreakpoint.h"
#include "lldb/API/SBData.h"
#include "lldb/API/SBError.h"
#include "lldb/API/SBEvent.h"
#include "lldb/API/SBExecutionContext.h"
#include "lldb/API/SBLaunchInfo.h"
#include "lldb/API/SBMemoryRegionInfo.h"
#include "lldb/API/SBStream.h"
#include "lldb/Breakpoint/BreakpointOptions.h"
#include "lldb/Core/PluginInterface.h"
#include "lldb/Core/SearchFilter.h"
#include "lldb/Core/ThreadedCommunication.h"
#include "lldb/Host/PseudoTerminal.h"
#include "lldb/Host/StreamFile.h"
#include "lldb/Interpreter/Interfaces/OperatingSystemInterface.h"
#include "lldb/Interpreter/Interfaces/ScriptedPlatformInterface.h"
#include "lldb/Interpreter/Interfaces/ScriptedProcessInterface.h"
#include "lldb/Interpreter/Interfaces/ScriptedThreadInterface.h"
#include "lldb/Interpreter/ScriptObject.h"
#include "lldb/Utility/Broadcaster.h"
#include "lldb/Utility/Status.h"
#include "lldb/Utility/StructuredData.h"
#include "lldb/lldb-private.h"
#include <optional>

namespace lldb_private {

class ScriptInterpreterLocker {
public:
  ScriptInterpreterLocker() = default;

  virtual ~ScriptInterpreterLocker() = default;

private:
  ScriptInterpreterLocker(const ScriptInterpreterLocker &) = delete;
  const ScriptInterpreterLocker &
  operator=(const ScriptInterpreterLocker &) = delete;
};

class ExecuteScriptOptions {
public:
  ExecuteScriptOptions() = default;

  bool GetEnableIO() const { return m_enable_io; }

  bool GetSetLLDBGlobals() const { return m_set_lldb_globals; }

  // If this is true then any exceptions raised by the script will be
  // cleared with PyErr_Clear().   If false then they will be left for
  // the caller to clean up
  bool GetMaskoutErrors() const { return m_maskout_errors; }

  ExecuteScriptOptions &SetEnableIO(bool enable) {
    m_enable_io = enable;
    return *this;
  }

  ExecuteScriptOptions &SetSetLLDBGlobals(bool set) {
    m_set_lldb_globals = set;
    return *this;
  }

  ExecuteScriptOptions &SetMaskoutErrors(bool maskout) {
    m_maskout_errors = maskout;
    return *this;
  }

private:
  bool m_enable_io = true;
  bool m_set_lldb_globals = true;
  bool m_maskout_errors = true;
};

class LoadScriptOptions {
public:
  LoadScriptOptions() = default;

  bool GetInitSession() const { return m_init_session; }
  bool GetSilent() const { return m_silent; }

  LoadScriptOptions &SetInitSession(bool b) {
    m_init_session = b;
    return *this;
  }

  LoadScriptOptions &SetSilent(bool b) {
    m_silent = b;
    return *this;
  }

private:
  bool m_init_session = false;
  bool m_silent = false;
};

class ScriptInterpreterIORedirect {
public:
  /// Create an IO redirect. If IO is enabled, this will redirects the output
  /// to the command return object if set or to the debugger otherwise. If IO
  /// is disabled, it will redirect all IO to /dev/null.
  static llvm::Expected<std::unique_ptr<ScriptInterpreterIORedirect>>
  Create(bool enable_io, Debugger &debugger, CommandReturnObject *result);

  ~ScriptInterpreterIORedirect();

  lldb::FileSP GetInputFile() const { return m_input_file_sp; }
  lldb::FileSP GetOutputFile() const {
    return m_output_file_sp->GetUnlockedFileSP();
  }
  lldb::FileSP GetErrorFile() const {
    return m_error_file_sp->GetUnlockedFileSP();
  }

  /// Flush our output and error file handles.
  void Flush();

private:
  ScriptInterpreterIORedirect(std::unique_ptr<File> input,
                              std::unique_ptr<File> output);
  ScriptInterpreterIORedirect(Debugger &debugger, CommandReturnObject *result);

  lldb::FileSP m_input_file_sp;
  lldb::LockableStreamFileSP m_output_file_sp;
  lldb::LockableStreamFileSP m_error_file_sp;
  LockableStreamFile::Mutex m_output_mutex;
  ThreadedCommunication m_communication;
  bool m_disconnect;
};

class ScriptInterpreter : public PluginInterface {
public:
  enum ScriptReturnType {
    eScriptReturnTypeCharPtr,
    eScriptReturnTypeBool,
    eScriptReturnTypeShortInt,
    eScriptReturnTypeShortIntUnsigned,
    eScriptReturnTypeInt,
    eScriptReturnTypeIntUnsigned,
    eScriptReturnTypeLongInt,
    eScriptReturnTypeLongIntUnsigned,
    eScriptReturnTypeLongLong,
    eScriptReturnTypeLongLongUnsigned,
    eScriptReturnTypeFloat,
    eScriptReturnTypeDouble,
    eScriptReturnTypeChar,
    eScriptReturnTypeCharStrOrNone,
    eScriptReturnTypeOpaqueObject
  };

  ScriptInterpreter(Debugger &debugger, lldb::ScriptLanguage script_lang);

  virtual StructuredData::DictionarySP GetInterpreterInfo();

  ~ScriptInterpreter() override = default;

  virtual bool Interrupt() { return false; }

  virtual bool ExecuteOneLine(
      llvm::StringRef command, CommandReturnObject *result,
      const ExecuteScriptOptions &options = ExecuteScriptOptions()) = 0;

  virtual void ExecuteInterpreterLoop() = 0;

  virtual bool ExecuteOneLineWithReturn(
      llvm::StringRef in_string, ScriptReturnType return_type, void *ret_value,
      const ExecuteScriptOptions &options = ExecuteScriptOptions()) {
    return true;
  }

  virtual Status ExecuteMultipleLines(
      const char *in_string,
      const ExecuteScriptOptions &options = ExecuteScriptOptions()) {
    return Status::FromErrorString("not implemented");
  }

  virtual Status
  ExportFunctionDefinitionToInterpreter(StringList &function_def) {
    return Status::FromErrorString("not implemented");
  }

  virtual Status GenerateBreakpointCommandCallbackData(StringList &input,
                                                       std::string &output,
                                                       bool has_extra_args,
                                                       bool is_callback) {
    return Status::FromErrorString("not implemented");
  }

  virtual bool GenerateWatchpointCommandCallbackData(StringList &input,
                                                     std::string &output,
                                                     bool is_callback) {
    return false;
  }

  virtual bool GenerateTypeScriptFunction(const char *oneliner,
                                          std::string &output,
                                          const void *name_token = nullptr) {
    return false;
  }

  virtual bool GenerateTypeScriptFunction(StringList &input,
                                          std::string &output,
                                          const void *name_token = nullptr) {
    return false;
  }

  virtual bool GenerateScriptAliasFunction(StringList &input,
                                           std::string &output) {
    return false;
  }

  virtual bool GenerateTypeSynthClass(StringList &input, std::string &output,
                                      const void *name_token = nullptr) {
    return false;
  }

  virtual bool GenerateTypeSynthClass(const char *oneliner, std::string &output,
                                      const void *name_token = nullptr) {
    return false;
  }

  virtual StructuredData::ObjectSP
  CreateSyntheticScriptedProvider(const char *class_name,
                                  lldb::ValueObjectSP valobj) {
    return StructuredData::ObjectSP();
  }

  virtual StructuredData::GenericSP
  CreateScriptCommandObject(const char *class_name) {
    return StructuredData::GenericSP();
  }

  virtual StructuredData::GenericSP
  CreateFrameRecognizer(const char *class_name) {
    return StructuredData::GenericSP();
  }

  virtual lldb::ValueObjectListSP GetRecognizedArguments(
      const StructuredData::ObjectSP &implementor,
      lldb::StackFrameSP frame_sp) {
    return lldb::ValueObjectListSP();
  }

  virtual bool ShouldHide(const StructuredData::ObjectSP &implementor,
                          lldb::StackFrameSP frame_sp) {
    return false;
  }

  virtual StructuredData::GenericSP
  CreateScriptedBreakpointResolver(const char *class_name,
                                   const StructuredDataImpl &args_data,
                                   lldb::BreakpointSP &bkpt_sp) {
    return StructuredData::GenericSP();
  }

  virtual bool
  ScriptedBreakpointResolverSearchCallback(StructuredData::GenericSP implementor_sp,
                                           SymbolContext *sym_ctx)
  {
    return false;
  }

  virtual lldb::SearchDepth
  ScriptedBreakpointResolverSearchDepth(StructuredData::GenericSP implementor_sp)
  {
    return lldb::eSearchDepthModule;
  }

  virtual StructuredData::ObjectSP
  LoadPluginModule(const FileSpec &file_spec, lldb_private::Status &error) {
    return StructuredData::ObjectSP();
  }

  virtual StructuredData::DictionarySP
  GetDynamicSettings(StructuredData::ObjectSP plugin_module_sp, Target *target,
                     const char *setting_name, lldb_private::Status &error) {
    return StructuredData::DictionarySP();
  }

  virtual Status GenerateFunction(const char *signature,
                                  const StringList &input,
                                  bool is_callback) {
    return Status::FromErrorString("not implemented");
  }

  virtual void CollectDataForBreakpointCommandCallback(
      std::vector<std::reference_wrapper<BreakpointOptions>> &options,
      CommandReturnObject &result);

  virtual void
  CollectDataForWatchpointCommandCallback(WatchpointOptions *wp_options,
                                          CommandReturnObject &result);

  /// Set the specified text as the callback for the breakpoint.
  Status SetBreakpointCommandCallback(
      std::vector<std::reference_wrapper<BreakpointOptions>> &bp_options_vec,
      const char *callback_text);

  virtual Status SetBreakpointCommandCallback(BreakpointOptions &bp_options,
                                              const char *callback_text,
                                              bool is_callback) {
    return Status::FromErrorString("not implemented");
  }

  /// This one is for deserialization:
  virtual Status SetBreakpointCommandCallback(
      BreakpointOptions &bp_options,
      std::unique_ptr<BreakpointOptions::CommandData> &data_up) {
    return Status::FromErrorString("not implemented");
  }

  Status SetBreakpointCommandCallbackFunction(
      std::vector<std::reference_wrapper<BreakpointOptions>> &bp_options_vec,
      const char *function_name, StructuredData::ObjectSP extra_args_sp);

  /// Set a script function as the callback for the breakpoint.
  virtual Status
  SetBreakpointCommandCallbackFunction(BreakpointOptions &bp_options,
                                       const char *function_name,
                                       StructuredData::ObjectSP extra_args_sp) {
    return Status::FromErrorString("not implemented");
  }

  /// Set a one-liner as the callback for the watchpoint.
  virtual void SetWatchpointCommandCallback(WatchpointOptions *wp_options,
                                            const char *user_input,
                                            bool is_callback) {}

  virtual bool GetScriptedSummary(const char *function_name,
                                  lldb::ValueObjectSP valobj,
                                  StructuredData::ObjectSP &callee_wrapper_sp,
                                  const TypeSummaryOptions &options,
                                  std::string &retval) {
    return false;
  }

  // Calls the specified formatter matching Python function and returns its
  // result (true if it's a match, false if we should keep looking for a
  // matching formatter).
  virtual bool FormatterCallbackFunction(const char *function_name,
                                         lldb::TypeImplSP type_impl_sp) {
    return true;
  }

  virtual void Clear() {
    // Clean up any ref counts to SBObjects that might be in global variables
  }

  virtual size_t
  CalculateNumChildren(const StructuredData::ObjectSP &implementor,
                       uint32_t max) {
    return 0;
  }

  virtual lldb::ValueObjectSP
  GetChildAtIndex(const StructuredData::ObjectSP &implementor, uint32_t idx) {
    return lldb::ValueObjectSP();
  }

  virtual llvm::Expected<int>
  GetIndexOfChildWithName(const StructuredData::ObjectSP &implementor,
                          const char *child_name) {
    return llvm::createStringError("Type has no child named '%s'", child_name);
  }

  virtual bool
  UpdateSynthProviderInstance(const StructuredData::ObjectSP &implementor) {
    return false;
  }

  virtual bool MightHaveChildrenSynthProviderInstance(
      const StructuredData::ObjectSP &implementor) {
    return true;
  }

  virtual lldb::ValueObjectSP
  GetSyntheticValue(const StructuredData::ObjectSP &implementor) {
    return nullptr;
  }

  virtual ConstString
  GetSyntheticTypeName(const StructuredData::ObjectSP &implementor) {
    return ConstString();
  }

  virtual bool
  RunScriptBasedCommand(const char *impl_function, llvm::StringRef args,
                        ScriptedCommandSynchronicity synchronicity,
                        lldb_private::CommandReturnObject &cmd_retobj,
                        Status &error,
                        const lldb_private::ExecutionContext &exe_ctx) {
    return false;
  }

  virtual bool RunScriptBasedCommand(
      StructuredData::GenericSP impl_obj_sp, llvm::StringRef args,
      ScriptedCommandSynchronicity synchronicity,
      lldb_private::CommandReturnObject &cmd_retobj, Status &error,
      const lldb_private::ExecutionContext &exe_ctx) {
    return false;
  }

  virtual bool RunScriptBasedParsedCommand(
      StructuredData::GenericSP impl_obj_sp, Args& args,
      ScriptedCommandSynchronicity synchronicity,
      lldb_private::CommandReturnObject &cmd_retobj, Status &error,
      const lldb_private::ExecutionContext &exe_ctx) {
    return false;
  }

  virtual std::optional<std::string>
  GetRepeatCommandForScriptedCommand(StructuredData::GenericSP impl_obj_sp,
                                     Args &args) {
    return std::nullopt;
  }

  virtual StructuredData::DictionarySP
  HandleArgumentCompletionForScriptedCommand(
      StructuredData::GenericSP impl_obj_sp, std::vector<llvm::StringRef> &args,
      size_t args_pos, size_t char_in_arg) {
    return {};
  }

  virtual StructuredData::DictionarySP
  HandleOptionArgumentCompletionForScriptedCommand(
      StructuredData::GenericSP impl_obj_sp, llvm::StringRef &long_name,
      size_t char_in_arg) {
    return {};
  }

  virtual bool RunScriptFormatKeyword(const char *impl_function,
                                      Process *process, std::string &output,
                                      Status &error) {
    error = Status::FromErrorString("unimplemented");
    return false;
  }

  virtual bool RunScriptFormatKeyword(const char *impl_function, Thread *thread,
                                      std::string &output, Status &error) {
    error = Status::FromErrorString("unimplemented");
    return false;
  }

  virtual bool RunScriptFormatKeyword(const char *impl_function, Target *target,
                                      std::string &output, Status &error) {
    error = Status::FromErrorString("unimplemented");
    return false;
  }

  virtual bool RunScriptFormatKeyword(const char *impl_function,
                                      StackFrame *frame, std::string &output,
                                      Status &error) {
    error = Status::FromErrorString("unimplemented");
    return false;
  }

  virtual bool RunScriptFormatKeyword(const char *impl_function,
                                      ValueObject *value, std::string &output,
                                      Status &error) {
    error = Status::FromErrorString("unimplemented");
    return false;
  }

  virtual bool GetDocumentationForItem(const char *item, std::string &dest) {
    dest.clear();
    return false;
  }

  virtual bool
  GetShortHelpForCommandObject(StructuredData::GenericSP cmd_obj_sp,
                               std::string &dest) {
    dest.clear();
    return false;
  }

  virtual StructuredData::ObjectSP
  GetOptionsForCommandObject(StructuredData::GenericSP cmd_obj_sp) {
    return {};
  }

  virtual StructuredData::ObjectSP
  GetArgumentsForCommandObject(StructuredData::GenericSP cmd_obj_sp) {
    return {};
  }

  virtual bool SetOptionValueForCommandObject(
      StructuredData::GenericSP cmd_obj_sp, ExecutionContext *exe_ctx,
      llvm::StringRef long_option, llvm::StringRef value) {
    return false;
  }

  virtual void
  OptionParsingStartedForCommandObject(StructuredData::GenericSP cmd_obj_sp) {}

  virtual uint32_t
  GetFlagsForCommandObject(StructuredData::GenericSP cmd_obj_sp) {
    return 0;
  }

  virtual bool GetLongHelpForCommandObject(StructuredData::GenericSP cmd_obj_sp,
                                           std::string &dest) {
    dest.clear();
    return false;
  }

  virtual bool CheckObjectExists(const char *name) { return false; }

  virtual bool
  LoadScriptingModule(const char *filename, const LoadScriptOptions &options,
                      lldb_private::Status &error,
                      StructuredData::ObjectSP *module_sp = nullptr,
                      FileSpec extra_search_dir = {},
                      lldb::TargetSP loaded_into_target_sp = {});

  virtual bool IsReservedWord(const char *word) { return false; }

  virtual std::unique_ptr<ScriptInterpreterLocker> AcquireInterpreterLock();

  const char *GetScriptInterpreterPtyName();

  virtual llvm::Expected<unsigned>
  GetMaxPositionalArgumentsForCallable(const llvm::StringRef &callable_name) {
    return llvm::createStringError(
    llvm::inconvertibleErrorCode(), "Unimplemented function");
  }

  static std::string LanguageToString(lldb::ScriptLanguage language);

  static lldb::ScriptLanguage StringToLanguage(const llvm::StringRef &string);

  lldb::ScriptLanguage GetLanguage() { return m_script_lang; }

  virtual lldb::ScriptedProcessInterfaceUP CreateScriptedProcessInterface() {
    return {};
  }

  virtual lldb::ScriptedThreadInterfaceSP CreateScriptedThreadInterface() {
    return {};
  }

  virtual lldb::ScriptedThreadPlanInterfaceSP
  CreateScriptedThreadPlanInterface() {
    return {};
  }

  virtual lldb::OperatingSystemInterfaceSP CreateOperatingSystemInterface() {
    return {};
  }

  virtual lldb::ScriptedPlatformInterfaceUP GetScriptedPlatformInterface() {
    return {};
  }

  virtual lldb::ScriptedStopHookInterfaceSP CreateScriptedStopHookInterface() {
    return {};
  }

  virtual StructuredData::ObjectSP
  CreateStructuredDataFromScriptObject(ScriptObject obj) {
    return {};
  }

  lldb::DataExtractorSP
  GetDataExtractorFromSBData(const lldb::SBData &data) const;

  Status GetStatusFromSBError(const lldb::SBError &error) const;

  Event *GetOpaqueTypeFromSBEvent(const lldb::SBEvent &event) const;

  lldb::StreamSP GetOpaqueTypeFromSBStream(const lldb::SBStream &stream) const;

  lldb::BreakpointSP
  GetOpaqueTypeFromSBBreakpoint(const lldb::SBBreakpoint &breakpoint) const;

  lldb::ProcessAttachInfoSP
  GetOpaqueTypeFromSBAttachInfo(const lldb::SBAttachInfo &attach_info) const;

  lldb::ProcessLaunchInfoSP
  GetOpaqueTypeFromSBLaunchInfo(const lldb::SBLaunchInfo &launch_info) const;

  std::optional<MemoryRegionInfo> GetOpaqueTypeFromSBMemoryRegionInfo(
      const lldb::SBMemoryRegionInfo &mem_region) const;

  lldb::ExecutionContextRefSP GetOpaqueTypeFromSBExecutionContext(
      const lldb::SBExecutionContext &exe_ctx) const;

protected:
  Debugger &m_debugger;
  lldb::ScriptLanguage m_script_lang;
};

} // namespace lldb_private

#endif // LLDB_INTERPRETER_SCRIPTINTERPRETER_H
