import math, os, string
import color, method
from lex import Lexer
from point import Point
from render import RenderString

DEBUG = False

class ActionError(Exception):
    pass

class Handler(object):
    def __init__(self):
        self.prefixes      = set(["C-x", "C-c", "C-u"])
        self.last_sequence = ''
        self.curr_tokens   = []
        self.bindings      = {}

    # handle adding and removing actions
    def add_action(self, action):
        if self.window is None:
            return
        elif action.name in self.window.application.methods:
            return
        else:
            self.window.application.methods[action.name] = action
    def del_action(self, name):
        if self.window is None:
            return
        for binding in self.bindings.keys():
            if self.bindings[binding] == name:
                del self.bindings[binding]
    def add_binding(self, name, sequence):
        if self.window is None:
            return
        elif not hasattr(self.window, 'application'):
            raise Exception, "argh %r %r" % (self, self.window)
        elif name not in self.window.application.methods:
            raise Exception, "No action called %r found" % name
        else:
            self.bindings[sequence] = name
    def add_bindings(self, name, sequences):
        if self.window is None:
            return
        for sequence in sequences:
            self.add_binding(name, sequence)
    def del_binding(self, sequence):
        if self.window is None:
            return
        del self.bindings[sequence]
    def add_action_and_bindings(self, action, sequences):
        if self.window is None:
            return
        self.add_action(action)
        for sequence in sequences:
            self.add_binding(action.name, sequence)
        
    def handle_token(self, t):
        '''self.handle_token(token): returns None, or the action to
        take. raises an exception on unknown input'''
        self.curr_tokens.append(t)
        sequence = " ".join(self.curr_tokens)
        if sequence in self.bindings:
            act = self.window.application.methods[self.bindings[sequence]]
            self.last_sequence = sequence
            self.curr_tokens = []
            return act
        elif t in self.prefixes:
            for binding in self.bindings:
                if binding.startswith(sequence):
                    return None
        self.curr_tokens = []
        self.last_sequence = sequence
        raise ActionError("no action defined for %r" % (sequence))

