# -*- coding: utf-8 -*-
#  Copyright (C) 2009-2010, 2013, 2015, 2017 Rocky Bernstein
#
#  This program is free software: you can redistribute it and/or modify
#  it under the terms of the GNU General Public License as published by
#  the Free Software Foundation, either version 3 of the License, or
#  (at your option) any later version.
#
#  This program is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.
#
#  You should have received a copy of the GNU General Public License
#  along with this program.  If not, see <http://www.gnu.org/licenses/>.
import code, os, sys

# Our local modules
from trepan.processor.command import base_cmd as Mbase_cmd

from traitlets.config.loader import Config

class IPythonCommand(Mbase_cmd.DebuggerCommand):
    """**ipython** [**-d**]

Run IPython as a command subshell.

If *-d* is passed, you can access debugger state via local variable *debugger*.

To issue a debugger command use function *dbgr()*. For example:

  dbgr('info program')

See also:
---------

`python`, `bpython`
"""

    category      = 'support'
    min_args      = 0
    max_args      = 1
    name          = os.path.basename(__file__).split('.')[0]
    need_stack    = False
    short_help    = 'Run IPython as a command subshell'

    def dbgr(self, string):
        '''Invoke a debugger command from inside a IPython shell called
        inside the debugger.
        '''
        self.proc.cmd_queue.append(string)
        self.proc.process_command()
        return

    def run(self, args):
        # See if python's code module is around

        # Python does it's own history thing.
        # Make sure it doesn't damage ours.
        have_line_edit = self.debugger.intf[-1].input.line_edit
        if have_line_edit:
            try:
                self.proc.write_history_file()
            except IOError:
                pass
            pass

        cfg = Config()
        banner_tmpl='''IPython trepan2 shell%s

Use dbgr(*string*) to issue non-continuing debugger command: *string*'''

        debug = len(args) > 1 and args[1] == '-d'
        if debug:
            banner_tmpl += ("\nVariable 'debugger' contains a trepan "
                            "debugger object.")
            pass
        try:
            from IPython.terminal.embed import InteractiveShellEmbed
        except ImportError:
            from IPython.frontend.terminal.embed import InteractiveShellEmbed

        # Now create an instance of the embeddable shell. The first
        # argument is a string with options exactly as you would type them
        # if you were starting IPython at the system command line. Any
        # parameters you want to define for configuration can thus be
        # specified here.

        # Add common classes and methods our namespace here so that
        # inside the ipython shell users don't have run imports

        my_locals  = {}
        my_globals = None
        if self.proc.curframe:
            my_globals = self.proc.curframe.f_globals
            if self.proc.curframe.f_locals:
                my_locals = self.proc.curframe.f_locals
            pass
        pass

        # Give IPython and the user a way to get access to the debugger.
        if debug: my_locals['debugger'] = self.debugger
        my_locals['dbgr'] = self.dbgr
        cfg.TerminalInteractiveShell.confirm_exit = False

        # sys.ps1 = 'trepan3 >>> '
        if len(my_locals):
            banner=(banner_tmpl % ' with locals')
        else:
            banner=(banner_tmpl % '')
            pass

        InteractiveShellEmbed(config=cfg, banner1=banner,
                              user_ns = my_locals,
                              module = my_globals,
                              exit_msg='IPython exiting to trepan3k...')()

        # restore completion and our history if we can do so.
        if hasattr(self.proc.intf[-1], 'complete'):
            try:
                from readline import set_completer, parse_and_bind
                parse_and_bind("tab: complete")
                set_completer(self.proc.intf[-1].complete)
            except ImportError:
                pass
            pass

        if have_line_edit:
            self.proc.read_history_file()
            pass
        return
    pass

# Monkey-patched from code.py
# FIXME: get changes into Python.
def interact(banner=None, readfunc=None, my_locals=None, my_globals=None):
    """Almost a copy of code.interact
    Closely emulate the interactive Python interpreter.

    This is a backwards compatible interface to the InteractiveConsole
    class.  When readfunc is not specified, it attempts to import the
    readline module to enable GNU readline if it is available.

    Arguments (all optional, all default to None):

    banner -- passed to InteractiveConsole.interact()
    readfunc -- if not None, replaces InteractiveConsole.raw_input()
    local -- passed to InteractiveInterpreter.__init__()

    """
    console = code.InteractiveConsole(my_locals, filename='<trepan>')
    console.runcode = lambda code_obj: runcode(console, code_obj)
    setattr(console, 'globals', my_globals)
    if readfunc is not None:
        console.raw_input = readfunc
    else:
        try:
            import readline
        except ImportError:
            pass
    console.interact(banner)
    pass

# Also monkey-patched from code.py
# FIXME: get changes into Python.
def runcode(obj, code_obj):
    """Execute a code object.

    When an exception occurs, self.showtraceback() is called to
    display a traceback.  All exceptions are caught except
    SystemExit, which is reraised.

    A note about KeyboardInterrupt: this exception may occur
    elsewhere in this code, and may not always be caught.  The
    caller should be prepared to deal with it.

    """
    try:
        exec(code_obj, obj.locals, obj.globals)
    except SystemExit:
        raise
    except:
        obj.showtraceback()
    else:
        if code.softspace(sys.stdout, 0):
            print()
            pass
        pass
    return

if __name__ == '__main__':
    from trepan import debugger as Mdebugger
    d = Mdebugger.Debugger()
    command = IPythonCommand(d.core.processor)
    command.proc.frame = sys._getframe()
    command.proc.setup()
    if len(sys.argv) > 1:
        print("Type Python commands and exit to quit.")
        print(sys.argv[1])
        if sys.argv[1] == '-d':
            print(command.run(['python', '-d']))
        else:
            print(command.run(['python']))
            pass
        pass
    pass


# -*- coding: utf-8 -*-
#   Copyright (C) 2013-2014 Rocky Bernstein <rocky@gnu.org>
#
#   This program is free software: you can redistribute it and/or modify
#   it under the terms of the GNU General Public License as published by
#   the Free Software Foundation, either version 3 of the License, or
#   (at your option) any later version.
#
#   This program is distributed in the hope that it will be useful,
#   but WITHOUT ANY WARRANTY; without even the implied warranty of
#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#   GNU General Public License for more details.
#
#   You should have received a copy of the GNU General Public License
#   along with this program.  If not, see <http://www.gnu.org/licenses/>.
#
# Call-frame-oriented helper function for Processor. Put here so we
# can use this in a couple of processors.

from trepan.processor import cmdfns as Mcmdfns
from trepan.lib import complete as Mcomplete

def frame_low_high(proc_obj, direction):
    stack_size = len(proc_obj.stack)  # - hide_level
    if direction is None:
        return [-stack_size, stack_size-1]
    else:
        frame_index = proc_obj.curindex
        low, high = [ frame_index * -direction,
                      (stack_size - frame_index - 1) * direction ]
        if direction < 0: low, high = [high, low]
        return (low, high)
    return

def frame_complete(proc_obj, prefix, direction):
    low, high = frame_low_high(proc_obj, direction)
    ary = [str(low+i) for i in range(high-low+1)]
    return Mcomplete.complete_token(ary, prefix)

def frame_num(proc_obj, pos):
    return len(proc_obj.stack) - pos - 1

def adjust_frame(proc_obj, name, pos, absolute_pos):
    """Adjust stack frame by pos positions. If absolute_pos then
    pos is an absolute number. Otherwise it is a relative number.

    A negative number indexes from the other end."""
    if not proc_obj.curframe:
        proc_obj.errmsg("No stack.")
        return

    # Below we remove any negativity. At the end, pos will be
    # the new value of proc_obj.curindex.
    if absolute_pos:
        if pos >= 0:
            pos = frame_num(proc_obj, pos)
        else:
            pos = -pos - 1
            pass
    else:
        pos += proc_obj.curindex
        pass

    if pos < 0:
        proc_obj.errmsg("Adjusting would put us beyond the oldest frame.")
        return
    elif pos >= len(proc_obj.stack):
        proc_obj.errmsg("Adjusting would put us beyond the newest frame.")
        return

    proc_obj.curindex = pos
    proc_obj.curframe = proc_obj.stack[proc_obj.curindex][0]
    proc_obj.location()
    proc_obj.list_lineno   = None
    proc_obj.list_offset   = proc_obj.curframe.f_lasti
    proc_obj.list_object   = proc_obj.curframe
    proc_obj.list_filename = proc_obj.curframe.f_code.co_filename

    return

def adjust_relative(proc_obj, name, args, signum):
    if not proc_obj.stack:
        proc_obj.errmsg("Program has no stack frame set.")
        return False
    if len(args) == 1:
        count = 1
    else:
        count_str = args[1]
        low, high = frame_low_high(proc_obj, signum)
        count = Mcmdfns.get_an_int( proc_obj.errmsg, count_str,
                                    ("The '%s' command argument must eval to" +
                                     " an integer. Got: %s") %
                                     (name, count_str), low, high )
        if count is None: return
        pass

    adjust_frame(proc_obj, name, pos=signum*count,
                 absolute_pos=False)
    return


# -*- coding: utf-8 -*-
#
#  Copyright (C) 2017 Rocky Bernstein
#  This program is free software: you can redistribute it and/or modify
#  it under the terms of the GNU General Public License as published by
#  the Free Software Foundation, either version 3 of the License, or
#  (at your option) any later version.
#
#  This program is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.
#
#  You should have received a copy of the GNU General Public License
#  along with this program.  If not, see <http://www.gnu.org/licenses/>.

from trepan.processor.parse.semantics import build_range, Location
from trepan.processor.parse.parser import LocationError
from trepan.processor.parse.scanner import ScannerError
from trepan.processor.location import resolve_location

