import re, string, struct
from subprocess import Popen, PIPE, STDOUT
import color
from mode import Fundamental
from lex import Grammar, PatternRule, RegionRule
from method import Method, Argument
from method.move import GotoBeginning, GotoEnd
from point import Point
from render import RenderString

class HexSetByteOrder(Method):
    '''Sets the byte-order to use to 'little', 'big', or 'native' order'''
    args = [Argument("byteorder", type=type(''), prompt="Byte order: ")]
    def _execute(self, w, **vargs):
        order = vargs['byteorder']
        if order in w.mode.byteorders:
            w.mode.byteorder = order
            w.set_error("byte-order set to %r" % w.mode.byteorder)
        else:
            w.set_error("invalid ordering %r (use 'little', 'big', or 'native'")

class HexForward(Method):
    def _execute(self, w, **vargs):
        end = w.buffer.get_buffer_end()
        if w.cursor >= end.add(-2, 0):
            return
        w.forward()
        if w.mode.symbolic_edit:
            w.forward()
        while w.cursor_char().isspace() and w.cursor < end:
            w.forward()
class HexBackward(Method):
    def _execute(self, w, **vargs):
        w.backward()
        start = w.buffer.get_buffer_start()
        while w.cursor_char().isspace() and w.cursor > start:
            w.backward()
        if w.mode.symbolic_edit:
            w.backward()

class HexForwardWord(Method):
    def _execute(self, w, **vargs):
        hf = w.application.methods['hex-forward']
        for i in range(0, w.buffer.wordsize * 2):
            hf.execute(w, **vargs)
class HexBackwardWord(Method):
    def _execute(self, w, **vargs):
        hb = w.application.methods['hex-backward']
        for i in range(0, w.buffer.wordsize * 2):
            hb.execute(w, **vargs)

class HexStartOfLine(Method):
    '''Move the cursor to the start of the current hex line'''
    def _execute(self, w, **vargs):
        w.start_of_line()
class HexEndOfLine(Method):
    '''Move the cursor to the end of the current hex line'''
    def _execute(self, w, **vargs):
        w.end_of_line()
        if w.cursor_char() == '\n':
            w.backward()

class HexGotoBeginning(GotoBeginning):
    '''Move the cursor to the start of the current hex line'''
class HexGotoEnd(GotoEnd):
    '''Move the cursor to the end of the current hex line'''
    def _execute(self, w, **vargs):
        w.application.methods['goto-end'].execute(w)
        w.application.methods['hex-backward'].execute(w)

class HexRead(Method):
    _is_method = False
    def __init__(self, type_, fmt):
        self.name  = 'hex-read-%s' % type_.lower()
        self.type_ = type_
        self.fmt   = fmt
        self.size  = struct.calcsize(fmt)
        self.args  = []
        self.help  = "Read %r from address in the current buffer." % self.type_
    def _get_ix(self, b, cy, cx):
        ix = b.cursorx_to_datax(cy, cx)
        return ix
    def _execute(self, w, **vargs):
        b  = w.buffer
        hb = w.application.methods['hex-backward']
        (cx, cy) = w.cursor.xy()
        ix = self._get_ix(b, cy, cx)
        addr = b.get_address(cy, 0) + ix
        try:
            v = w.mode.read_struct(cy, ix, self.fmt, self.size)
            if v is None:
                w.set_error("not enough data to read %s" % self.type_)
            else:
                end = '%s-endian' % w.mode.byteorder
                w.set_error("%s %s at 0x%08x: %r" % (end, self.type_, addr, v))
        except Exception, e:
            w.set_error("%s could not be read at 0x%08x" % (self.type_, addr))
class HexReadAligned(HexRead):
    _is_method = False
    def __init__(self, type_, fmt):
        self.name  = 'hex-read-aligned-%s' % type_.lower()
        self.type_ = type_
        self.fmt   = fmt
        self.size  = struct.calcsize(fmt)
        self.args  = []
        self.help  = "Read %r from word-aligned address in the current buffer." % self.type_
    def _get_ix(self, b, cy, cx):
        ix = b.cursorx_to_datax(cy, cx)
        ix -= ix % b.wordsize
        return ix

