//===-- ScriptInterpreterPython.h -------------------------------*- C++ -*-===//
//
//                     The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//

#ifndef LLDB_PLUGINS_SCRIPTINTERPRETER_PYTHON_SCRIPTINTERPRETERPYTHON_H
#define LLDB_PLUGINS_SCRIPTINTERPRETER_PYTHON_SCRIPTINTERPRETERPYTHON_H

#ifdef LLDB_DISABLE_PYTHON

// Python is disabled in this build

#else

// C Includes
// C++ Includes
#include <memory>
#include <string>
#include <vector>

// Other libraries and framework includes
// Project includes
#include "PythonDataObjects.h"
#include "lldb/Breakpoint/BreakpointOptions.h"
#include "lldb/Core/IOHandler.h"
#include "lldb/Host/Terminal.h"
#include "lldb/Interpreter/ScriptInterpreter.h"
#include "lldb/lldb-private.h"

class IOHandlerPythonInterpreter;

namespace lldb_private {

class ScriptInterpreterPython : public ScriptInterpreter,
                                public IOHandlerDelegateMultiline {
public:
  class CommandDataPython : public BreakpointOptions::CommandData {
  public:
    CommandDataPython() : BreakpointOptions::CommandData() {
      interpreter = lldb::eScriptLanguagePython;
    }
  };

#if PY_MAJOR_VERSION >= 3
  typedef PyObject *(*SWIGInitCallback)(void);
#else
  typedef void (*SWIGInitCallback)(void);
#endif

  typedef bool (*SWIGBreakpointCallbackFunction)(
      const char *python_function_name, const char *session_dictionary_name,
      const lldb::StackFrameSP &frame_sp,
      const lldb::BreakpointLocationSP &bp_loc_sp);

  typedef bool (*SWIGWatchpointCallbackFunction)(
      const char *python_function_name, const char *session_dictionary_name,
      const lldb::StackFrameSP &frame_sp, const lldb::WatchpointSP &wp_sp);

  typedef bool (*SWIGPythonTypeScriptCallbackFunction)(
      const char *python_function_name, void *session_dictionary,
      const lldb::ValueObjectSP &valobj_sp, void **pyfunct_wrapper,
      const lldb::TypeSummaryOptionsSP &options, std::string &retval);

  typedef void *(*SWIGPythonCreateSyntheticProvider)(
      const char *python_class_name, const char *session_dictionary_name,
      const lldb::ValueObjectSP &valobj_sp);

  typedef void *(*SWIGPythonCreateCommandObject)(
      const char *python_class_name, const char *session_dictionary_name,
      const lldb::DebuggerSP debugger_sp);

  typedef void *(*SWIGPythonCreateScriptedThreadPlan)(
      const char *python_class_name, const char *session_dictionary_name,
      const lldb::ThreadPlanSP &thread_plan_sp);

  typedef bool (*SWIGPythonCallThreadPlan)(void *implementor,
                                           const char *method_name,
                                           Event *event_sp, bool &got_error);

  typedef void *(*SWIGPythonCreateOSPlugin)(const char *python_class_name,
                                            const char *session_dictionary_name,
                                            const lldb::ProcessSP &process_sp);

  typedef size_t (*SWIGPythonCalculateNumChildren)(void *implementor,
                                                   uint32_t max);

  typedef void *(*SWIGPythonGetChildAtIndex)(void *implementor, uint32_t idx);

  typedef int (*SWIGPythonGetIndexOfChildWithName)(void *implementor,
                                                   const char *child_name);

  typedef void *(*SWIGPythonCastPyObjectToSBValue)(void *data);

  typedef lldb::ValueObjectSP (*SWIGPythonGetValueObjectSPFromSBValue)(
      void *data);

  typedef bool (*SWIGPythonUpdateSynthProviderInstance)(void *data);

  typedef bool (*SWIGPythonMightHaveChildrenSynthProviderInstance)(void *data);

  typedef void *(*SWIGPythonGetValueSynthProviderInstance)(void *implementor);

  typedef bool (*SWIGPythonCallCommand)(
      const char *python_function_name, const char *session_dictionary_name,
      lldb::DebuggerSP &debugger, const char *args,
      lldb_private::CommandReturnObject &cmd_retobj,
      lldb::ExecutionContextRefSP exe_ctx_ref_sp);

  typedef bool (*SWIGPythonCallCommandObject)(
      void *implementor, lldb::DebuggerSP &debugger, const char *args,
      lldb_private::CommandReturnObject &cmd_retobj,
      lldb::ExecutionContextRefSP exe_ctx_ref_sp);

  typedef bool (*SWIGPythonCallModuleInit)(const char *python_module_name,
                                           const char *session_dictionary_name,
                                           lldb::DebuggerSP &debugger);

  typedef bool (*SWIGPythonScriptKeyword_Process)(
      const char *python_function_name, const char *session_dictionary_name,
      lldb::ProcessSP &process, std::string &output);

  typedef bool (*SWIGPythonScriptKeyword_Thread)(
      const char *python_function_name, const char *session_dictionary_name,
      lldb::ThreadSP &thread, std::string &output);

  typedef bool (*SWIGPythonScriptKeyword_Target)(
      const char *python_function_name, const char *session_dictionary_name,
      lldb::TargetSP &target, std::string &output);

  typedef bool (*SWIGPythonScriptKeyword_Frame)(
      const char *python_function_name, const char *session_dictionary_name,
      lldb::StackFrameSP &frame, std::string &output);

  typedef bool (*SWIGPythonScriptKeyword_Value)(
      const char *python_function_name, const char *session_dictionary_name,
      lldb::ValueObjectSP &value, std::string &output);

  typedef void *(*SWIGPython_GetDynamicSetting)(
      void *module, const char *setting, const lldb::TargetSP &target_sp);

  friend class ::IOHandlerPythonInterpreter;

  ScriptInterpreterPython(CommandInterpreter &interpreter);

  ~ScriptInterpreterPython() override;

  bool Interrupt() override;

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

  void ExecuteInterpreterLoop() override;

  bool ExecuteOneLineWithReturn(
      llvm::StringRef in_string,
      ScriptInterpreter::ScriptReturnType return_type, void *ret_value,
      const ExecuteScriptOptions &options = ExecuteScriptOptions()) override;

  lldb_private::Status ExecuteMultipleLines(
      const char *in_string,
      const ExecuteScriptOptions &options = ExecuteScriptOptions()) override;

  Status
  ExportFunctionDefinitionToInterpreter(StringList &function_def) override;

  bool GenerateTypeScriptFunction(StringList &input, std::string &output,
                                  const void *name_token = nullptr) override;

  bool GenerateTypeSynthClass(StringList &input, std::string &output,
                              const void *name_token = nullptr) override;

  bool GenerateTypeSynthClass(const char *oneliner, std::string &output,
                              const void *name_token = nullptr) override;

  // use this if the function code is just a one-liner script
  bool GenerateTypeScriptFunction(const char *oneliner, std::string &output,
                                  const void *name_token = nullptr) override;

  bool GenerateScriptAliasFunction(StringList &input,
                                   std::string &output) override;

  StructuredData::ObjectSP
  CreateSyntheticScriptedProvider(const char *class_name,
                                  lldb::ValueObjectSP valobj) override;

  StructuredData::GenericSP
  CreateScriptCommandObject(const char *class_name) override;

  StructuredData::ObjectSP
  CreateScriptedThreadPlan(const char *class_name,
                           lldb::ThreadPlanSP thread_plan) override;

  bool ScriptedThreadPlanExplainsStop(StructuredData::ObjectSP implementor_sp,
                                      Event *event,
                                      bool &script_error) override;

  bool ScriptedThreadPlanShouldStop(StructuredData::ObjectSP implementor_sp,
                                    Event *event, bool &script_error) override;

  bool ScriptedThreadPlanIsStale(StructuredData::ObjectSP implementor_sp,
                                 bool &script_error) override;

  lldb::StateType
  ScriptedThreadPlanGetRunState(StructuredData::ObjectSP implementor_sp,
                                bool &script_error) override;

  StructuredData::GenericSP
  OSPlugin_CreatePluginObject(const char *class_name,
                              lldb::ProcessSP process_sp) override;

  StructuredData::DictionarySP
  OSPlugin_RegisterInfo(StructuredData::ObjectSP os_plugin_object_sp) override;

  StructuredData::ArraySP
  OSPlugin_ThreadsInfo(StructuredData::ObjectSP os_plugin_object_sp) override;

  StructuredData::StringSP
  OSPlugin_RegisterContextData(StructuredData::ObjectSP os_plugin_object_sp,
                               lldb::tid_t thread_id) override;

  StructuredData::DictionarySP
  OSPlugin_CreateThread(StructuredData::ObjectSP os_plugin_object_sp,
                        lldb::tid_t tid, lldb::addr_t context) override;

  StructuredData::ObjectSP
  LoadPluginModule(const FileSpec &file_spec,
                   lldb_private::Status &error) override;

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

  size_t CalculateNumChildren(const StructuredData::ObjectSP &implementor,
                              uint32_t max) override;

  lldb::ValueObjectSP
  GetChildAtIndex(const StructuredData::ObjectSP &implementor,
                  uint32_t idx) override;

  int GetIndexOfChildWithName(const StructuredData::ObjectSP &implementor,
                              const char *child_name) override;

  bool UpdateSynthProviderInstance(
      const StructuredData::ObjectSP &implementor) override;

  bool MightHaveChildrenSynthProviderInstance(
      const StructuredData::ObjectSP &implementor) override;

  lldb::ValueObjectSP
  GetSyntheticValue(const StructuredData::ObjectSP &implementor) override;

  ConstString
  GetSyntheticTypeName(const StructuredData::ObjectSP &implementor) override;

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

  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) override;