INVALID_PARSE_LIST = (None, None, None)
def parse_list_cmd(proc, args, listsize=10):
    """Parses arguments for the "list" command and returns the tuple:
    (filename, first line number, last line number)
    or sets these to None if there was some problem."""

    text = proc.current_command[len(args[0])+1:].strip()

    if text in frozenset(('', '.', '+', '-')):
        if text == '.':
            location = resolve_location(proc, '.')
            return location.path, location.line_number, listsize
        else:
            if proc.list_lineno is None:
                proc.errmsg("Don't have previous list location")
                return INVALID_PARSE_LIST
            filename = proc.list_filename
            if text == '+':
                first = max(1, proc.list_lineno + listsize)
            elif text == '-':
                if proc.list_lineno == 1 + listsize:
                    proc.errmsg("Already at start of %s." % proc.list_filename)
                    return INVALID_PARSE_LIST
                first = max(1, proc.list_lineno - (2*listsize) - 1)
            elif text == '':
                # Continue from where we last left off
                first = proc.list_lineno + 1
        last = first + listsize - 1
        return filename, first, last
    else:
        try:
            list_range = build_range(text)
        except LocationError as e:
            proc.errmsg("Error in parsing list range at or around:")
            proc.errmsg(e.text)
            proc.errmsg(e.text_cursor)
            return INVALID_PARSE_LIST
        except ScannerError as e:
            proc.errmsg("Lexical error in parsing list range at or around:")
            proc.errmsg(e.text)
            proc.errmsg(e.text_cursor)
            return INVALID_PARSE_LIST

        if list_range.first is None:
            # Last must have been given
            assert isinstance(list_range.last, Location)
            location = resolve_location(proc, list_range.last)
            if not location:
                return INVALID_PARSE_LIST
            last     = location.line_number
            first    = max(1, last - listsize)
            return location.path, first, last
        elif isinstance(list_range.first, int):
            first    = list_range.first
            location = resolve_location(proc, list_range.last)
            if not location:
                return INVALID_PARSE_LIST
            filename = location.path
            last     = location.line_number
            if last < first:
                # Treat as a count rather than an absolute location
                last = first + last
            return location.path, first, last
        else:
            # First is location. Last may be empty or a number
            assert isinstance(list_range.first, Location)
            location = resolve_location(proc, list_range.first)
            if not location:
                return INVALID_PARSE_LIST
            first    = location.line_number
            last     = list_range.last
            if location.method:
                first -= listsize // 2
            if isinstance(last, str):
                # Is an offset +number
                assert last[0] == '+'
                last = first + int(last[1:])
            elif not last:
                last = first + listsize
            elif last < first:
                # Treat as a count rather than an absolute location
                last = first + last

            return location.path, first, last
        pass
    return

# Demo it
if __name__=='__main__':
    from trepan.processor.command import mock as Mmock
    from trepan.processor.cmdproc import CommandProcessor
    import sys
    d = Mmock.MockDebugger()
    cmdproc = CommandProcessor(d.core)
    # print '-' * 10
    # print_source_line(sys.stdout.write, 100, 'source_line_test.py')
    # print '-' * 10
    cmdproc.frame = sys._getframe()
    cmdproc.setup()

    def five():
        return 5
    import os
    for cmd in (
            # "list",
            # "list +",
            # "list -",
            # "list 15, 10",
            "list five()",
            # "list 9 , 5",
            # "list 7 ,",
            # "list '''c:\\tmp\\foo.bat''':1",
            # 'list """/Users/My Documents/foo.py""":2',
            # 'list build_range()',
            # 'list os:1 ,',
            'list os.path:9 ,',
            ):
        args = cmd.split(' ')
        cmdproc.current_command = cmd
        print(parse_list_cmd(cmdproc, args))
    pass


# -*- coding: utf-8 -*-
#   Copyright (C) 2009-2010, 2013-2015, 2017 Rocky Bernstein <rocky@gnu.org>
#
#   This program is free software: you can redistribute it and/or modify
#   it under the terms of the GNU General Public License as published by
#   the Free Software Foundation, either version 3 of the License, or
#   (at your option) any later version.
#
#   This program is distributed in the hope that it will be useful,
#   but WITHOUT ANY WARRANTY; without even the implied warranty of
#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#   GNU General Public License for more details.
#
#   You should have received a copy of the GNU General Public License
#   along with this program.  If not, see <http://www.gnu.org/licenses/>.
"""Debugger input possibly attached to a user or interactive. """

import io, sys

from trepan import misc as Mmisc
from trepan.inout import base as Mbase

def readline_importable():
    try:
        import readline  # NOQA
        return True
    except ImportError:
        return False
    return  # Not reached

class DebuggerUserInput(Mbase.DebuggerInputBase):
    """Debugger input connected to what we think of as a end-user input
    as opposed to a relay mechanism to another process. Input could be
    interative terminal, but it might be file input."""

    def __init__(self, inp=None, opts=None):
        self.input     = inp or sys.stdin
        self.line_edit = None  # Our name for GNU readline capability
        self.open(self.input, opts)
        return

    def close(self):
        self.input.close()
        self.closed = True
        return

    DEFAULT_OPEN_READ_OPTS = {
        'use_raw'      : True,
        'try_readline' : True,
        }

    def use_history(self):
        return self.use_raw and readline_importable()

    def open(self, inp, opts={}):
        """Use this to set where to read from.

        Set opts['try_lineedit'] if you want this input to interact
        with GNU-like readline library. By default, we will assume to
        try importing and using readline. If readline is not
        importable, line editing is not available whether or not
        opts['try_readline'] is set.

        Set opts['use_raw'] if input should use Python's use_raw(). If
        however 'inp' is a string and opts['use_raw'] is not set, we
        will assume no raw output. Note that an individual readline
        may override the setting.
        """
        get_option = lambda key: Mmisc.option_set(opts, key,
                                                  self.DEFAULT_OPEN_READ_OPTS)
        if (isinstance(inp, io.TextIOWrapper) or
            isinstance(inp, io.StringIO) or
            hasattr(inp, 'isatty') and inp.isatty()):
            self.use_raw = get_option('use_raw')
        elif isinstance(inp, 'string'.__class__):  # FIXME
            if opts is None:
                self.use_raw = False
            else:
                self.use_raw = get_option('use_raw')
                pass
            inp = open(inp, 'r')
        else:
            raise IOError("Invalid input type (%s) for %s" % (type(inp),
                                                                inp))
        self.input     = inp
        self.line_edit = get_option('try_readline') and readline_importable()
        self.closed = False
        return

    def readline(self, use_raw=None, prompt=''):
        """Read a line of input. EOFError will be raised on EOF.

        Note: some user interfaces may decide to arrange to call
        DebuggerOutput.write() first with the prompt rather than pass
        it here.. If `use_raw' is set raw_input() will be used in that
        is supported by the specific input input. If this option is
        left None as is normally expected the value from the class
        initialization is used.
        """
        # FIXME we don't do command completion.
        if use_raw is None:
            use_raw = self.use_raw
            pass
        if use_raw:
            try:
                inp = input(prompt)
                # import pdb; pdb.set_trace()
                return inp
            except ValueError:
                raise EOFError
            pass

        else:
            line = self.input.readline()
            if not line: raise EOFError
            return line.rstrip("\n")
        pass
    pass

# Demo
if __name__=='__main__':
    print('readline importable: ', readline_importable())
    inp = DebuggerUserInput(__file__)
    line = inp.readline()
    print(line)
    inp.close()
    inp.open('input.py', opts={'use_raw': False})
    while True:
        try:
            inp.readline()
        except EOFError:
            break
        pass
    try:
        inp.readline()
    except EOFError:
        print('EOF handled correctly')
    pass

    if len(sys.argv) > 1:
        inp = DebuggerUserInput()
        try:
            print("Type some characters:", end=' ')
            line = inp.readline()
            print("You typed: %s" % line)
            print("Type some more characters (raw):", end=' ')
            line = inp.readline(True)
            print("Type even more characters (not raw):", end=' ')
            line = inp.readline(True)
            print("You also typed: %s" % line)
        except EOFError:
            print("Got EOF")
        pass
    pass


# -*- coding: utf-8 -*-
#   Copyright (C) 2008-2010, 2013-2015, 2018 Rocky Bernstein <rocky@gnu.org>
#
#   This program is free software: you can redistribute it and/or modify
#   it under the terms of the GNU General Public License as published by
#   the Free Software Foundation, either version 3 of the License, or
#   (at your option) any later version.
#
#   This program is distributed in the hope that it will be useful,
#   but WITHOUT ANY WARRANTY; without even the implied warranty of
#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#   GNU General Public License for more details.
#
#   You should have received a copy of the GNU General Public License
#   along with this program.  If not, see <http://www.gnu.org/licenses/>.
"""Debugger class and top-level debugger functions.

This module contains the `Debugger' class and some top-level routines
for creating and invoking a debugger. Most of this module serves as:
  * a wrapper for `Debugger.core' routines,
  * a place to define `Debugger' exceptions, and
  * `Debugger' settings.

See also module `cli' which contains a command-line interface to debug
a Python script and `core' which contains the core debugging
start/stop and event-handling dispatcher and `client.py' which is a
user or client-side code for connecting to server'd debugged program.
"""

# Our local modules

from trepan.exception import DebuggerQuit, DebuggerRestart

# Default settings used here
import trepan.lib.default as Mdefault
import trepan.interfaces.user as Muser
from trepan.misc import option_set
import trepan.lib.sighandler as Msig

# Common Python packages
import sys, types

# External Egg packages
import tracer, tracefilter, pyficache

debugger_obj = None

try:
    from readline import get_line_buffer
except ImportError:
    def get_line_buffer():
        return None
    pass

