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

#ifndef liblldb_IOHandler_h_
#define liblldb_IOHandler_h_

#include <string.h>

#include <stack>

#include "lldb/lldb-public.h"
#include "lldb/lldb-enumerations.h"
#include "lldb/Core/ConstString.h"
#include "lldb/Core/Error.h"
#include "lldb/Core/Flags.h"
#include "lldb/Core/StringList.h"
#include "lldb/Core/ValueObjectList.h"
#include "lldb/Host/Mutex.h"

namespace curses
{
    class Application;
    typedef std::unique_ptr<Application> ApplicationAP;
}

namespace lldb_private {

    class IOHandler
    {
    public:
        IOHandler (Debugger &debugger);

        IOHandler (Debugger &debugger,
                   const lldb::StreamFileSP &input_sp,
                   const lldb::StreamFileSP &output_sp,
                   const lldb::StreamFileSP &error_sp,
                   uint32_t flags);

        virtual
        ~IOHandler ();

        // Each IOHandler gets to run until it is done. It should read data
        // from the "in" and place output into "out" and "err and return
        // when done.
        virtual void
        Run () = 0;

        // Hide any characters that have been displayed so far so async
        // output can be displayed. Refresh() will be called after the
        // output has been displayed.
        virtual void
        Hide () = 0;
        
        // Called when the async output has been received in order to update
        // the input reader (refresh the prompt and redisplay any current
        // line(s) that are being edited
        virtual void
        Refresh () = 0;

        // Called when an input reader should relinquish its control so another
        // can be pushed onto the IO handler stack, or so the current IO
        // handler can pop itself off the stack

        virtual void
        Cancel () = 0;

        // Called when CTRL+C is pressed which usually causes
        // Debugger::DispatchInputInterrupt to be called.
        
        virtual bool
        Interrupt () = 0;
        
        virtual void
        GotEOF() = 0;
        
        virtual bool
        IsActive ()
        {
            return m_active && !m_done;
        }

        virtual void
        SetIsDone (bool b)
        {
            m_done = b;
        }

        virtual bool
        GetIsDone ()
        {
            return m_done;
        }

        virtual void
        Activate ()
        {
            m_active = true;
        }
        
        virtual void
        Deactivate ()
        {
            m_active = false;
        }

        virtual const char *
        GetPrompt ()
        {
            // Prompt support isn't mandatory
            return NULL;
        }
        
        virtual bool
        SetPrompt (const char *prompt)
        {
            // Prompt support isn't mandatory
            return false;
        }
        
        virtual ConstString
        GetControlSequence (char ch)
        {
            return ConstString();
        }
        
        int
        GetInputFD();
        
        int
        GetOutputFD();
        
        int
        GetErrorFD();

        FILE *
        GetInputFILE();
        
        FILE *
        GetOutputFILE();
        
        FILE *
        GetErrorFILE();

        lldb::StreamFileSP &
        GetInputStreamFile();
        
        lldb::StreamFileSP &
        GetOutputStreamFile();
        
        lldb::StreamFileSP &
        GetErrorStreamFile();

        Debugger &
        GetDebugger()
        {
            return m_debugger;
        }

        void *
        GetUserData ()
        {
            return m_user_data;
        }

        void
        SetUserData (void *user_data)
        {
            m_user_data = user_data;
        }

        Flags &
        GetFlags ()
        {
            return m_flags;
        }

        const Flags &
        GetFlags () const
        {
            return m_flags;
        }

        //------------------------------------------------------------------
        /// Check if the input is being supplied interactively by a user
        ///
        /// This will return true if the input stream is a terminal (tty or
        /// pty) and can cause IO handlers to do different things (like
        /// for a confirmation when deleting all breakpoints).
        //------------------------------------------------------------------
        bool
        GetIsInteractive ();

        //------------------------------------------------------------------
        /// Check if the input is coming from a real terminal.
        ///
        /// A real terminal has a valid size with a certain number of rows
        /// and columns. If this function returns true, then terminal escape
        /// sequences are expected to work (cursor movement escape sequences,
        /// clearing lines, etc).
        //------------------------------------------------------------------
        bool
        GetIsRealTerminal ();

    protected:
        Debugger &m_debugger;
        lldb::StreamFileSP m_input_sp;
        lldb::StreamFileSP m_output_sp;
        lldb::StreamFileSP m_error_sp;
        Flags m_flags;
        void *m_user_data;
        bool m_done;
        bool m_active;

    private:
        DISALLOW_COPY_AND_ASSIGN (IOHandler);
    };

    
    //------------------------------------------------------------------
    /// A delegate class for use with IOHandler subclasses.
    ///
    /// The IOHandler delegate is designed to be mixed into classes so
    /// they can use an IOHandler subclass to fetch input and notify the
    /// object that inherits from this delegate class when a token is
    /// received.
    //------------------------------------------------------------------
    class IOHandlerDelegate
    {
    public:
        enum class Completion {
            None,
            LLDBCommand,
            Expression
        };
        
        IOHandlerDelegate (Completion completion = Completion::None) :
            m_completion(completion),
            m_io_handler_done (false)
        {
        }
        
