import code
import re
import StringIO
import sys
import traceback
import completer
from method import Method
import method.move
import mode.mini
from lex import Lexer, Grammar, PatternRule
from mode.python import PythonGrammar

PAD   = '   '

class ConsoleExec(Method):
    def _execute(self, w, **vargs):
        if w.application.completion_window_is_open():
            w.application.close_completion_buffer()
        s = w.buffer.make_string()
        w.mode.history[-1] = s
        w.mode.history.append('')
        w.buffer.set_data('')
        w.mode.hindex = len(w.mode.history) - 1

        a = w.application
        if not a.has_buffer_name('*Console*'):
            raise Exception, "No console found!"
        b = a.bufferlist.get_buffer_by_name('*Console*')
        if a.window().buffer is not b:
            a.switch_buffer(b)
        p = a.get_mini_buffer_prompt()
        b.insert_string(b.get_buffer_end(), p + s + '\n', force=True)

        if w.mode.saved_input:
            s = w.mode.saved_input + '\n' + s

        try:
            code_obj = code.compile_command(s)
            if code_obj is None:
                w.mode.saved_input = s
                a.set_mini_buffer_prompt('--> ')
                output = None
            else:
                w.mode.saved_input = ''
                a.set_mini_buffer_prompt('>>> ')
                sys.stdout = code_out = StringIO.StringIO()
                sys.stderr = code_err = StringIO.StringIO()
                ok = True
                try:
                    exec code_obj in w.mode.globals, w.mode.locals
                except Exception, e:
                    ok = False
                    output = str(e) + '\n'
                sys.stdout = sys.__stdout__
                sys.stdout = sys.__stderr__
                if ok:
                    output = code_out.getvalue()
                code_out.close()
                code_err.close()
        #except (SyntaxError, OverflowError, ValueError), e:
        except Exception, e:
            a.set_mini_buffer_prompt('>>> ')
            t = sys.exc_traceback
            output = str(e) + traceback.format_exc()

        limit = min([w.width for w in b.windows]) - len(PAD)

        if output:
            newlines = []
            for line in output.split('\n'):
                i = 0
                while i + limit < len(line):
                    j = limit
                    while j > 0 and line[i + j] != ' ':
                        j -= 1
                    if j == 0:
                        newlines.append(PAD + line[i:i + limit])
                        i += j
                    else:
                        newlines.append(PAD + line[i:i + j])
                        i += j + 1
                newlines.append(PAD + line[i:])
            assert newlines[-1] == PAD
            newlines[-1] = ''
            b.insert_lines(b.get_buffer_end(), newlines, force=True)
        for w2 in b.windows:
            w2.goto_end(force=True)

class ConsoleCancel(Method):
    def execute(self, w, **vargs):
        w.application.close_mini_buffer()
        if w.application.completion_window_is_open():
            w.application.close_completion_buffer()
class ConsoleClear(Method):
    def execute(self, w, **vargs):
        a = w.application
        if not a.has_buffer_name('*Console*'):
            raise Exception, "No console found!"
        b = a.bufferlist.get_buffer_by_name('*Console*')
        b.clear()

class ConsoleHistoryPrev(Method):
    def execute(self, w, **vargs):
        if w.mode.hindex <= 0:
            w.mode.hindex = 0
            return
        elif w.mode.hindex == len(w.mode.history) - 1:
            w.mode.history[-1] = w.buffer.make_string()
        w.mode.hindex -= 1
        w.buffer.set_data(w.mode.history[w.mode.hindex])
class ConsoleHistoryNext(Method):
    def execute(self, w, **vargs):
        if w.mode.hindex == len(w.mode.history) - 1:
            return
        w.mode.hindex += 1
        w.buffer.set_data(w.mode.history[w.mode.hindex])