class Trepan:

    # The following functions have to be defined before
    # DEFAULT_INIT_OPTS which includes references to these.

    # FIXME DRY run, run_exec, run_eval.

    def run(self, cmd, start_opts=None, globals_=None, locals_=None):
        """ Run debugger on string `cmd' using builtin function eval
        and if that builtin exec.  Arguments `globals_' and `locals_'
        are the dictionaries to use for local and global variables. By
        default, the value of globals is globals(), the current global
        variables. If `locals_' is not given, it becomes a copy of
        `globals_'.

        Debugger.core.start settings are passed via optional
        dictionary `start_opts'. Overall debugger settings are in
        Debugger.settings which changed after an instance is created
        . Also see `run_eval' if what you want to run is an
        run_eval'able expression have that result returned and
        `run_call' if you want to debug function run_call.
        """
        if globals_ is None:
            globals_ = globals()
        if locals_ is None:
            locals_ = globals_
        if not isinstance(cmd, types.CodeType):
            self.eval_string = cmd
            cmd = cmd+'\n'
            pass
        retval = None
        self.core.start(start_opts)
        try:
            retval = eval(cmd, globals_, locals_)
        except SyntaxError:
            try:
                exec(cmd, globals_, locals_)
            except DebuggerQuit:
                pass
            except DebuggerQuit:
                pass
            pass
        except DebuggerQuit:
            pass
        finally:
            self.core.stop()
        return retval

    def run_exec(self, cmd, start_opts=None, globals_=None, locals_=None):
        """ Run debugger on string `cmd' which will executed via the
        builtin function exec. Arguments `globals_' and `locals_' are
        the dictionaries to use for local and global variables. By
        default, the value of globals is globals(), the current global
        variables. If `locals_' is not given, it becomes a copy of
        `globals_'.

        Debugger.core.start settings are passed via optional
        dictionary `start_opts'. Overall debugger settings are in
        Debugger.settings which changed after an instance is created
        . Also see `run_eval' if what you want to run is an
        run_eval'able expression have that result returned and
        `run_call' if you want to debug function run_call.
        """
        if globals_ is None:
            globals_ = globals()
        if locals_ is None:
            locals_ = globals_
        if not isinstance(cmd, types.CodeType):
            cmd = cmd+'\n'
            pass
        self.core.start(start_opts)
        try:
            exec(cmd, globals_, locals_)
        except DebuggerQuit:
            pass
        finally:
            self.core.stop()
        return

    def run_call(self, func, start_opts=None, *args, **kwds):
        """ Run debugger on function call: `func(*args, **kwds)'

        See also `run_eval' if what you want to run is an eval'able
        expression have that result returned and `run' if you want to
        debug a statment via exec.
        """
        res = None
        self.core.start(opts=start_opts)
        try:
            res = func(*args, **kwds)
        except DebuggerQuit:
            pass
        finally:
            self.core.stop()
        return res

    def run_eval(self, expr, start_opts=None, globals_=None, locals_=None):
        """ Run debugger on string `expr' which will executed via the
        built-in Python function: eval; `globals_' and `locals_' are
        the dictionaries to use for local and global variables. If
        `globals' is not given, __main__.__dict__ (the current global
        variables) is used. If `locals_' is not given, it becomes a
        copy of `globals_'.

        See also `run_call' if what you to debug a function call and
        `run' if you want to debug general Python statements.
        """
        if globals_ is None:
            globals_ = globals()
        if locals_ is None:
            locals_ = globals_
        if not isinstance(expr, types.CodeType):
            self.eval_string = expr
            expr = expr+'\n'
            pass
        retval = None
        self.core.start(start_opts)
        try:
            retval = eval(expr, globals_, locals_)
        except DebuggerQuit:
            pass
        finally:
            pyficache.remove_remap_file('<string>')
            self.core.stop()
        return retval

    def run_script(self, filename, start_opts=None, globals_=None,
                   locals_=None):
        """ Run debugger on Python script `filename'. The script may
        inspect sys.argv for command arguments. `globals_' and
        `locals_' are the dictionaries to use for local and global
        variables. If `globals' is not given, globals() (the current
        global variables) is used. If `locals_' is not given, it
        becomes a copy of `globals_'.

        True is returned if the program terminated normally and False
        if the debugger initiated a quit or the program did not normally
        terminate.

        See also `run_call' if what you to debug a function call,
        `run_eval' if you want to debug an expression, and `run' if you
        want to debug general Python statements not inside a file.
        """
        self.mainpyfile = self.core.canonic(filename)

        # Start with fresh empty copy of globals and locals and tell the script
        # that it's being run as __main__ to avoid scripts being able to access
        # the debugger namespace.
        if globals_ is None:
            import __main__  # NOQA
            globals_ = {"__name__" : "__main__",
                       "__file__" : self.mainpyfile,
                       "__builtins__" : __builtins__
                       }  # NOQA
        if locals_ is None:
            locals_ = globals_
        retval = False
        self.core.execution_status = 'Running'
        try:
            compiled = compile(open(self.mainpyfile).read(),
                               self.mainpyfile, 'exec')
            self.core.start(start_opts)
            exec(compiled, globals_, locals_)
            retval = True
        except SyntaxError:
            print(sys.exc_info()[1])
            retval = False
            pass
        except IOError:
            print(sys.exc_info()[1])
        except DebuggerQuit:
            retval = False
            pass
        except DebuggerRestart:
            self.core.execution_status = 'Restart requested'
            raise DebuggerRestart
        finally:
            self.core.stop(options={'remove': True})
        return retval

    def restart_argv(self):
        '''Return an array that would be execv-ed  to restart the program'''
        return self.orig_sys_argv or self.program_sys_argv

    # Note: has to come after functions listed in ignore_filter.
    DEFAULT_INIT_OPTS = {
        # What routines will we not trace into?
        'ignore_filter': tracefilter.TraceFilter(
            [tracer.start, tracer.stop,
             run_eval, run_call, run_eval, run_script]),

        # sys.argv when not None contains sys.argv *before* debugger
        # command processing. So sys.argv contains debugger options as
        # well as debugged-program options. These options are used to
        # do a "hard" or execv() restart.

        # program_sys_argv is set by option save_sys_argv and contains
        # sys.argv that we see now which may have debugger options
        # stripped, or it may be that we were not called from a
        # debugger front end but from inside the running
        # program. These options are suitable for a "soft" or
        # exception-handling DebuggerRestart kind of restart.
        'orig_sys_argv' : None,
        'save_sys_argv' : True,

        # How is I/O for this debugger handled?
        'activate'    : False,
        'interface'   : None,
        'input'       : None,
        'output'      : None,
        'processor'   : None,

        # Setting contains lots of debugger settings - a whole file
        # full of them!
        'settings'    : Mdefault.DEBUGGER_SETTINGS,

        'start_opts'  : Mdefault.START_OPTS,
        'step_ignore' : 0,

        'from_ipython' : False
        }

    def __init__(self, opts=None):
        """Create a debugger object. But depending on the value of
        key 'start' inside hash 'opts', we may or may not initially
        start debugging.

        See also Debugger.start and Debugger.stop.
        """

        import trepan.lib.core as Mcore

        self.mainpyfile  = None
        self.thread      = None
        self.eval_string = None
        get_option = lambda key: option_set(opts, key,
                                            self.DEFAULT_INIT_OPTS)
        completer  = lambda text, state: self.complete(text, state)

        # set the instance variables that come directly from options.
        for opt in ('settings', 'orig_sys_argv', 'from_ipython'):
            setattr(self, opt, get_option(opt))
            pass

        core_opts = {}
        for opt in ('ignore_filter', 'proc_opts', 'processor', 'step_ignore',
                    'processor',):
            core_opts[opt] = get_option(opt)
            pass

        # How is I/O for this debugger handled? This should
        # be set before calling DebuggerCore.
        interface_opts={'complete': completer}
        # FIXME when I pass in opts=opts things break
        interface = (get_option('interface') or
                     Muser.UserInterface(opts=interface_opts))
        self.intf = [interface]

        inp = get_option('input')
        if inp:
            self.intf[-1].input = inp
            pass

        out = get_option('output')
        if out:
            self.intf[-1].output = out
            pass

        self.core = Mcore.TrepanCore(self, core_opts)
        self.core.add_ignore(self.core.stop)

        # When set True, we'll also suspend our debug-hook tracing.
        # This gives us a way to prevent or allow self debugging.
        self.core.trace_hook_suspend = False

        if get_option('save_sys_argv'):
            # Save the debugged program's sys.argv? We do this so that
            # when the debugged script munges these, we have a good
            # copy to use for an exec restart
            self.program_sys_argv = list(sys.argv)
        else:
            self.program_sys_argv = None
            pass

        self.sigmgr = Msig.SignalManager(self)

        # Were we requested to activate immediately?
        if get_option('activate'):
            self.core.start(get_option('start_opts'))
            pass
        return

    def complete(self, last_token, state):
        if hasattr(self.core.processor, 'completer'):
            str = get_line_buffer() or last_token
            results = self.core.processor.completer(str, state)
            return results[state]
        else:
            return [None]
    pass

# Demo it
if __name__=='__main__':
    def foo():
        y = 2
        for i in range(2):
            print("%d %d" % (i, y) )
            pass
        return 3
    import debugger
    d = debugger.Trepan()
    d.settings['trace'] = True
    d.settings['printset'] = tracer.ALL_EVENTS
    d.core.step_ignore = -1
    print('Issuing: run_eval("1+2")')
    print(d.run_eval('1+2'))
    print('Issuing: run_exec("x=1; y=2")')
    d.run_exec('x=1; y=2')

    print('Issuing: run("3*4")')
    print(d.run('3*4'))
    print('Issuing: run("x=3; y=4")')
    d.run('x=3; y=4')

    print('Issuing: run_call(foo)')
    d.run_call(foo)
    if len(sys.argv) > 1:
        while True:
            try:
                print('started')
                d.core.step_ignore = 0
                d.core.start()
                x = 4
                x = foo()
                for i in range(2):
                    print("%d" % (i+1)*10)
                    pass
                d.core.stop()

                def square(x): return x*x
                print('calling: run_call(square,2)')
                d.run_call(square, 2)
            except DebuggerQuit:
                print("That's all Folks!...")
                break
            except DebuggerRestart:
                print('Restarting...')
                pass
            pass
        pass
    pass


