//===-- 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 "lldb/lldb-private.h"
#include "PythonDataObjects.h"
#include "lldb/Core/IOHandler.h"
#include "lldb/Interpreter/ScriptInterpreter.h"
#include "lldb/Host/Terminal.h"

class IOHandlerPythonInterpreter;

namespace lldb_private {
    
class ScriptInterpreterPython :
    public ScriptInterpreter,
    public IOHandlerDelegateMultiline
{
public:
#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 (const char *command,
                    CommandReturnObject *result,
                    const ExecuteScriptOptions &options = ExecuteScriptOptions()) override;

    void
    ExecuteInterpreterLoop () override;

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

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

    Error
    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;

    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::Error &error) override;

    StructuredData::DictionarySP GetDynamicSettings(StructuredData::ObjectSP plugin_module_sp, Target *target, const char *setting_name,
                                                    lldb_private::Error &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,
                          const char* args,
                          ScriptedCommandSynchronicity synchronicity,
                          lldb_private::CommandReturnObject& cmd_retobj,
                          Error& error,
                          const lldb_private::ExecutionContext& exe_ctx) override;
    
    bool
    RunScriptBasedCommand (StructuredData::GenericSP impl_obj_sp,
                           const char* args,
                           ScriptedCommandSynchronicity synchronicity,
                           lldb_private::CommandReturnObject& cmd_retobj,
                           Error& error,
                           const lldb_private::ExecutionContext& exe_ctx) override;
    
    Error
    GenerateFunction(const char *signature, const StringList &input) override;
    
    Error
    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,
                            Error& error) override;

    bool
    RunScriptFormatKeyword (const char* impl_function,
                            Thread* thread,
                            std::string& output,
                            Error& error) override;
    
    bool
    RunScriptFormatKeyword (const char* impl_function,
                            Target* target,
                            std::string& output,
                            Error& error) override;
    
    bool
    RunScriptFormatKeyword (const char* impl_function,
                            StackFrame* frame,
                            std::string& output,
                            Error& error) override;
    
    bool
    RunScriptFormatKeyword (const char* impl_function,
                            ValueObject* value,
                            std::string& output,
                            Error& error) override;

    bool LoadScriptingModule(const char *filename, bool can_reload, bool init_session, lldb_private::Error &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.
    Error
    SetBreakpointCommandCallback (BreakpointOptions *bp_options,
                                  const char *callback_body) override;

    void 
    SetBreakpointCommandCallbackFunction (BreakpointOptions *bp_options,
                                          const char *function_name) 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();
    
    //------------------------------------------------------------------
    // 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);

    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