        virtual
        ~IOHandlerDelegate()
        {
        }
        
        virtual void
        IOHandlerActivated (IOHandler &io_handler)
        {
        }
        
        virtual int
        IOHandlerComplete (IOHandler &io_handler,
                           const char *current_line,
                           const char *cursor,
                           const char *last_char,
                           int skip_first_n_matches,
                           int max_matches,
                           StringList &matches);
        
        //------------------------------------------------------------------
        /// Called when a line or lines have been retrieved.
        ///
        /// This function can handle the current line and possibly call
        /// IOHandler::SetIsDone(true) when the IO handler is done like when
        /// "quit" is entered as a command, of when an empty line is
        /// received. It is up to the delegate to determine when a line
        /// should cause a IOHandler to exit.
        //------------------------------------------------------------------
        virtual void
        IOHandlerInputComplete (IOHandler &io_handler, std::string &data) = 0;
        
        //------------------------------------------------------------------
        /// Called when a line in \a lines has been updated when doing
        /// multi-line input.
        ///
        /// @return
        ///     Return an enumeration to indicate the status of the current
        ///     line:
        ///         Success - The line is good and should be added to the
        ///                   multiple lines
        ///         Error - There is an error with the current line and it
        ///                 need to be re-edited before it is acceptable
        ///         Done - The lines collection is complete and ready to be
        ///                returned.
        //------------------------------------------------------------------
        virtual LineStatus
        IOHandlerLinesUpdated (IOHandler &io_handler,
                               StringList &lines,
                               uint32_t line_idx,
                               Error &error)
        {
            return LineStatus::Done; // Stop getting lines on the first line that is updated
            // subclasses should do something more intelligent here.
            // This function will not be called on IOHandler objects
            // that are getting single lines.
        }
        
        
        virtual ConstString
        IOHandlerGetControlSequence (char ch)
        {
            return ConstString();
        }
        
        //------------------------------------------------------------------
        // Intercept the IOHandler::Interrupt() calls and do something.
        //
        // Return true if the interrupt was handled, false if the IOHandler
        // should continue to try handle the interrupt itself.
        //------------------------------------------------------------------
        virtual bool
        IOHandlerInterrupt (IOHandler &io_handler)
        {
            return false;
        }
    protected:
        Completion m_completion; // Support for common builtin completions
        bool m_io_handler_done;
    };

    //----------------------------------------------------------------------
    // IOHandlerDelegateMultiline
    //
    // A IOHandlerDelegate that handles terminating multi-line input when
    // the last line is equal to "end_line" which is specified in the
    // constructor.
    //----------------------------------------------------------------------
    class IOHandlerDelegateMultiline :
        public IOHandlerDelegate
    {
    public:
        IOHandlerDelegateMultiline (const char *end_line,
                                    Completion completion = Completion::None) :
            IOHandlerDelegate (completion),
            m_end_line((end_line && end_line[0]) ? end_line : "")
        {
        }
        
        virtual
        ~IOHandlerDelegateMultiline ()
        {
        }
        
        virtual ConstString
        IOHandlerGetControlSequence (char ch)
        {
            if (ch == 'd')
                return ConstString (m_end_line + "\n");
            return ConstString();
        }

        virtual LineStatus
        IOHandlerLinesUpdated (IOHandler &io_handler,
                               StringList &lines,
                               uint32_t line_idx,
                               Error &error)
        {
            if (line_idx == UINT32_MAX)
            {
                // Remove the last empty line from "lines" so it doesn't appear
                // in our final expression and return true to indicate we are done
                // getting lines
                lines.PopBack();
                return LineStatus::Done;
            }
            else if (line_idx + 1 == lines.GetSize())
            {
                // The last line was edited, if this line is empty, then we are done
                // getting our multiple lines.
                if (lines[line_idx] == m_end_line)
                {
                    return LineStatus::Done;
                }
            }
            return LineStatus::Success;
        }
    protected:
        const std::string m_end_line;
    };
    
    
    class IOHandlerEditline : public IOHandler
    {
    public:
        IOHandlerEditline (Debugger &debugger,
                           const char *editline_name, // Used for saving history files
                           const char *prompt,
                           bool multi_line,
                           uint32_t line_number_start, // If non-zero show line numbers starting at 'line_number_start'
                           IOHandlerDelegate &delegate);

        IOHandlerEditline (Debugger &debugger,
                           const lldb::StreamFileSP &input_sp,
                           const lldb::StreamFileSP &output_sp,
                           const lldb::StreamFileSP &error_sp,
                           uint32_t flags,
                           const char *editline_name, // Used for saving history files
                           const char *prompt,
                           bool multi_line,
                           uint32_t line_number_start, // If non-zero show line numbers starting at 'line_number_start'
                           IOHandlerDelegate &delegate);
        
        virtual
        ~IOHandlerEditline ();
        
        virtual void
        Run ();
        
        virtual void
        Hide ();

        virtual void
        Refresh ();

        virtual void
        Cancel ();

        virtual bool
        Interrupt ();
        
        virtual void
        GotEOF();
        