# -*- coding: utf-8 -*-
#   Copyright (C) 2008-2010, 2013-2018 Rocky Bernstein <rocky@gnu.org>
#
#   This program is free software: you can redistribute it and/or modify
#   it under the terms of the GNU General Public License as published by
#   the Free Software Foundation, either version 3 of the License, or
#   (at your option) any later version.
#
#   This program is distributed in the hope that it will be useful,
#   but WITHOUT ANY WARRANTY; without even the implied warranty of
#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#   GNU General Public License for more details.
#
#   You should have received a copy of the GNU General Public License
#   along with this program.  If not, see <http://www.gnu.org/licenses/>.
import inspect, linecache, os, sys, shlex, tempfile, traceback, re
import pyficache
from trepan.processor import cmdfns
from trepan.lib.deparse import deparse_and_cache

try:
    from reprlib import Repr
except:
    from reprlib import repr as Repr

from pygments.console import colorize

from tracer import EVENT2SHORT

from trepan import vprocessor as Mprocessor
from trepan.lib import bytecode as Mbytecode
from trepan import exception as Mexcept
from trepan.lib import display as Mdisplay
from trepan import misc as Mmisc
from trepan.lib import file as Mfile
from trepan.lib import stack as Mstack
from trepan.lib import thred as Mthread
from trepan.processor import complete as Mcomplete
from trepan.processor.cmdfns import deparse_fn

warned_file_mismatches = set()

def get_srcdir():
    filename = os.path.normcase(os.path.dirname(os.path.abspath(__file__)))
    return os.path.realpath(filename)

# arg_split culled from ipython's routine
def arg_split(s, posix=False):
    """Split a command line's arguments in a shell-like manner returned
    as a list of lists. Use ';;' with white space to indicate separate
    commands.

    This is a modified version of the standard library's shlex.split()
    function, but with a default of posix=False for splitting, so that quotes
    in inputs are respected.
    """

    args_list = [[]]
    if isinstance(s, bytes):
        s = s.decode("utf-8")
    lex = shlex.shlex(s, posix=posix)

    lex.whitespace_split = True
    args = list(lex)
    for arg in args:
        if ';;' == arg:
            args_list.append([])
        else:
            args_list[-1].append(arg)
            pass
        pass
    return args_list

def get_stack(f, t, botframe, proc_obj=None):
    """Return a stack of frames which the debugger will use for in
    showing backtraces and in frame switching. As such various frame
    that are really around may be excluded unless we are debugging the
    sebugger. Also we will add traceback frame on top if that
    exists."""
    exclude_frame = lambda f: False
    if proc_obj:
        settings = proc_obj.debugger.settings
        if not settings['dbg_trepan']:
            exclude_frame = lambda f: \
                proc_obj.core.ignore_filter.is_included(f)
            pass
        pass
    stack = []
    if t and t.tb_frame is f:
        t = t.tb_next
    while f is not None:
        if exclude_frame(f): break  # See commented alternative below
        stack.append((f, f.f_lineno))
        # bdb has:
        # if f is botframe: break
        f = f.f_back
        pass
    stack.reverse()
    i = max(0, len(stack) - 1)
    while t is not None:
        stack.append((t.tb_frame, t.tb_lineno))
        t = t.tb_next
        pass
    return stack, i

def run_hooks(obj, hooks, *args):
    """Run each function in `hooks' with args"""
    for hook in hooks:
        if hook(obj, *args): return True
        pass
    return False

def resolve_name(obj, command_name):
    if command_name.lower() not in obj.commands:
        if command_name in obj.aliases:
            command_name = obj.aliases[command_name]
            pass
        else:
            return None
        pass
    try:
        return command_name.lower()
    except:
        return None
    return

def print_source_line(msg, lineno, line, event_str=None):
    """Print out a source line of text , e.g. the second
    line in:
        (/tmp.py:2):  <module>
        L -- 2 import sys,os
        (trepan3k)

    We define this method
    specifically so it can be customized for such applications
    like ipython."""

    # We don't use the filename normally. ipython and other applications
    # however might.
    return msg('%s %d %s' % (event_str, lineno, line))

def print_source_location_info(print_fn, filename, lineno, fn_name=None,
                               f_lasti=None, remapped_file=None):
    """Print out a source location , e.g. the first line in
    line in:
        (/tmp.py:2 @21):  <module>
        L -- 2 import sys,os
        (trepan3k)
    """
    if remapped_file:
        mess = '(%s:%s remapped %s' % (remapped_file, lineno, filename)
    else:
        mess = '(%s:%s' % (filename, lineno)
    if f_lasti and f_lasti != -1:
        mess += ' @%d' % f_lasti
        pass
    mess += '):'
    if fn_name and fn_name != '?':
        mess += " %s" % fn_name
        pass
    print_fn(mess)
    return

def print_location(proc_obj):
    """Show where we are. GUI's and front-end interfaces often
    use this to update displays. So it is helpful to make sure
    we give at least some place that's located in a file.
    """
    i_stack = proc_obj.curindex
    if i_stack is None or proc_obj.stack is None:
        return False
    core_obj = proc_obj.core
    dbgr_obj = proc_obj.debugger
    intf_obj = dbgr_obj.intf[-1]

    # Evaluation routines like "exec" don't show useful location
    # info. In these cases, we will use the position before that in
    # the stack.  Hence the looping below which in practices loops
    # once and sometimes twice.
    remapped_file = None
    source_text = None
    while i_stack >= 0:
        frame_lineno = proc_obj.stack[i_stack]
        i_stack -= 1
        frame, lineno = frame_lineno

#         # Next check to see that local variable breadcrumb exists and
#         # has the magic dynamic value.
#         # If so, it's us and we don't normally show this.a
#         if 'breadcrumb' in frame.f_locals:
#             if self.run == frame.f_locals['breadcrumb']:
#                 break

        filename = Mstack.frame2file(core_obj, frame, canonic=False)
        if '<string>' == filename and dbgr_obj.eval_string:
            remapped_file = filename
            filename = pyficache.unmap_file(filename)
            if '<string>' == filename:
                remapped = cmdfns.source_tempfile_remap('eval_string',
                                                        dbgr_obj.eval_string)
                pyficache.remap_file(filename, remapped)
                filename = remapped
                lineno = pyficache.unmap_file_line(filename, lineno)
                pass
            pass
        elif '<string>' == filename:
            source_text = deparse_fn(frame.f_code)
            filename = "<string: '%s'>" % source_text
            pass
        else:
            m = re.search('^<frozen (.*)>', filename)
            if m and m.group(1) in pyficache.file2file_remap:
                remapped_file = pyficache.file2file_remap[m.group(1)]
                pass
            elif filename in pyficache.file2file_remap:
                remapped_file = pyficache.unmap_file(filename)
                # FIXME: a remapped_file shouldn't be the same as its unmapped version
                if remapped_file == filename:
                    remapped_file = None
                    pass
                pass
            elif m and m.group(1) in sys.modules:
                remapped_file = m.group(1)
                pyficache.remap_file(filename, remapped_file)
            pass

        opts = {
            'reload_on_change' : proc_obj.settings('reload'),
            'output'           : proc_obj.settings('highlight')
            }

        if 'style' in proc_obj.debugger.settings:
            opts['style'] = proc_obj.settings('style')

        pyficache.update_cache(filename)
        line = pyficache.getline(filename, lineno, opts)
        if not line:
            if (not source_text and
                filename.startswith("<string: ") and proc_obj.curframe.f_code):
                # Deparse the code object into a temp file and remap the line from code
                # into the corresponding line of the tempfile
                co = proc_obj.curframe.f_code
                temp_filename, name_for_code = deparse_and_cache(co, proc_obj.errmsg)
                lineno = 1
                # _, lineno = pyficache.unmap_file_line(temp_filename, lineno, True)
                if temp_filename:
                    filename = temp_filename
                pass

            else:
                # FIXME:
                if source_text:
                    lines = source_text.split("\n")
                    temp_name='string-'
                else:
                    # try with good ol linecache and consider fixing pyficache
                    lines = linecache.getlines(filename)
                    temp_name = filename
                if lines:
                    # FIXME: DRY code with version in cmdproc.py print_location
                    prefix = os.path.basename(temp_name).split('.')[0]
                    fd = tempfile.NamedTemporaryFile(suffix='.py',
                                                     prefix=prefix,
                                                     delete=False)
                    with fd:
                        fd.write(''.join(lines))
                        remapped_file = fd.name
                        pyficache.remap_file(remapped_file, filename)
                    fd.close()
                    pass
            line = linecache.getline(filename, lineno,
                                     proc_obj.curframe.f_globals)
            if not line:
                m = re.search('^<frozen (.*)>', filename)
                if m and m.group(1):
                    remapped_file = m.group(1)
                    try_module = sys.modules.get(remapped_file)
                    if (try_module and inspect.ismodule(try_module) and
                        hasattr(try_module, '__file__')):
                        remapped_file = sys.modules[remapped_file].__file__
                        pyficache.remap_file(filename, remapped_file)
                        line = linecache.getline(remapped_file, lineno,
                                                 proc_obj.curframe.f_globals)
                    else:
                        remapped_file = m.group(1)
                        code = proc_obj.curframe.f_code
                        filename, line = cmdfns.deparse_getline(code, remapped_file,
                                                                lineno, opts)
                    pass
            pass

        try:
            match, reason = Mstack.check_path_with_frame(frame, filename)
            if not match:
                if filename not in warned_file_mismatches:
                    proc_obj.errmsg(reason)
                    warned_file_mismatches.add(filename)
        except:
            pass

        fn_name = frame.f_code.co_name
        last_i  = frame.f_lasti
        print_source_location_info(intf_obj.msg, filename, lineno, fn_name,
                                   remapped_file = remapped_file,
                                   f_lasti = last_i)
        if line and len(line.strip()) != 0:
            if proc_obj.event:
                print_source_line(intf_obj.msg, lineno, line,
                                  proc_obj.event2short[proc_obj.event])
            pass
        if '<string>' != filename: break
        pass

    if proc_obj.event in ['return', 'exception']:
        val = proc_obj.event_arg
        intf_obj.msg('R=> %s' % proc_obj._saferepr(val))
        pass
    return True