class ConsoleTab(Method):
    def execute(self, w, **vargs):
        a = w.application
        s = w.buffer.make_string()

        x = w.logical_cursor().x
        if not s or s[:x].isspace():
            w.insert_string_at_cursor(' ' * w.mode.tabwidth)
            return

        l = Lexer(w.mode, PythonGrammar)
        tokens = list(l.lex([s]))

        curr_t = None
        curr_i = None
        for i in range(0, len(tokens)):
            t = tokens[i]
            if t.x < x and t.end_x() >= x:
                curr_i = i
                curr_t = t
        if curr_t is None:
            return

        first_t = curr_t
        j       = curr_i

        name_re = re.compile('^[a-zA-Z_][a-zA-Z0-9_]*$')
        if name_re.match(curr_t.string):
            names = [curr_t.string]
        elif curr_t.string == '.':
            names = ['']
        else:
            names = []

        while j >= 1:
            j -= 1
            t = tokens[j]
            if name_re.match(t.string):
                names.insert(0, t.string)
            elif t.string == '.':
                pass
            else:
                break

        if not names:
            return

        obj  = None
        g    = globals()
        i    = 0
        name = names[0]
        if len(names) > 1:
            while i < len(names):
                name = names[i]
                if obj is None:
                    if name in w.mode.locals:
                        obj = w.mode.locals[name]
                    elif name in w.mode.globals:
                        obj = w.mode.globals[name]
                    else:
                        break
                else:
                    if hasattr(obj, name):
                        obj = getattr(obj, name)
                    else:
                        break
                i += 1

        if i == len(names) - 1:
            if obj is not None:
                newnames = dir(obj)
            else:
                newnames = set()
                newnames.update(__builtins__)
                newnames.update(w.mode.locals)
                newnames.update(w.mode.globals)
            candidates = [x for x in newnames if x.startswith(name)]

            s = completer.find_common_string(candidates)[len(name):]
            w.insert_string_at_cursor(s)
            mode.mini.use_completion_window(a, name, candidates)

class ConsoleBase(Method):
    subcls = Method
    subbuf = '*Console*'
    def __init__(self):
        Method.__init__(self)
        self.submethod = self.subcls()
    def _execute(self, w, **vargs):
        a = w.application
        if not a.has_buffer_name(self.subbuf):
            raise Exception, "No console found!"
        w2 = a.bufferlist.get_buffer_by_name(self.subbuf).windows[0]
        self.submethod.execute(w2, **vargs)

class ConsolePageUp(ConsoleBase): subcls = method.move.PageUp
class ConsolePageDown(ConsoleBase): subcls = method.move.PageDown
class ConsoleGotoBeginning(ConsoleBase): subcls = method.move.GotoBeginning
class ConsoleGotoEnd(ConsoleBase): subcls = method.move.GotoEnd

class ConsoleMini(mode.Fundamental):
    name    = 'ConsoleMini'
    grammar = PythonGrammar
    actions = [ConsoleExec, ConsoleClear, ConsoleCancel, ConsoleHistoryPrev,
               ConsoleHistoryNext, ConsoleTab,
               ConsolePageUp, ConsolePageDown, ConsoleGotoBeginning,
               ConsoleGotoEnd]
    _bindings = {
        'console-exec':           ('RETURN',),
        'console-clear':          ('C-l',),
        'console-cancel':         ('C-]', 'C-g'),
        'console-history-prev':   ('C-p', 'UP'),
        'console-history-next':   ('C-n', 'DOWN'),
        'console-tab':            ('TAB',),
        'console-page-up':        ('M-v',),
        'console-page-down':      ('C-v',),
        'console-goto-beginning': ('M-<',),
        'console-goto-end':       ('M->',),
    }
    def __init__(self, w):
        mode.Fundamental.__init__(self, w)
        self.globals     = dict(w.application.globals())
        self.locals      = dict(w.application.locals())
        self.saved_input = ""
        self.history     = ['']
        self.hindex      = 0

install = ConsoleMini.install