        virtual void
        Activate ()
        {
            IOHandler::Activate();
            m_delegate.IOHandlerActivated(*this);
        }

        virtual ConstString
        GetControlSequence (char ch)
        {
            return m_delegate.IOHandlerGetControlSequence (ch);
        }

        virtual const char *
        GetPrompt ();
        
        virtual bool
        SetPrompt (const char *prompt);

        bool
        GetLine (std::string &line, bool &interrupted);
        
        bool
        GetLines (StringList &lines, bool &interrupted);
        
        void
        SetBaseLineNumber (uint32_t line);
        
    private:
        static LineStatus
        LineCompletedCallback (Editline *editline,
                               StringList &lines,
                               uint32_t line_idx,
                               Error &error,
                               void *baton);

        static int AutoCompleteCallback (const char *current_line,
                                         const char *cursor,
                                         const char *last_char,
                                         int skip_first_n_matches,
                                         int max_matches,
                                         StringList &matches,
                                         void *baton);

    protected:
        std::unique_ptr<Editline> m_editline_ap;
        IOHandlerDelegate &m_delegate;
        std::string m_prompt;
        uint32_t m_base_line_number; // If non-zero, then show line numbers in prompt
        bool m_multi_line;        
    };
    
    class IOHandlerConfirm :
        public IOHandlerEditline,
        public IOHandlerDelegate
    {
    public:
        IOHandlerConfirm (Debugger &debugger,
                          const char *prompt,
                          bool default_response);
        
        virtual
        ~IOHandlerConfirm ();
                
        bool
        GetResponse () const
        {
            return m_user_response;
        }
        
        virtual int
        IOHandlerComplete (IOHandler &io_handler,
                           const char *current_line,
                           const char *cursor,
                           const char *last_char,
                           int skip_first_n_matches,
                           int max_matches,
                           StringList &matches);
        
        virtual void
        IOHandlerInputComplete (IOHandler &io_handler, std::string &data);

    protected:
        const bool m_default_response;
        bool m_user_response;
    };

    class IOHandlerCursesGUI :
        public IOHandler
    {
    public:
        IOHandlerCursesGUI (Debugger &debugger);
        
        virtual
        ~IOHandlerCursesGUI ();
        
        virtual void
        Run ();
        
        virtual void
        Hide ();
        
        virtual void
        Refresh ();

        virtual void
        Cancel ();

        virtual bool
        Interrupt ();
        
        virtual void
        GotEOF();
        
        virtual void
        Activate ();
        
        virtual void
        Deactivate ();

    protected:
        curses::ApplicationAP m_app_ap;
    };

    class IOHandlerCursesValueObjectList :
        public IOHandler
    {
    public:
        IOHandlerCursesValueObjectList (Debugger &debugger, ValueObjectList &valobj_list);
        
        virtual
        ~IOHandlerCursesValueObjectList ();
        
        virtual void
        Run ();
        
        virtual void
        Hide ();
        
        virtual void
        Refresh ();
        
        virtual bool
        HandleInterrupt ();
        
        virtual void
        GotEOF();
    protected:
        ValueObjectList m_valobj_list;
    };

    class IOHandlerStack
    {
    public:
        
        IOHandlerStack () :
            m_stack(),
            m_mutex(Mutex::eMutexTypeRecursive),
            m_top (NULL)
        {
        }
        
        ~IOHandlerStack ()
        {
        }
        
        size_t
        GetSize () const
        {
            Mutex::Locker locker (m_mutex);
            return m_stack.size();
        }
        
        void
        Push (const lldb::IOHandlerSP& sp)
        {
            if (sp)
            {
                Mutex::Locker locker (m_mutex);
                m_stack.push (sp);
                // Set m_top the non-locking IsTop() call
                m_top = sp.get();
            }
        }
        
        bool
        IsEmpty () const
        {
            Mutex::Locker locker (m_mutex);
            return m_stack.empty();
        }
        
        lldb::IOHandlerSP
        Top ()
        {
            lldb::IOHandlerSP sp;
            {
                Mutex::Locker locker (m_mutex);
                if (!m_stack.empty())
                    sp = m_stack.top();
            }
            return sp;
        }
        
        void
        Pop ()
        {
            Mutex::Locker locker (m_mutex);
            if (!m_stack.empty())
                m_stack.pop();
            // Set m_top the non-locking IsTop() call
            if (m_stack.empty())
                m_top = NULL;
            else
                m_top = m_stack.top().get();
        }

        Mutex &
        GetMutex()
        {
            return m_mutex;
        }
      
        bool
        IsTop (const lldb::IOHandlerSP &io_handler_sp) const
        {
            return m_top == io_handler_sp.get();
        }

        ConstString
        GetTopIOHandlerControlSequence (char ch)
        {
            if (m_top)
                return m_top->GetControlSequence(ch);
            return ConstString();
        }

    protected:
        
        std::stack<lldb::IOHandlerSP> m_stack;
        mutable Mutex m_mutex;
        IOHandler *m_top;
        
    private:
        
        DISALLOW_COPY_AND_ASSIGN (IOHandlerStack);
    };

} // namespace lldb_private

#endif // #ifndef liblldb_IOHandler_h_