# Default settings for command processor method call
DEFAULT_PROC_OPTS = {
    # A list of debugger initialization files to read on first command
    # loop entry.  Often this something like [~/.config/trepanpy/profile] which the
    # front-end sets.
    'initfile_list' : []
}

class CommandProcessor(Mprocessor.Processor):

    def __init__(self, core_obj, opts=None):
        get_option = lambda key: \
            Mmisc.option_set(opts, key, DEFAULT_PROC_OPTS)
        Mprocessor.Processor.__init__(self, core_obj)

        self.continue_running = False  # True if we should leave command loop
        self.event2short      = dict(EVENT2SHORT)
        self.event2short['signal'] = '?!'
        self.event2short['brkpt']  = 'xx'

        self.optional_modules = ('ipython', 'bpy')
        self.cmd_instances    = self._populate_commands()

        # command argument string. Is like current_command, but the part
        # after cmd_name has been removed.
        self.cmd_argstr       = ''

        # command name before alias or macro resolution
        self.cmd_name         = ''
        self.cmd_queue        = []     # Queued debugger commands
        self.completer        = lambda text, state: \
          Mcomplete.completer(self, text, state)
        self.current_command  = ''     # Current command getting run
        self.debug_nest       = 1
        self.display_mgr      = Mdisplay.DisplayMgr()
        self.intf             = core_obj.debugger.intf
        self.last_command     = None   # Initially a no-op
        self.precmd_hooks     = []

        self.location         = lambda : print_location(self)

        self.preloop_hooks    = []
        self.postcmd_hooks    = []

        self._populate_cmd_lists()

        # Note: prompt_str's value set below isn't used. It is
        # computed dynamically. The value is suggestive of what it
        # looks like.
        self.prompt_str     = '(trepan3k) '

        # Stop only if line/file is different from last time
        self.different_line = None

        # These values updated on entry. Set initial values.
        self.curframe       = None
        self.event          = None
        self.event_arg      = None
        self.frame          = None
        self.list_lineno    = 0      # last list number used in "list"
        self.list_offset    = -1     # last list number used in "disassemble"
        self.list_obj       = None
        self.list_filename  = None   # last filename used in list
        self.list_orig_lineno = 0    # line number of frame or exception on setup
        self.list_filename  = None   # filename of frame or exception on setup

        self.macros         = {}     # Debugger Macros

        # Create a custom safe Repr instance and increase its maxstring.
        # The default of 30 truncates error messages too easily.
        self._repr             = Repr()
        self._repr.maxstring   = 100
        self._repr.maxother    = 60
        self._repr.maxset      = 10
        self._repr.maxfrozen   = 10
        self._repr.array       = 10
        self.stack             = []
        self.thread_name       = None
        self.frame_thread_name = None
        initfile_list          = get_option('initfile_list')
        for init_cmdfile in initfile_list:
            self.queue_startfile(init_cmdfile)
        return

    def _saferepr(self, str, maxwidth=None):
        if maxwidth is None:
            maxwidth = self.debugger.settings['width']
        return self._repr.repr(str)[:maxwidth]

    def add_preloop_hook(self, hook, position=-1, nodups = True):
        if hook in self.preloop_hooks: return False
        self.preloop_hooks.insert(position, hook)
        return True

    # To be overridden in derived debuggers
    def defaultFile(self):
        """Produce a reasonable default."""
        filename = self.curframe.f_code.co_filename
        # Consider using is_exec_stmt(). I just don't understand
        # the conditions under which the below test is true.
        if filename == '<string>' and self.debugger.mainpyfile:
            filename = self.debugger.mainpyfile
            pass
        return filename

    def set_prompt(self, prompt='trepan3k'):
        if self.thread_name and self.thread_name != 'MainThread':
            prompt += ':' + self.thread_name
            pass
        self.prompt_str = '%s%s%s' % ('(' * self.debug_nest,
                                       prompt,
                                       ')' * self.debug_nest)
        highlight = self.debugger.settings['highlight']
        if highlight and highlight in ('light', 'dark'):
            self.prompt_str =  colorize('underline', self.prompt_str)
        self.prompt_str += ' '

    def event_processor(self, frame, event, event_arg, prompt='trepan3k'):
        'command event processor: reading a commands do something with them.'
        self.frame     = frame
        self.event     = event
        self.event_arg = event_arg

        filename = frame.f_code.co_filename
        lineno   = frame.f_lineno
        line     = linecache.getline(filename, lineno, frame.f_globals)
        if not line:
            opts = {'output': 'plain',
                    'reload_on_change': self.settings('reload'),
                    'strip_nl': False}
            m = re.search('^<frozen (.*)>', filename)
            if m and m.group(1):
                filename = pyficache.unmap_file(m.group(1))
            line = pyficache.getline(filename, lineno, opts)
        self.current_source_text = line
        if self.settings('skip') is not None:
            if Mbytecode.is_def_stmt(line, frame):
                return True
            if Mbytecode.is_class_def(line, frame):
                return True
            pass
        self.thread_name = Mthread.current_thread_name()
        self.frame_thread_name = self.thread_name
        self.set_prompt(prompt)
        self.process_commands()
        if filename == '<string>': pyficache.remove_remap_file('<string>')
        return True

    def forget(self):
        """ Remove memory of state variables set in the command processor """
        self.stack       = []
        self.curindex    = 0
        self.curframe    = None
        self.thread_name = None
        self.frame_thread_name = None
        return

    def eval(self, arg):
        """Eval string arg in the current frame context."""
        try:
            return eval(arg, self.curframe.f_globals,
                        self.curframe.f_locals)
        except:
            t, v = sys.exc_info()[:2]
            if isinstance(t, str):
                exc_type_name = t
                pass
            else: exc_type_name = t.__name__
            self.errmsg(str("%s: %s" % (exc_type_name, arg)))
            raise
        return None  # Not reached

    def exec_line(self, line):
        if self.curframe:
            local_vars = self.curframe.f_locals
            global_vars = self.curframe.f_globals
        else:
            local_vars = None
            # FIXME: should probably have place where the
            # user can store variables inside the debug session.
            # The setup for this should be elsewhere. Possibly
            # in interaction.
            global_vars = None
        try:
            code = compile(line + '\n', '"%s"' % line, 'single')
            exec(code, global_vars, local_vars)
        except:
            t, v = sys.exc_info()[:2]
            if type(t) == bytes:
                exc_type_name = t
            else: exc_type_name = t.__name__
            self.errmsg('%s: %s' % (str(exc_type_name), str(v)))
            pass
        return

    def get_an_int(self, arg, msg_on_error, min_value=None, max_value=None):
        """Like cmdfns.get_an_int(), but if there's a stack frame use that
        in evaluation."""
        ret_value = self.get_int_noerr(arg)
        if ret_value is None:
            if msg_on_error:
                self.errmsg(msg_on_error)
            else:
                self.errmsg('Expecting an integer, got: %s.' % str(arg))
                pass
            return None
        if min_value and ret_value < min_value:
            self.errmsg('Expecting integer value to be at least %d, got: %d.' %
                        (min_value, ret_value))
            return None
        elif max_value and ret_value > max_value:
            self.errmsg('Expecting integer value to be at most %d, got: %d.' %
                        (max_value, ret_value))
            return None
        return ret_value

    def get_int_noerr(self, arg):
        """Eval arg and it is an integer return the value. Otherwise
        return None"""
        if self.curframe:
            g = self.curframe.f_globals
            l = self.curframe.f_locals
        else:
            g = globals()
            l = locals()
            pass
        try:
            val = int(eval(arg, g, l))
        except (SyntaxError, NameError, ValueError, TypeError):
            return None
        return val

    def get_int(self, arg, min_value=0, default=1, cmdname=None,
                at_most=None):
        """If no argument use the default. If arg is a an integer between
        least min_value and at_most, use that. Otherwise report an error.
        If there's a stack frame use that in evaluation."""

        if arg is None: return default
        default = self.get_int_noerr(arg)
        if default is None:
            if cmdname:
                self.errmsg(("Command '%s' expects an integer; "
                             + "got: %s.") % (cmdname, str(arg)))
            else:
                self.errmsg('Expecting a positive integer, got: %s'
                            % str(arg))
                pass
            return None
            pass
        if default < min_value:
            if cmdname:
                self.errmsg(("Command '%s' expects an integer at least" +
                             ' %d; got: %d.')
                            % (cmdname, min_value, default))
            else:
                self.errmsg(("Expecting a positive integer at least" +
                             ' %d; got: %d')
                            % (min_value, default))
                pass
            return None
        elif at_most and default > at_most:
            if cmdname:
                self.errmsg(("Command '%s' expects an integer at most" +
                             ' %d; got: %d.')
                            % (cmdname, at_most, default))
            else:
                self.errmsg(("Expecting an integer at most %d; got: %d")
                            % (at_most, default))
                pass
            pass
        return default

    def getval(self, arg, locals=None):
        if not locals:
            locals = self.curframe.f_locals
        try:
            return eval(arg, self.curframe.f_globals, locals)
        except:
            t, v = sys.exc_info()[:2]
            if isinstance(t, str):
                exc_type_name = t
            else: exc_type_name = t.__name__
            self.errmsg(str("%s: %s" % (exc_type_name, arg)))
            raise
        return

    def ok_for_running(self, cmd_obj, name, nargs):
        """We separate some of the common debugger command checks here:
        whether it makes sense to run the command in this execution state,
        if the command has the right number of arguments and so on.
        """
        if hasattr(cmd_obj, 'execution_set'):
            if not (self.core.execution_status in cmd_obj.execution_set):
                part1 = ("Command '%s' is not available for execution status:"
                         % name)
                mess = Mmisc.wrapped_lines(part1,
                                        self.core.execution_status,
                                        self.debugger.settings['width'])
                self.errmsg(mess)
                return False
            pass
        if self.frame is None and cmd_obj.need_stack:
            self.intf[-1].errmsg("Command '%s' needs an execution stack."
                                 % name)
            return False
        if nargs < cmd_obj.min_args:
            self.errmsg(("Command '%s' needs at least %d argument(s); " +
                             "got %d.") %
                             (name, cmd_obj.min_args, nargs))
            return False
        elif cmd_obj.max_args is not None and nargs > cmd_obj.max_args:
            self.errmsg(("Command '%s' can take at most %d argument(s);" +
                              " got %d.") %
                             (name, cmd_obj.max_args, nargs))
            return False
        return True

    def process_commands(self):
        """Handle debugger commands."""
        if self.core.execution_status != 'No program':
            self.setup()
            self.location()
            pass
        leave_loop = run_hooks(self, self.preloop_hooks)
        self.continue_running = False

        while not leave_loop:
            try:
                run_hooks(self, self.precmd_hooks)
                # bdb had a True return to leave loop.
                # A more straight-forward way is to set
                # instance variable self.continue_running.
                leave_loop = self.process_command()
                if leave_loop or self.continue_running: break
            except EOFError:
                # If we have stacked interfaces, pop to the next
                # one.  If this is the last one however, we'll
                # just stick with that.  FIXME: Possibly we should
                # check to see if we are interactive.  and not
                # leave if that's the case. Is this the right
                # thing?  investigate and fix.
                if len(self.debugger.intf) > 1:
                    del self.debugger.intf[-1]
                    self.last_command = ''
                else:
                    if self.debugger.intf[-1].output:
                        self.debugger.intf[-1].output.writeline('Leaving')
                        raise Mexcept.DebuggerQuit
                        pass
                    break
                pass
            pass
        return run_hooks(self, self.postcmd_hooks)

    def process_command(self):
        # process command
        if len(self.cmd_queue) > 0:
            current_command = self.cmd_queue[0].strip()
            del self.cmd_queue[0]
        else:
            current_command = (
                self.intf[-1].read_command(self.prompt_str).strip())
            if '' == current_command and self.intf[-1].interactive:
                current_command = self.last_command
                pass
            pass
        # Look for comments
        if '' == current_command:
            if self.intf[-1].interactive:
                self.errmsg("No previous command registered, " +
                            "so this is a no-op.")
                pass
            return False
        if current_command is None or current_command[0] == '#':
            return False
        try:
            args_list = arg_split(current_command)
        except:
            self.errmsg("bad parse %s: %s" % sys.exc_info()[0:2])
            return False

        for args in args_list:
            if len(args):
                while True:
                    if len(args) == 0: return False
                    macro_cmd_name = args[0]
                    if macro_cmd_name not in self.macros: break
                    try:
                        current_command = \
                          self.macros[macro_cmd_name][0](*args[1:])
                    except TypeError:
                        t, v = sys.exc_info()[:2]
                        self.errmsg("Error expanding macro %s" %
                                    macro_cmd_name)
                        return False
                    if self.settings('debugmacro'):
                        print(current_command)
                        pass
                    if isinstance(types.ListType, type(current_command)):
                        for x in current_command:
                            if str != type(x):
                                self.errmsg(("macro %s should return a List " +
                                             "of Strings. Has %s of type %s") %
                                             (macro_cmd_name, x,
                                              repr(current_command),
                                              type(x)))
                                return False
                            pass

                        first = current_command[0]
                        args =  first.split()
                        self.cmd_queue + [current_command[1:]]
                        current_command = first
                    elif type(current_command) == str:
                        args = current_command.split()
                    else:
                        self.errmsg(("macro %s should return a List " +
                                     "of Strings or a String. Got %s") %
                                     (macro_cmd_name, repr(current_command)))
                        return False
                    pass

                self.cmd_name = args[0]
                cmd_name = resolve_name(self, self.cmd_name)
                self.cmd_argstr = current_command[len(self.cmd_name):].lstrip()
                if cmd_name:
                    self.last_command = current_command
                    cmd_obj = self.commands[cmd_name]
                    if self.ok_for_running(cmd_obj, cmd_name, len(args)-1):
                        try:
                            self.current_command = current_command
                            result = cmd_obj.run(args)
                            if result: return result
                        except (Mexcept.DebuggerQuit,
                                Mexcept.DebuggerRestart, SystemExit):
                            # Let these exceptions propagate through
                            raise
                        except:
                            self.errmsg("INTERNAL ERROR: " +
                                        traceback.format_exc())
                            pass
                        pass
                    pass
                elif not self.settings('autoeval'):
                    self.undefined_cmd(current_command)
                else:
                    self.exec_line(current_command)
                    pass
                pass
            pass
        return False

    def remove_preloop_hook(self, hook):
        try:
            position = self.preloop_hooks.index(hook)
        except ValueError:
            return False
        del self.preloop_hooks[position]
        return True

    def setup(self):
        """Initialization done before entering the debugger-command
        loop. In particular we set up the call stack used for local
        variable lookup and frame/up/down commands.

        We return True if we should NOT enter the debugger-command
        loop."""
        self.forget()
        if self.settings('dbg_trepan'):
            self.frame = inspect.currentframe()
            pass
        if self.event in ['exception', 'c_exception']:
            exc_type, exc_value, exc_traceback = self.event_arg
        else:
            _, _, exc_traceback = (None, None, None,)  # NOQA
            pass
        if self.frame or exc_traceback:
            self.stack, self.curindex = \
                get_stack(self.frame, exc_traceback, None, self)
            self.curframe = self.stack[self.curindex][0]
            self.thread_name = Mthread.current_thread_name()
            if exc_traceback:
                self.list_lineno = traceback.extract_tb(exc_traceback, 1)[0][1]
                self.list_offset = self.curframe.f_lasti
                self.list_object = self.curframe
        else:
            self.stack = self.curframe = \
                self.botframe = None
            pass
        if self.curframe:
            self.list_lineno = \
                max(1, inspect.getlineno(self.curframe)
                    - int(self.settings('listsize') / 2)) - 1
            self.list_offset   = self.curframe.f_lasti
            self.list_filename = self.curframe.f_code.co_filename
            self.list_object   = self.curframe
        else:
            if not exc_traceback: self.list_lineno = None
            pass
        # if self.execRcLines()==1: return True

        # FIXME:  do we want to save self.list_lineno a second place
        # so that we can do 'list .' and go back to the first place we listed?
        return False

    def queue_startfile(self, cmdfile):
        """Arrange for file of debugger commands to get read in the
        process-command loop."""
        expanded_cmdfile = os.path.expanduser(cmdfile)
        is_readable = Mfile.readable(expanded_cmdfile)
        if is_readable:
            self.cmd_queue.append('source ' + expanded_cmdfile)
        elif is_readable is None:
            self.errmsg("source file '%s' doesn't exist" % expanded_cmdfile)
        else:
            self.errmsg("source file '%s' is not readable" %
                        expanded_cmdfile)
            pass
        return

    def undefined_cmd(self, cmd):
        """Error message when a command doesn't exist"""
        self.errmsg('Undefined command: "%s". Try "help".' % cmd)
        return

    def read_history_file(self):
        """Read the command history file -- possibly."""
        histfile = self.debugger.intf[-1].histfile
        try:
            import readline
            readline.read_history_file(histfile)
        except IOError:
                pass
        except ImportError:
            pass
        return

    def write_history_file(self):
        """Write the command history file -- possibly."""
        settings = self.debugger.settings
        histfile = self.debugger.intf[-1].histfile
        if settings['hist_save']:
            try:
                import readline
                try:
                    readline.write_history_file(histfile)
                except IOError:
                    pass
            except ImportError:
                pass
            pass
        return

    def _populate_commands(self):
        """ Create an instance of each of the debugger
        commands. Commands are found by importing files in the
        directory 'command'. Some files are excluded via an array set
        in __init__.  For each of the remaining files, we import them
        and scan for class names inside those files and for each class
        name, we will create an instance of that class. The set of
        DebuggerCommand class instances form set of possible debugger
        commands."""
        from trepan.processor import command as Mcommand
        if hasattr(Mcommand, '__modules__'):
            return self.populate_commands_easy_install(Mcommand)
        else:
            return self.populate_commands_pip(Mcommand)

    def populate_commands_pip(self, Mcommand):
        cmd_instances = []
        eval_cmd_template = 'command_mod.%s(self)'
        for mod_name in Mcommand.__dict__.keys():
            if mod_name.startswith('__'): continue
            import_name = "trepan.processor.command." + mod_name
            imp = __import__(import_name)
            if imp.__name__ == 'trepan':
                command_mod = imp.processor.command
            else:
                if mod_name in ('info_sub', 'set_sub', 'show_sub',):
                    pass
                try:
                    command_mod = getattr(__import__(import_name), mod_name)
                except:
                    # Don't need to warn about optional modules
                    if mod_name not in self.optional_modules:
                        print('Error importing %s: %s' %
                              (mod_name, sys.exc_info()[0]))
                        pass
                    continue
                pass

            classnames = [ tup[0] for tup in
                           inspect.getmembers(command_mod, inspect.isclass)
                           if ('DebuggerCommand' != tup[0] and
                               tup[0].endswith('Command')) ]
            for classname in classnames:
                eval_cmd = eval_cmd_template % classname
                if False:
                    instance = eval(eval_cmd)
                    cmd_instances.append(instance)
                else:
                    try:
                        instance = eval(eval_cmd)
                        cmd_instances.append(instance)
                    except:
                        print('Error loading %s from %s: %s' %
                              (classname, mod_name, sys.exc_info()[0]))
                        pass
                    pass
                pass
            pass
        return cmd_instances

    def populate_commands_easy_install(self, Mcommand):
        cmd_instances = []
        srcdir = get_srcdir()
        sys.path.insert(0, srcdir)

        for mod_name in Mcommand.__modules__:
            if mod_name in ('info_sub', 'set_sub', 'show_sub',):
                pass
            import_name = "command." + mod_name
            try:
                command_mod = getattr(__import__(import_name), mod_name)
            except:
                if mod_name not in self.optional_modules:
                    print('Error importing %s: %s' %
                          (mod_name, sys.exc_info()[0]))
                    pass
                continue

            classnames = [ tup[0] for tup in
                           inspect.getmembers(command_mod, inspect.isclass)
                           if ('DebuggerCommand' != tup[0] and
                               tup[0].endswith('Command')) ]
            for classname in classnames:
                if False:
                    instance = getattr(command_mod, classname)(self)
                    cmd_instances.append(instance)
                else:
                    try:
                        instance = getattr(command_mod, classname)(self)
                        cmd_instances.append(instance)
                    except:
                        print('Error loading %s from %s: %s' %
                              (classname, mod_name, sys.exc_info()[0]))
                        pass
                    pass
                pass
            pass
        return cmd_instances

    def _populate_cmd_lists(self):
        """ Populate self.lists and hashes:
        self.commands, and self.aliases, self.category """
        self.commands = {}
        self.aliases = {}
        self.category = {}
