import fcntl, os, select, pty, threading

from buffer import Buffer, ACT_NORM, ACT_NONE
import term

class PipeBuffer(Buffer):
    btype = 'pipe'
    terms = {
        'dumb':  term.Dumb,
        'xterm': term.XTerm,
    }
    modename = 'pipe'
    def __init__(self, cmd, args, name=None, term='dumb'):
        Buffer.__init__(self)
        self.cmd = cmd
        if name:
            self._name = name
        else:
            self._name = '*Pipe*'

        self.term     = self.terms[term]()
        self._pid, self._pty = pty.fork()
        if self._pid == 0:
            os.execve(cmd, [cmd] + args, {'TERM': self.term.name})

        self._lock    = threading.Lock()
        self._towrite = ''
        self._done    = False
        self._set_nonblock(self._pty)
        self._thread = threading.Thread(target=self.pipe_read)
        self._thread.setDaemon(True)
        self._thread.start()

    def _set_nonblock(self, fd):
        flags = fcntl.fcntl(fd, fcntl.F_GETFL)
        fcntl.fcntl(fd, fcntl.F_SETFL, flags | os.O_NDELAY)

    def _filter_output(self, output):
        output2 = []
        i = 0
        escaped = []
        for c in output:
            if escaped:
                escaped.append(c)
                if c == 'm':
                    escaped = []
            elif c == '\x1b':
                escaped.append(c)
            elif c == '\n':
                output2.append(c)
                i = 0
            elif c == '\t':
                j = i % 8
                output2.append(' ' * (8 - j))
                i += 8 - j
            elif c == '\a':
                pass
            elif c == '\b':
                if i > 0:
                    output2.pop(-1)
                    i -= 1
            else:
                output2.append(c)
                i += 1
        return ''.join(output2)

    def pipe_read(self):
        fd = self._pty
        try:
            while not self._done:
                if self._towrite:
                    ifd, ofd, efd = select.select([fd], [fd], [fd], 0.1)
                else:
                    ifd, ofd, efd = select.select([fd], [], [fd], 0.1)
                if ifd:
                    data = os.read(ifd[0], 1024)
                    end = self.get_buffer_end()
                    data = self.term.term_filter(data)
                    self.insert_string(end, data, force=True, act=ACT_NONE)
                if ofd:
                    self._lock.acquire()
                    n = os.write(ofd[0], self._towrite)
                    self._towrite = self._towrite[n:]
                    self._lock.release()
                if efd:
                    raise Exception, "exception is ready: %s" % repr(efd)
        except (OSError, TypeError):
            pass
        os.close(fd)

    def pipe_write(self, s):
        self._lock.acquire()
        self._towrite += s
        self._lock.release()

    def insert_string(self, p, s, act=ACT_NORM, force=False):
        lines = s.split("\n")
        self.insert_lines(p, lines, act, force)

    def insert_lines(self, p, lines, act=ACT_NORM, force=False):
        llen = len(lines)
        assert llen > 0
        if not force and self.readonly():
            raise ReadOnlyError("buffer is read-only")
        p2 = p.vadd(len(lines[-1]), llen - 1)
        if llen > 1:
            self.lines.insert(p.y + 1, [])
            self.lines[p.y + 1] = lines[-1] + self.lines[p.y][p.x:]
            self.lines[p.y] = self.lines[p.y][:p.x] + lines[0]
            for i in xrange(1, llen - 1):
                self.lines.insert(p.y + i, lines[i])
        else:
            self.lines[p.y] = self.lines[p.y][:p.x] + lines[-1] + self.lines[p.y][p.x:]
        self._region_add(p, p2, lines, act)
        self.modified = True
        
    def name(self): return self._name
    def changed(self): return False
    def readonly(self): return True
    def undo(self, move, act): raise Exception, "invalid"
    def redo(self, move, act): raise Exception, "invalid"
    def reload(self): raise Exception, "invalid"