class HexOverwriteChar(Method):
    _is_method = False
    def __init__(self, c):
        self.name = 'hex-overwrite-char-%s' % c
        self.args = []
        self.help = "Overwrite %r into the current hex buffer." % c
        self.char = c
        self.part1, self.part2 = '%02x' % ord(self.char)
    def _execute(self, w, **vargs):
        if w.mode.symbolic_edit:
            b  = w.buffer
            hb = w.application.methods['hex-backward']
            (cx, cy) = w.cursor.xy()
            ix = b.cursorx_to_datax(cy, cx)
            if ix is None:
                return
            cx2 = w.buffer.datax_to_cursorx(ix)
            w.goto(Point(cx2, cy))
            w.overwrite_char_at_cursor(self.part1)
            w.overwrite_char_at_cursor(self.part2)
        elif self.char not in string.hexdigits:
            return
        else:
            w.overwrite_char_at_cursor(self.char)
        end = w.buffer.get_buffer_end()
        while w.cursor_char().isspace() and w.cursor < end:
            w.forward()
class HexOverwriteCharSpace(HexOverwriteChar):
    def __init__(self):
        self.name = 'hex-overwrite-char-space'
        self.args = []
        self.help = "Overwrite SPACE into the current hex buffer."
        self.char = ' '
        self.part1, self.part2 = '%02x' % ord(self.char)
class HexOverwriteCharTab(HexOverwriteChar):
    def __init__(self):
        self.name = 'hex-overwrite-char-tab'
        self.args = []
        self.help = "Overwrite TAB into the current hex buffer."
        self.char = '\t'
        self.part1, self.part2 = '%02x' % ord(self.char)
class HexOverwriteCharNewline(HexOverwriteChar):
    def __init__(self):
        self.name = 'hex-overwrite-char-newline'
        self.args = []
        self.help = "Overwrite NEWLINE into the current hex buffer."
        self.char = '\n'
        self.part1, self.part2 = '%02x' % ord(self.char)

class HexToggleSymbolic(Method):
    def _execute(self, w, **vargs):
        w.mode.symbolic_edit = not w.mode.symbolic_edit
        if w.mode.symbolic_edit:
            w.set_error("Symbolic editing enabled")
        else:
            w.set_error("Symbolic editing disabled")
class HexLiteral(Method):
    def _execute(self, w, **vargs):
        w.mode.symbolic_edit = False
        w.set_error("Symbolic editing disabled")
class HexSymbolic(Method):
    def _execute(self, w, **vargs):
        w.mode.symbolic_edit = True
        w.set_error("Symbolic editing enabled")

class ShowX86Instruction(Method):
    ''''''
    size_re = re.compile(r'X86 insn \((\d+) bytes\):')
    def _execute(self, w, **vargs):
        disinst = w.application.config.get('hex.disinst')
        (cx, cy) = w.cursor.xy()
        ix = w.buffer.cursorx_to_datax(cy, cx)
        data = w.mode.read_data(cy, ix, 13)
        data = ''.join(['%02x' % ord(c) for c in data])
        try:
            p      = Popen((disinst, data), stdout=PIPE, stderr=STDOUT)
            lines  = [l.strip() for l in p.stdout.readlines()]
            result = p.wait()
            m = self.size_re.match(lines[0])
            assert m
            size = int(m.group(1))
            data = data[:size]
            w.set_error("%s %s" % (data, lines[1]))
        except Exception, e:
            w.set_error("there was an error")

class GotoAddress(Method):
    '''Jump to the specified line number'''
    args = [Argument("address", type=type(0), prompt="Goto address: ")]
    def _execute(self, w, **vargs):
        b = w.buffer
        addr = vargs["address"]
        if addr < 0:
            w.set_error("Negative address not supported.")
        ix = addr % (b.groupsize * b.numgroups)
        cx = w.buffer.datax_to_cursorx(ix)
        cy = addr // (b.groupsize * b.numgroups)
        w.goto(Point(cx, cy))
        w.set_error("Goto 0x%08x (%r, %r)" % (addr, cx, cy))