#         self.short_help = {}
        for cmd_instance in self.cmd_instances:
            if not hasattr(cmd_instance, 'aliases'): continue
            alias_names = cmd_instance.aliases
            cmd_name = cmd_instance.name
            self.commands[cmd_name] = cmd_instance
            for alias_name in alias_names:
                self.aliases[alias_name] = cmd_name
                pass
            cat  = getattr(cmd_instance, 'category')
            if cat and self.category.get(cat):
                self.category[cat].append(cmd_name)
            else:
                self.category[cat] = [cmd_name]
                pass
#             sh = getattr(cmd_instance, 'short_help')
#             if sh:
#                 self.short_help[cmd_name] = getattr(c, 'short_help')
#                 pass
            pass
        for k in list(self.category.keys()):
            self.category[k].sort()
            pass

        return

    pass

# Demo it
if __name__=='__main__':
    from trepan.processor.command import mock as Mmock
    d = Mmock.MockDebugger()
    cmdproc = CommandProcessor(d.core)
    print('commands:')
    commands = list(cmdproc.commands.keys())
    commands.sort()
    print(commands)
    print('aliases:')
    aliases = list(cmdproc.aliases.keys())
    aliases.sort()
    print(aliases)
    print(resolve_name(cmdproc, 'quit'))
    print(resolve_name(cmdproc, 'q'))
    print(resolve_name(cmdproc, 'info'))
    print(resolve_name(cmdproc, 'i'))
    # print '-' * 10
    # print_source_line(sys.stdout.write, 100, 'source_line_test.py')
    # print '-' * 10
    cmdproc.frame = sys._getframe()
    cmdproc.setup()
    print()
    print('-' * 10)
    cmdproc.location()
    print('-' * 10)
    print(cmdproc.eval('1+2'))
    print(cmdproc.eval('len(aliases)'))
    import pprint
    print(pprint.pformat(cmdproc.category))
    print(arg_split("Now is the time"))
    print(arg_split("Now is the time ;;"))
    print(arg_split("Now is 'the time'"))
    print(arg_split("Now is the time ;; for all good men"))
    print(arg_split("Now is the time ';;' for all good men"))

    print(cmdproc.commands)
    fn = cmdproc.commands['quit']

    print('Removing non-existing quit hook: %s' %
          cmdproc.remove_preloop_hook(fn))
    cmdproc.add_preloop_hook(fn)
    print(cmdproc.preloop_hooks)
    print('Removed existing quit hook: %s' % cmdproc.remove_preloop_hook(fn))
    pass