  Status GenerateFunction(const char *signature,
                          const StringList &input) override;

  Status GenerateBreakpointCommandCallbackData(StringList &input,
                                               std::string &output) override;

  bool GenerateWatchpointCommandCallbackData(StringList &input,
                                             std::string &output) override;

  //    static size_t
  //    GenerateBreakpointOptionsCommandCallback (void *baton,
  //                                              InputReader &reader,
  //                                              lldb::InputReaderAction
  //                                              notification,
  //                                              const char *bytes,
  //                                              size_t bytes_len);
  //
  //    static size_t
  //    GenerateWatchpointOptionsCommandCallback (void *baton,
  //                                              InputReader &reader,
  //                                              lldb::InputReaderAction
  //                                              notification,
  //                                              const char *bytes,
  //                                              size_t bytes_len);

  static bool BreakpointCallbackFunction(void *baton,
                                         StoppointCallbackContext *context,
                                         lldb::user_id_t break_id,
                                         lldb::user_id_t break_loc_id);

  static bool WatchpointCallbackFunction(void *baton,
                                         StoppointCallbackContext *context,
                                         lldb::user_id_t watch_id);

  bool GetScriptedSummary(const char *function_name, lldb::ValueObjectSP valobj,
                          StructuredData::ObjectSP &callee_wrapper_sp,
                          const TypeSummaryOptions &options,
                          std::string &retval) override;