class Fundamental(Handler):
    '''This is the default mode'''
    modename   = "Fundamental"
    paths      = []
    basenames  = []
    extensions = []
    detection  = []
    savetabs   = False
    tabwidth   = 4
    tabbercls  = None
    grammar    = None
    lexer      = None
    tabber     = None
    context    = None
    colors     = {}
    config     = {}
    actions    = []
    completers = {}
    format     = "%(flag)s  %(bname)-18s  (%(mname)s)  %(cursor)s/%(mark)s  %(perc)s"

    # margin/line numbering
    show_line_numbers = False
    lmargin = 0
    rmargin = 1

    def install(cls, app):
        app.setmode(cls.modename.lower(), cls, paths=cls.paths,
                    basenames=cls.basenames, extensions=cls.extensions,
                    detection=cls.detection)
        for (key, val) in cls.colors.iteritems():
            if key in app.token_colors:
                s = 'token_colors: name %r conflict in %r' % (key, cls.modename)
                raise Exception, s
            else:
                app.token_colors[key] = val
        for (key, val) in cls.config.iteritems():
            app.config[key] = val
        for mcls in cls.actions:
            m = mcls()
            if m.name in app.methods:
                return
            else:
                app.methods[m.name] = m
        for (datatype, completer) in cls.completers.iteritems():
            app.set_completer(datatype, completer)
    install = classmethod(install)

    def __init__(self, w):
        assert w is not None
        self.window = w
        Handler.__init__(self)

        # first let's add all the "default" actions
        self.add_bindings('start-of-line', ('C-a', 'HOME',))
        self.add_bindings('end-of-line', ('C-e', 'END',))
        self.add_bindings('backward', ('C-b', 'L_ARROW',))
        self.add_bindings('forward', ('C-f', 'R_ARROW',))
        self.add_bindings('center-view', ('C-l',))
        self.add_bindings('next-line', ('C-n', 'D_ARROW',))
        self.add_bindings('previous-line', ('C-p', 'U_ARROW',))
        self.add_bindings('next-section', ('M-n', 'M-D_ARROW',))
        self.add_bindings('previous-section', ('M-p', 'M-U_ARROW',))
        self.add_bindings('page-down', ('C-v', 'PG_DN',))
        self.add_bindings('page-up', ('M-v', 'PG_UP',))
        self.add_bindings('goto-beginning', ('M-<',))
        self.add_bindings('goto-end', ('M->',))
        self.add_bindings('delete-left', ('DELETE', 'BACKSPACE',))
        self.add_bindings('delete-left-word', ('M-DELETE', 'M-BACKSPACE',))
        self.add_bindings('delete-right', ('C-d',))
        self.add_bindings('delete-right-word', ('M-d',))
        self.add_bindings('kill-region', ('C-w',))
        self.add_bindings('copy-region', ('M-w',))
        self.add_bindings('kill', ('C-k',))
        self.add_bindings('copy', ('M-k',))
        self.add_bindings('yank', ('C-y',))
        self.add_bindings('pop-kill', ('M-y',))
        self.add_bindings('right-word', ('M-f',))
        self.add_bindings('left-word', ('M-b',))
        self.add_bindings('set-mark', ('C-@',))
        self.add_bindings('switch-buffer', ('C-x b',))
        self.add_bindings('switch-mark', ('C-x C-x',))
        self.add_bindings('undo', ('C-/', 'C-x u',))
        self.add_bindings('redo', ('M-/', 'M-_', 'C-x r',))
        self.add_bindings('goto-line', ('M-g',))
        self.add_bindings('forward-chars', ('C-x M-c',))
        self.add_bindings('forward-lines', ('C-x M-n',))
        self.add_bindings('search', ('C-s',))
        self.add_bindings('reverse-search', ('C-r',))
        self.add_bindings('regex-search', ('M-C-s',))
        self.add_bindings('regex-reverse-search', ('M-C-r',))
        self.add_bindings('toggle-margins', ('M-m',))
        self.add_bindings('replace', ('M-%',))
        self.add_bindings('regex-replace', ('M-$',))
        self.add_bindings('open-file', ('C-x C-f',))
        self.add_bindings('kill-buffer', ('C-x k',))
        self.add_bindings('list-buffers', ('C-x C-b',))
        self.add_bindings('meta-x', ('M-x',))
        self.add_bindings('wrap-line', ('M-q',))
        self.add_bindings('transpose-words', ('M-t',))
        self.add_bindings('save-buffer', ('C-x C-s',))
        self.add_bindings('save-buffer-as', ('C-x C-w',))
        self.add_bindings('relex-buffer', ('M-r',))
        self.add_bindings('exit', ('C-x C-c',))
        self.add_bindings('split-window', ('C-x s', 'C-x 2',))
        self.add_bindings('unsplit-window', ('C-u s', 'C-x 1',))
        self.add_bindings('toggle-window', ('C-x o',))
        self.add_bindings('delete-left-whitespace', ('C-c DELETE', 'C-c BACKSPACE',))
        self.add_bindings('delete-right-whitespace', ('C-c d',))
        self.add_bindings('insert-space', ('SPACE',))
        self.add_bindings('insert-tab', ('TAB',))
        self.add_bindings('insert-newline', ('RETURN',))
        self.add_bindings('comment-region', ('C-c #',))
        self.add_bindings('uncomment-region', ('C-u C-c #',))
        self.add_bindings('justify-right', ('C-c f',))
        self.add_bindings('justify-left', ('C-c b',))
        self.add_bindings('indent-block', ('C-c >',))
        self.add_bindings('unindent-block', ('C-c <',))
        self.add_bindings('token-complete', ('M-c', 'C-c c', 'C-c TAB',))
        self.add_bindings('open-aes-file', ('C-c a',))
        self.add_bindings('open-console', ('M-e',))
        self.add_bindings('show-bindings-buffer', ('C-c M-h',))
        self.add_bindings('show-functions-buffer', ('C-c M-?',))
        self.add_bindings('which-command', ('M-h',))
        self.add_bindings('cmd-help-buffer', ('M-?',))
        self.add_bindings('set-mode', ('C-x m',))
        self.add_bindings('cancel', ('C-]',))
        self.add_bindings('exec', ('C-c e', 'C-c !'))
        self.add_bindings('grep', ('C-c g',))
        self.add_bindings('pipe', ('C-c p', 'C-c |'))
        self.add_bindings('view-buffer-parent', ('C-c .',))
        self.add_bindings('insert-squotes', ('M-\'',))
        self.add_bindings('insert-dquotes', ('M-"',))
        self.add_bindings('get-token', ('C-c t',))
        self.add_bindings('insert-text', ('C-c i',))
        self.add_bindings('insert-text2', ('C-c M-i',))
        self.add_bindings('insert-multiline-text', ('C-c m',))

        # create all the insert actions for the basic text input
        for c in string.letters + string.digits + string.punctuation:
            self.add_binding('insert-string-%s' % c, c)

        # lexing for highlighting, etc.
        if self.grammar:
            self.lexer = Lexer(self, self.grammar)
        else:
            self.lexer = None
        self.gstack = {}
        self.ghist  = {}

        # tab handling
        if self.tabbercls:
            self.tabber = self.tabbercls(self)

    def enable_line_numbers(self):
        self.show_line_numbers = True
        l = len(self.window.buffer.lines)
        self.lmargin = int(math.log(l, 10)) + 3
    def disable_line_numbers(self):
        self.show_line_numbers = False
        self.lmargin = 0

    def get_rmargin(self, w, y, x, ended=False, cont=False):
        c, fg, bg = " ", "red", "default"
        if cont:
            c = "\\"
        if w.hiddenindicator(y):
            bg = "green"
        return [RenderString(s=c, attrs=color.build(fg, bg))]
    def get_lmargin(self, w, y, x, ended=False, cont=False):
        lm = self.lmargin
        if ended:
            i = int(math.log(y, 10)) + 1
            s = ('% *s' % (lm - 1, '-' * i))[-lm:] + ' '
        elif not cont:
            s = ('% *d' % (lm - 1, y + 1))[-lm:] + ' '
        else:
            s = ' ' * lm
        return [RenderString(s=s, attrs=color.build('default', 'default', 'bold'))]

    # get mode name
    def name(self):
        return self.modename

    def _get_flag(self):
        b = self.window.buffer
        if b.readonly():
            if b.changed():
                return '%*'
            else:
                return '%%'
        else:
            if b.changed():
                return '**'
            else:
                return '--'
    def _get_perc(self):
        w = self.window
        if w.first_is_visible():
            return "Top"
        elif w.last_is_visible():
            return "Bot"
        else:
            return "%2d%%" % (w.first.y*100 / len(w.buffer.lines))
    def _get_mark(self):
        w = self.window
        if w.mark:
            return '(%d,%d)' % (w.mark.y + 1, w.mark.x + 1)
        else:
            return '(None)'
    def get_status_names(self):
        w = self.window
        c = w.logical_cursor()
        return {
            'bname':  w.buffer.name(),
            'mname':  self.name(),
            'flag':   self._get_flag(),
            'perc':   self._get_perc(),
            'cursor': '(%d,%d)' % (c.y + 1, c.x + 1),
            'mark':   self._get_mark(),
        }
    def get_status_bar(self):
        names = self.get_status_names()
        return self.format % names

    # handle input tokens
    def handle_token(self, t):
        '''self.handle_token(token): handles input "token"'''
        self.window.active_point = None
        self.window.clear_error()
        try:
            act = Handler.handle_token(self, t)
            if act is None:
                self.window.set_error(' '.join(self.curr_tokens))
                return
            else:
                act.execute(self.window)
                self.window.application.last_action = act.name
        except ActionError, e:
            if t != 'C-]':
                self.window.set_error(str(e))
            else:
                self.window.set_error('Cancelled')
        except Exception, e:
            if DEBUG:
                raise
            else:
                err = "%s in mode '%s'" % (e, self.name())
                self.window.set_error(err)

    def region_added(self, p, newlines):
        if self.lexer is not None:
            ydelta = len(newlines) - 1
            xdelta = len(newlines[-1])
            ghist = {}
            for name in self.ghist:
                for gp in self.ghist[name]:
                    if gp < p:
                        newp = gp
                    elif ydelta == 0:
                        if p.y == gp.y:
                            newp = Point(gp.x + xdelta, gp.y)
                        else:
                            newp = gp
                    else:
                        if gp.y == p.y:
                            newp = Point(gp.x + xdelta, gp.y + ydelta)
                        else:
                            newp = Point(gp.x, gp.y + ydelta)
                    ghist.setdefault(name, {})
                    ghist[name][newp] = self.ghist[name][gp]
            self.ghist = ghist
        if self.tabber is not None:
            self.tabber.region_added(p, newlines)
        if self.context is not None:
            self.context.region_added(p, newlines)

    def region_removed(self, p1, p2):
        if self.lexer is not None:
            ydelta = p2.y - p1.y
            xdelta = p2.x - p1.x
            ghist = {}
            for name in self.ghist:
                for gp in self.ghist[name]:
                    if gp < p1:
                        newp = gp
                    elif p1 <= gp and gp < p2:
                        continue
                    elif ydelta == 0:
                        if gp.y == p2.y:
                            newp = Point(gp.x - xdelta, gp.y)
                        else:
                            newp = gp
                    else:
                        if gp.y == p2.y:
                            newp = Point(gp.x - xdelta, gp.y - ydelta)
                        else:
                            newp = Point(gp.x, gp.y - ydelta)
                    ghist.setdefault(name, {})
                    ghist[name][newp] = self.ghist[name][gp]
            self.ghist = ghist
        if self.tabber is not None:
            self.tabber.region_removed(p1, p2)
        if self.context is not None:
            self.context.region_removed(p1, p2)

install = Fundamental.install