# -*- coding: utf-8 -*-
#   Copyright (C) 2013 Rocky Bernstein <rocky@gnu.org>
#
#   This program is free software: you can redistribute it and/or modify
#   it under the terms of the GNU General Public License as published by
#   the Free Software Foundation, either version 3 of the License, or
#   (at your option) any later version.
#
#   This program is distributed in the hope that it will be useful,
#   but WITHOUT ANY WARRANTY; without even the implied warranty of
#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#   GNU General Public License for more details.
#
#   You should have received a copy of the GNU General Public License
#   along with this program.  If not, see <http://www.gnu.org/licenses/>.
"""Command completion routines."""

import re

def complete_token(complete_ary, prefix):
    return sorted([cmd for cmd in
                   complete_ary if cmd.startswith(prefix)])

def complete_token_with_next(complete_hash, prefix, cmd_prefix=''):
    result = []
    for cmd_name in list(complete_hash.keys()):
        if cmd_name.startswith(cmd_prefix + prefix):
            result.append([cmd_name[len(cmd_prefix):],
                           complete_hash[cmd_name]])
            pass
        pass
    pass
    return sorted(result, key=lambda pair: pair[0])

def complete_token_filtered_with_next(aliases, prefix, expanded, commands):
    """Find all starting matches in dictionary *aliases* that start
    with *prefix*, but filter out any matches already in
    *expanded*."""

    complete_ary = list(aliases.keys())
    expanded_ary = list(expanded.keys())
    # result = [cmd for cmd in
    #             complete_ary if cmd.startswith(prefix) and not (
    #                 cmd in aliases and
    #                 0 == len(set(expanded_ary) - set([aliases[cmd]])))]
    result = []
    for cmd in complete_ary:
        if cmd.startswith(prefix):
            if cmd in aliases and (
                    0 == len(set(expanded_ary) - set([aliases[cmd]]))):
                result.append([cmd, aliases[cmd]])
            pass
        pass
    pass
    return sorted(result, key=lambda pair: pair[0])

def complete_token_filtered(aliases, prefix, expanded):
    """Find all starting matches in dictionary *aliases* that start
     with *prefix*, but filter out any matches already in *expanded*"""

    complete_ary = aliases.keys()
    return [cmd for cmd in complete_ary if cmd.startswith(prefix)]

def complete_brkpts(bpmgr, prefix):
    return complete_token(sorted(bpmgr.bpnumbers()), prefix)

def next_token(str, start_pos):
    """Find the next token in str string from start_pos, we return
    the token and the next blank position after the token or
    str.size if this is the last token. Tokens are delimited by
    white space."""
    look_at = str[start_pos:]
    match = re.search('\S', look_at)
    if match:
        pos = match.start()
    else:
        pos = 0
        pass
    next_nonblank_pos = start_pos + pos
    next_match = re.search('\s', str[next_nonblank_pos:])
    if next_match:
        next_blank_pos = next_nonblank_pos + next_match.start()
    else:
        next_blank_pos = len(str)
        pass
    return [next_blank_pos, str[next_nonblank_pos:next_blank_pos+1].rstrip()]

if __name__=='__main__':
    print(next_token('ab cd ef', 0))
    print(next_token('ab cd ef', 2))
    print(complete_token(('-1', '0'), ''))

    #   0         1
    #   0123456789012345678
    x = '  now is  the  time'
    for pos in [0, 2, 5, 8, 9, 13, 19]:
        print(next_token(x, pos))
        pass
    pass

    print(complete_token(['ba', 'aa', 'ab'], 'a'))
    print(complete_token(['cond', 'condition', 'continue'], 'cond'))
    h = {'ab': 1, 'aac': 2, 'aa': 3, 'b': 4}
    print(complete_token(h.keys(), 'a'))
    print(complete_token_with_next(h, 'a'))


# -*- coding: utf-8 -*-
#   Copyright (C) 2009, 2013-2015 Rocky Bernstein <rocky@gnu.org>
#
#   This program is free software: you can redistribute it and/or modify
#   it under the terms of the GNU General Public License as published by
#   the Free Software Foundation, either version 3 of the License, or
#   (at your option) any later version.
#
#   This program is distributed in the hope that it will be useful,
#   but WITHOUT ANY WARRANTY; without even the implied warranty of
#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#   GNU General Public License for more details.
#
#   You should have received a copy of the GNU General Public License
#   along with this program.  If not, see <http://www.gnu.org/licenses/>.
"""Module for reading debugger scripts"""
import atexit

# Our local modules
from trepan import interface as Minterface, misc as Mmisc
from trepan.inout import scriptin as Mscriptin, output as Moutput