class ShowAddress(Method):
    '''Show the cursor's address in the current buffer'''
    def _execute(self, w, **vargs):
        (cx, cy) = w.cursor.xy()
        ix = w.buffer.cursorx_to_datax(cy, cx)
        addr = w.buffer.get_address(cy, ix)
        w.set_error("Cursor's address is 0x%08x" % addr)

class Hex(Fundamental):
    name   = 'Hex'
    config = {
        'hex.disinst': 'disinst',
    }
    #lmargin    = 12
    #rmargin    = 18
    _ctrans    = ['.'] * 256
    byteorder  = 'native'
    byteorders = {
        'native': '=',
        'little': '<',
        'big':    '>',
    }
    cgreen   = color.build('green', 'default', 'bold')
    ccyan    = color.build('cyan', 'default', 'bold')
    ccursor  = color.build('default', 'default', 'bold', 'reverse')
    for c in string.letters + string.digits + string.punctuation + ' ':
        _ctrans[ord(c)] = c
    ctrans = ''.join(_ctrans)
    actions = [HexForward, HexBackward, HexForwardWord, HexBackwardWord,
               HexStartOfLine, HexEndOfLine, ShowAddress, ShowX86Instruction,
               GotoAddress, HexSetByteOrder, HexToggleSymbolic, HexSymbolic, HexLiteral,
               HexOverwriteCharSpace, HexOverwriteCharTab,
               HexOverwriteCharNewline, HexGotoBeginning, HexGotoEnd]

    #header      = 1
    #header_size = 1
    hlo = color.build('red', 'default')
    hhi = color.build('default', 'red')
    format = "%(flag)s  %(bname)-18s  (%(mname)s)  {%(symbolic)s}  %(cursor)s  %(perc)s"

    def get_status_names(self):
        names = Fundamental.get_status_names(self)
        if self.symbolic_edit:
            names['symbolic'] = 'symbolic'
        else:
            names['symbolic'] = 'literal'
        return names

    def get_header(self):
        s0 = '  87654321  '
        s1 = '00 11 22 33 44 55 66 77  88 99 aa bb cc dd ee ff'
        s2 = ' ' * (self.window.width - 48)
        s3 = '0123456789abcdef  '

        def lo(s): return RenderString(s=s, attrs=self.hlo)
        def hi(s): return RenderString(s=s, attrs=self.hhi)

        if self.symbolic_edit:
            return [[lo(s0), lo(s1), lo(s2), hi(s3)]]
        else:
            return [[lo(s0), hi(s1), lo(s2), lo(s3)]]

    def __init__(self, w):
        Fundamental.__init__(self, w)
        self.bindings = {}

        self.lmargin = 12
        self.rmargin = 18
        self.header = 1
        self.footer = 0

        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('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('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('save-buffer', ('C-x C-s',))
        self.add_bindings('save-buffer-as', ('C-x C-w',))
        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('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-]', 'C-g'))
        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('get-token', ('C-c t',))

        self.add_bindings('hex-forward', ('C-f', 'R_ARROW',))
        self.add_bindings('hex-backward', ('C-b', 'L_ARROW',))
        self.add_bindings('hex-forward-word', ('M-f', 'M-R_ARROW',))
        self.add_bindings('hex-backward-word', ('M-b', 'M-L_ARROW',))
        self.add_bindings('hex-start-of-line', ('C-a', 'HOME',))
        self.add_bindings('hex-end-of-line', ('C-e', 'END',))
        self.add_bindings('hex-goto-beginning', ('M-<',))
        self.add_bindings('hex-goto-end', ('M->',))

        self.add_action_and_bindings(HexRead('char', 'b'), ('C-c b',))
        self.add_action_and_bindings(HexRead('uchar', 'B'), ('C-c B',))
        self.add_action_and_bindings(HexRead('short', 'h'), ('C-c h',))
        self.add_action_and_bindings(HexRead('ushort', 'H'), ('C-c H',))
        self.add_action_and_bindings(HexRead('int', 'i'), ('C-c i',))
        self.add_action_and_bindings(HexRead('uint', 'I'), ('C-c I',))
        self.add_action_and_bindings(HexRead('long', 'l'), ('C-c l',))
        self.add_action_and_bindings(HexRead('ulong', 'L'), ('C-c L',))
        self.add_action_and_bindings(HexRead('float', 'f'), ('C-c f',))
        self.add_action_and_bindings(HexRead('double', 'd'), ('C-c d',))

        self.add_action_and_bindings(HexReadAligned('char', 'b'), ('C-u b',))
        self.add_action_and_bindings(HexReadAligned('uchar', 'B'), ('C-u B',))
        self.add_action_and_bindings(HexReadAligned('short', 'h'), ('C-u h',))
        self.add_action_and_bindings(HexReadAligned('ushort', 'H'), ('C-u H',))
        self.add_action_and_bindings(HexReadAligned('int', 'i'), ('C-u i',))
        self.add_action_and_bindings(HexReadAligned('uint', 'I'), ('C-u I',))
        self.add_action_and_bindings(HexReadAligned('long', 'l'), ('C-u l',))
        self.add_action_and_bindings(HexReadAligned('ulong', 'L'), ('C-u L',))
        self.add_action_and_bindings(HexReadAligned('float', 'f'), ('C-u f',))
        self.add_action_and_bindings(HexReadAligned('double', 'd'), ('C-u d',))

        self.add_bindings('show-address', ('C-c a',))
        self.add_bindings('show-x86-instruction', ('C-c x',))
        self.add_bindings('goto-address', ('C-c M-g',))

        # create all the insert actions for the basic text input
        for c in string.letters + string.digits + string.punctuation:
            self.add_action_and_bindings(HexOverwriteChar(c), (c,))
        self.add_bindings('hex-overwrite-char-space', ('SPACE',))
        self.add_bindings('hex-overwrite-char-tab', ('TAB',))
        self.add_bindings('hex-overwrite-char-newline', ('RETURN',))

        self.symbolic_edit = True

    def get_address(self, y, x):
        return (y * 16) + x

    def get_lmargin(self, w, y, x, ended=False, cont=False):
        lm = self.lmargin
        if ended:
            s = '  --------  '
        else:
            addr = self.get_address(y, x)
            s = '0x%08x  ' % addr
        return (RenderString(s, attrs=self.ccyan),)

    def get_rmargin(self, w, y, x, ended=False, cont=False):
        if ended:
            return tuple()
        else:
            (cx, cy) = self.window.cursor.xy()
            s = string.translate(self.window.buffer.rawdata[y], self.ctrans)
            if cy == y:
                i = self.window.buffer.cursorx_to_datax(cy, cx)
                
                if i is None:
                    rmargins = (RenderString(s, attrs=self.cgreen),)
                elif i < len(s):
                    rmargins = (RenderString(s[0:i], attrs=self.cgreen),
                                RenderString(s[i], x=i, attrs=self.ccursor),
                                RenderString(s[i+1:], x=i + 1, attrs=self.cgreen))
                else:
                    rmargins= (RenderString(s[0:i], attrs=self.cgreen),
                               RenderString(s[i], x=i, attrs=self.cgreen))
                return rmargins
            else:
                return (RenderString(s, attrs=self.cgreen),)

    def read_data(self, cy, ix, size):
        b  = self.window.buffer
        s = b.rawdata[cy][ix:]
        if len(s) < size:
            if cy < len(b.rawdata) - 1:
                s += b.rawdata[cy + 1]
        s = s[:size]
        return s
    def read_struct(self, cy, ix, fmt, size):
        s = self.read_data(cy, ix, size)
        fmt = '%s%s' % (self.byteorders[self.byteorder], fmt)
        return struct.unpack(fmt, s)[0]

install = Hex.install