  void Clear() override;

  bool GetDocumentationForItem(const char *item, std::string &dest) override;

  bool GetShortHelpForCommandObject(StructuredData::GenericSP cmd_obj_sp,
                                    std::string &dest) override;

  uint32_t
  GetFlagsForCommandObject(StructuredData::GenericSP cmd_obj_sp) override;

  bool GetLongHelpForCommandObject(StructuredData::GenericSP cmd_obj_sp,
                                   std::string &dest) override;

  bool CheckObjectExists(const char *name) override {
    if (!name || !name[0])
      return false;
    std::string temp;
    return GetDocumentationForItem(name, temp);
  }

  bool RunScriptFormatKeyword(const char *impl_function, Process *process,
                              std::string &output, Status &error) override;

  bool RunScriptFormatKeyword(const char *impl_function, Thread *thread,
                              std::string &output, Status &error) override;

  bool RunScriptFormatKeyword(const char *impl_function, Target *target,
                              std::string &output, Status &error) override;

  bool RunScriptFormatKeyword(const char *impl_function, StackFrame *frame,
                              std::string &output, Status &error) override;

  bool RunScriptFormatKeyword(const char *impl_function, ValueObject *value,
                              std::string &output, Status &error) override;

  bool
  LoadScriptingModule(const char *filename, bool can_reload, bool init_session,
                      lldb_private::Status &error,
                      StructuredData::ObjectSP *module_sp = nullptr) override;