class ScriptInterface(Minterface.TrepanInterface):
    """Interface when reading debugger scripts"""

    DEFAULT_INIT_OPTS = {
        'abort_on_error' : True,
        'confirm_val'    : False,
        'verbose'        : False
        }

    def __init__(self, script_name, out=None, opts=None):
        get_option = lambda key: Mmisc.option_set(opts, key,
                                                  self.DEFAULT_INIT_OPTS)

        atexit.register(self.finalize)
        self.script_name     = script_name
        self.histfile        = None
        self.input_lineno    = 0
        self.input           = Mscriptin.ScriptInput(script_name)
        self.interactive     = False
        self.output          = out or Moutput.DebuggerUserOutput()

        self.abort_on_error  = get_option('abort_on_error')
        self.default_confirm = get_option('confirm_val')
        self.verbose         = get_option('verbose')

        return

    def close(self):
        """ Closes input. (We don't have an output.)"""
        self.input.close()
        return

    # Could look also look for interactive input and
    # use that. For now, though we'll simplify.
    def confirm(self, prompt, default):
        """ Called when a dangerous action is about to be done, to make
        sure it's okay. """
        return self.default_confirm

    def errmsg(self, msg, prefix="** "):
        """Common routine for reporting debugger error messages.
           """
        #  self.verbose shows lines so we don't have to duplicate info
        #  here. Perhaps there should be a 'terse' mode to never show
        #  position info.
        if not self.verbose:
            location = ("%s:%s: Error in source command file"
                        % (self.script_name, self.input_lineno))
            msg = "%s%s:\n%s%s" %(prefix, location, prefix, msg)
        else:
            msg = "%s%s" %(prefix, msg)
            pass
        self.msg(msg)
        if self.abort_on_error:
            raise EOFError
        return

    def finalize(self, last_wishes=None):
        # print exit annotation
        # save history
        self.close()
        return

    def read_command(self, prompt=''):
        '''Script interface to read a command. `prompt' is a parameter for
        compatibilty and is ignored.'''
        self.input_lineno += 1
        line = self.readline()
        if self.verbose:
            location = "%s line %s" % (self.script_name, self.input_lineno)
            self.msg('+ %s: %s' % (location, line))
            pass
        # Do something with history?
        return line

    # Could decide make this look for interactive input?
    def readline(self, prompt=''):
        '''Script interface to read a line. `prompt' is a parameter for
        compatibilty and is ignored.'''
        return self.input.readline()

# Demo
if __name__=='__main__':
    intf = ScriptInterface('script.py')
    line = intf.readline()
    print("Line read: %s" % line)
    pass


# -*- coding: utf-8 -*-
#   Copyright (C) 2009, 2013-2015 Rocky Bernstein <rocky@gnu.org>
#
#   This program is free software: you can redistribute it and/or modify
#   it under the terms of the GNU General Public License as published by
#   the Free Software Foundation, either version 3 of the License, or
#   (at your option) any later version.
#
#   This program is distributed in the hope that it will be useful,
#   but WITHOUT ANY WARRANTY; without even the implied warranty of
#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#   GNU General Public License for more details.
#
#   You should have received a copy of the GNU General Public License
#   along with this program.  If not, see <http://www.gnu.org/licenses/>.
"""Debugger FIFO Input/Output interface. """

import tempfile, os

from trepan.lib import default as Mdefault, file as Mfile
from trepan import misc as Mmisc
from trepan.inout.base import DebuggerInOutBase

class FIFOClient(DebuggerInOutBase):
    """Debugger Client Input/Output Socket."""

    DEFAULT_INIT_OPTS = {'open': True}

    def __init__(self, inp=None, opts=None):
        get_option = lambda key: Mmisc.option_set(opts, key,
                                                  Mdefault.CLIENT_SOCKET_OPTS)
        self.state = 'disconnected'
        self.flush_after_write = True
        self.input  = None
        self.output = None
        self.line_edit = False  # Our name for GNU readline capability
        self.state    = 'disconnected'
        open_pid = get_option('open')
        if open_pid:
            self.open(open_pid)
            pass
        return

    def close(self):
        """ Closes both input and output """
        self.state = 'closing'
        if self.input:
            self.input.close()
            pass
        if self.output:
            self.output.close()
            pass
        self.state = 'disconnnected'
        return

    def flush(self):
        return self.output.flush()

    def open(self, pid, opts=None):

        # Not in/out are reversed from server side
        d              = tempfile.gettempdir()
        self.out_name  = os.path.join(d, ('trepan-%s.in' % pid))
        self.in_name   = os.path.join(d, ('trepan-%s.out' % pid))
        is_readable = Mfile.readable(self.out_name)
        if not is_readable:
            if is_readable is None:
                raise IOError("output FIFO %s doesn't exist" %
                              self.out_name)
            else:
                raise IOError("output FIFO %s is not readable" %
                              self.out_name)
            pass
        is_readable = Mfile.readable(self.in_name)
        if not is_readable:
            if is_readable is None:
                raise IOError("input FIFO %s doesn't exist" %
                              self.in_name)
            else:
                raise IOError("output FIFO %s is not readable" %
                              self.out_name)
            self.state     = 'active'
            pass
        return

    def read_msg(self):
        """Read a line of input. EOFError will be raised on EOF.

        Note that we don't support prompting"""
        # FIXME: do we have to create and check a buffer for
        # lines?
        if self.state == 'active':
            if not self.input:
                self.input = open(self.in_name, 'r')
                pass
            line = self.input.readline()
            if not line:
                self.state = 'disconnected'
                raise EOFError
            return line.encode("utf-8")
        else:
            raise IOError("readline called in state: %s." % self.state)
        return  # Not reached

    def write(self, msg):
        """ This method the debugger uses to write. In contrast to
        writeline, no newline is added to the end to `str'.
        """
        if self.state == 'active':
            if not self.output:
                self.output = open(self.out_name, 'w')
                pass
            pass
        else:
            raise EOFError
        self.output.write(msg)
        if self.flush_after_write: self.flush()
        return

# Demo
if __name__=='__main__':
    fifo = FIFOClient(opts={'open': False})
    import sys
    if len(sys.argv) > 1:
        print('Connecting...',)
        fifo.open(sys.argv[1])
        print('connected.')
        while True:
            prompt = fifo.readline()
            line = input(prompt)
            if len(line) == 0: break
            try:
                line = fifo.writeline(line)
                print("Got: ", fifo.readline())
            except EOFError:
                break
            pass
        pass
    fifo.close()
    pass


#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#   Copyright (C) 2009, 2013-2017 Rocky Bernstein
#
#    This program is free software; you can redistribute it and/or modify
#    it under the terms of the GNU General Public License as published by
#    the Free Software Foundation; either version 2 of the License, or
#    (at your option) any later version.
#
#    This program is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#    GNU General Public License for more details.
#
#    You should have received a copy of the GNU General Public License
#    along with this program; if not, write to the Free Software
#    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
#    02110-1301 USA.

import sys, time

# Our local modules
from trepan.interfaces import client as Mclient
from trepan.interfaces import comcodes as Mcomcodes

from optparse import OptionParser
from trepan.version import VERSION

def process_options(pkg_version, sys_argv, option_list=None):
    """Handle debugger options. Set `option_list' if you are writing
    another main program and want to extend the existing set of debugger
    options.

    The options dicionary from opt_parser is return. sys_argv is
    also updated."""
    usage_str="""%prog [debugger-options]]

    Client connection to an out-of-process trepan3k debugger session"""

    # serverChoices = ('TCP','FIFO', None) # we use PID for now.

    optparser = OptionParser(usage=usage_str, option_list=option_list,
                             version="%%prog version %s" % pkg_version)

    optparser.add_option("-H", "--host", dest="host", default='127.0.0.1',
                         action="store", type='string', metavar='IP-OR-HOST',
                         help="connect IP or host name.")
    optparser.add_option("-P", "--port", dest="port", default=1027,
                         action="store", type='int', metavar='NUMBER',
                         help="Use TCP port number NUMBER for "
                         "out-of-process connections.")
    optparser.add_option("--pid", dest="pid", default=0,
                         action="store", type='int', metavar='NUMBER',
                         help="Use PID to get FIFO names for "
                         "out-of-process connections.")

    optparser.disable_interspersed_args()

    sys.argv = list(sys_argv)
    (opts, sys.argv) = optparser.parse_args()
    return opts, sys.argv

#
# Connects to a debugger in server mode
#

# DEFAULT_CLIENT_CONNECTION_OPTS = {'open': True, 'IO': 'FIFO'}
DEFAULT_CLIENT_CONNECTION_OPTS = {'open': True, 'IO': 'TCP',
                                  'HOST': '127.0.0.1', 'PORT': 1027}
def start_client(connection_opts):
    intf = Mclient.ClientInterface(connection_opts=connection_opts)
    # debugger.interface.append(intf)
    intf.msg("Connected.")
    done=False
    while not done:
        control, remote_msg = intf.read_remote()
        # print 'c, r', control, remote_msg
        if Mcomcodes.PRINT == control:
            print(remote_msg, end=' ')
            pass
        elif control in [Mcomcodes.CONFIRM_TRUE, Mcomcodes.CONFIRM_FALSE]:
            default = (Mcomcodes.CONFIRM_TRUE == control)
            if intf.confirm(remote_msg.rstrip('\n'), default):
                msg='Y'
            else:
                msg='N'
                pass
            intf.write_remote(Mcomcodes.CONFIRM_REPLY, msg)
        elif Mcomcodes.PROMPT == control:
            msg = intf.read_command('(Trepan*) ').strip()
            intf.write_remote(Mcomcodes.CONFIRM_REPLY, msg)
        elif Mcomcodes.QUIT == control:
            print("trepan3kc: That's all, folks...")
            done = True
            break
        elif Mcomcodes.RESTART == control:
            # FIXME need to save stuff like port # and
            # and for FIFO we need new pid.
            if 'TCP' == connection_opts['IO']:
                print('Restarting...')
                intf.inout.close()
                time.sleep(1)
                intf.inout.open()
            else:
                print("Don't know how to hard-restart FIFO...")
                done=True
                pass
            break
        else:
            print("!! Weird status code received '%s'" % control)
            print(remote_msg, end=' ')
            pass
        pass
    intf.close()
    return

def run(opts, sys_argv):
    if hasattr(opts, 'pid') and opts.pid > 0:
        remote_opts = {'open': opts.pid, 'IO': 'FIFO'}
    else:
        remote_opts = {'open': True, 'IO': 'TCP', 'PORT': opts.port,
                       'HOST': opts.host}
    start_client(remote_opts)
    return

def main():
    opts, sys_argv  = process_options(VERSION, sys.argv)
    # print(opts)
    run(opts, sys_argv)
    return

if __name__ == '__main__':
    main()
    pass