  bool IsReservedWord(const char *word) override;

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

  void CollectDataForBreakpointCommandCallback(
      std::vector<BreakpointOptions *> &bp_options_vec,
      CommandReturnObject &result) override;

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

  /// Set the callback body text into the callback for the breakpoint.
  Status SetBreakpointCommandCallback(BreakpointOptions *bp_options,
                                      const char *callback_body) override;

  void SetBreakpointCommandCallbackFunction(BreakpointOptions *bp_options,
                                            const char *function_name) override;

  /// This one is for deserialization:
  Status SetBreakpointCommandCallback(
      BreakpointOptions *bp_options,
      std::unique_ptr<BreakpointOptions::CommandData> &data_up) override;

  /// Set a one-liner as the callback for the watchpoint.
  void SetWatchpointCommandCallback(WatchpointOptions *wp_options,
                                    const char *oneliner) override;

  StringList ReadCommandInputFromUser(FILE *in_file);

  void ResetOutputFileHandle(FILE *new_fh) override;

  static void InitializePrivate();

  static void InitializeInterpreter(
      SWIGInitCallback python_swig_init_callback,
      SWIGBreakpointCallbackFunction swig_breakpoint_callback,
      SWIGWatchpointCallbackFunction swig_watchpoint_callback,
      SWIGPythonTypeScriptCallbackFunction swig_typescript_callback,
      SWIGPythonCreateSyntheticProvider swig_synthetic_script,
      SWIGPythonCreateCommandObject swig_create_cmd,
      SWIGPythonCalculateNumChildren swig_calc_children,
      SWIGPythonGetChildAtIndex swig_get_child_index,
      SWIGPythonGetIndexOfChildWithName swig_get_index_child,
      SWIGPythonCastPyObjectToSBValue swig_cast_to_sbvalue,
      SWIGPythonGetValueObjectSPFromSBValue swig_get_valobj_sp_from_sbvalue,
      SWIGPythonUpdateSynthProviderInstance swig_update_provider,
      SWIGPythonMightHaveChildrenSynthProviderInstance
          swig_mighthavechildren_provider,
      SWIGPythonGetValueSynthProviderInstance swig_getvalue_provider,
      SWIGPythonCallCommand swig_call_command,
      SWIGPythonCallCommandObject swig_call_command_object,
      SWIGPythonCallModuleInit swig_call_module_init,
      SWIGPythonCreateOSPlugin swig_create_os_plugin,
      SWIGPythonScriptKeyword_Process swig_run_script_keyword_process,
      SWIGPythonScriptKeyword_Thread swig_run_script_keyword_thread,
      SWIGPythonScriptKeyword_Target swig_run_script_keyword_target,
      SWIGPythonScriptKeyword_Frame swig_run_script_keyword_frame,
      SWIGPythonScriptKeyword_Value swig_run_script_keyword_value,
      SWIGPython_GetDynamicSetting swig_plugin_get,
      SWIGPythonCreateScriptedThreadPlan swig_thread_plan_script,
      SWIGPythonCallThreadPlan swig_call_thread_plan);

  const char *GetDictionaryName() { return m_dictionary_name.c_str(); }

  PyThreadState *GetThreadState() { return m_command_thread_state; }

  void SetThreadState(PyThreadState *s) {
    if (s)
      m_command_thread_state = s;
  }

  //----------------------------------------------------------------------
  // IOHandlerDelegate
  //----------------------------------------------------------------------
  void IOHandlerActivated(IOHandler &io_handler) override;

  void IOHandlerInputComplete(IOHandler &io_handler,
                              std::string &data) override;

  //------------------------------------------------------------------
  // Static Functions
  //------------------------------------------------------------------
  static void Initialize();

  static void Terminate();

  static lldb::ScriptInterpreterSP
  CreateInstance(CommandInterpreter &interpreter);

  static lldb_private::ConstString GetPluginNameStatic();

  static const char *GetPluginDescriptionStatic();

  static FileSpec GetPythonDir();

  //------------------------------------------------------------------
  // PluginInterface protocol
  //------------------------------------------------------------------
  lldb_private::ConstString GetPluginName() override;

  uint32_t GetPluginVersion() override;

  class Locker : public ScriptInterpreterLocker {
  public:
    enum OnEntry {
      AcquireLock = 0x0001,
      InitSession = 0x0002,
      InitGlobals = 0x0004,
      NoSTDIN = 0x0008
    };

    enum OnLeave {
      FreeLock = 0x0001,
      FreeAcquiredLock = 0x0002, // do not free the lock if we already held it
                                 // when calling constructor
      TearDownSession = 0x0004
    };

    Locker(ScriptInterpreterPython *py_interpreter = nullptr,
           uint16_t on_entry = AcquireLock | InitSession,
           uint16_t on_leave = FreeLock | TearDownSession, FILE *in = nullptr,
           FILE *out = nullptr, FILE *err = nullptr);

    ~Locker() override;

  private:
    bool DoAcquireLock();

    bool DoInitSession(uint16_t on_entry_flags, FILE *in, FILE *out, FILE *err);

    bool DoFreeLock();

    bool DoTearDownSession();

    static void ReleasePythonLock();

    bool m_teardown_session;
    ScriptInterpreterPython *m_python_interpreter;
    //    	FILE*                    m_tmp_fh;
    PyGILState_STATE m_GILState;
  };

protected:
  class SynchronicityHandler {
  private:
    lldb::DebuggerSP m_debugger_sp;
    ScriptedCommandSynchronicity m_synch_wanted;
    bool m_old_asynch;

  public:
    SynchronicityHandler(lldb::DebuggerSP, ScriptedCommandSynchronicity);

    ~SynchronicityHandler();
  };

  enum class AddLocation { Beginning, End };

  static void AddToSysPath(AddLocation location, std::string path);

  static void ComputePythonDirForApple(llvm::SmallVectorImpl<char> &path);
  static void ComputePythonDirForPosix(llvm::SmallVectorImpl<char> &path);
  static void ComputePythonDirForWindows(llvm::SmallVectorImpl<char> &path);

  bool EnterSession(uint16_t on_entry_flags, FILE *in, FILE *out, FILE *err);

  void LeaveSession();

  void SaveTerminalState(int fd);

  void RestoreTerminalState();

  uint32_t IsExecutingPython() const { return m_lock_count > 0; }

  uint32_t IncrementLockCount() { return ++m_lock_count; }

  uint32_t DecrementLockCount() {
    if (m_lock_count > 0)
      --m_lock_count;
    return m_lock_count;
  }

  enum ActiveIOHandler {
    eIOHandlerNone,
    eIOHandlerBreakpoint,
    eIOHandlerWatchpoint
  };

  PythonObject &GetMainModule();

  PythonDictionary &GetSessionDictionary();

  PythonDictionary &GetSysModuleDictionary();

  bool GetEmbeddedInterpreterModuleObjects();

  bool SetStdHandle(File &file, const char *py_name, PythonFile &save_file,
                    const char *mode);

  PythonFile m_saved_stdin;
  PythonFile m_saved_stdout;
  PythonFile m_saved_stderr;
  PythonObject m_main_module;
  PythonObject m_lldb_module;
  PythonDictionary m_session_dict;
  PythonDictionary m_sys_module_dict;
  PythonObject m_run_one_line_function;
  PythonObject m_run_one_line_str_global;
  std::string m_dictionary_name;
  TerminalState m_terminal_state;
  ActiveIOHandler m_active_io_handler;
  bool m_session_is_active;
  bool m_pty_slave_is_open;
  bool m_valid_session;
  uint32_t m_lock_count;
  PyThreadState *m_command_thread_state;
};

} // namespace lldb_private

#endif // LLDB_DISABLE_PYTHON

#endif // LLDB_PLUGINS_SCRIPTINTERPRETER_PYTHON_SCRIPTINTERPRETERPYTHON_H