import fcntl, os, select, pty, threading, time

from buffer import Buffer, ACT_NORM, ACT_NONE
from term import XTerm
from point import Point

# evil evil evil evil evil
class XTermBuffer(Buffer, XTerm):
    btype    = 'term'
    modename = 'pipe'
    termtype = 'xterm'
    delay    = 0.1
    bufsize  = 8192
    def __init__(self, app, cmd, args, name=None, modename=None):
        if modename:
            self.modename = modename
        XTerm.__init__(self)
        Buffer.__init__(self)

        self.application = app
        self._name = name or '*XTerm*'
        self._pid, self._pty = pty.fork()
        if self._pid == 0:
            # child process
            env = {
                'TERM': self.termtype,
                'PATH': os.getenv('PATH'),
                'USER': os.getenv('USER'),
                'HOME': os.getenv('HOME'),
            }
            os.execvpe(cmd, [cmd] + args, env)

        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 _w(self):
        return self.windows[0]
    def _get_height(self):
        return self._w().height
    def _get_width(self):
        return self._w().width
    # TERM STUFF
    def _term_insert(self, s):
        w = self._w()
        p = w.logical_cursor()
        if p.x == len(self.lines[p.y]):
            self.insert_string(p, s, act=ACT_NONE, force=True)
        else:
            self.overwrite_char(p, s, act=ACT_NONE, force=True)
    def term_do_newline(self):
        XTerm.term_do_newline(self)
        #p = w.logical_cursor()
        #self.insert_string(p, "XYZ", act=ACT_NONE, force=True)
    def term_do_style(self):
        pass
    def term_do_clear(self):
        self.set_lines([''], force=True)
    def term_do_home(self):
        w = self._w()
        w.cursor = w.first
    def term_do_clear_bol(self):
        w = self._w()
        p2 = w.logical_cursor()
        p1 = Point(0, p2.y)
        self.delete(p1, p2, force=True)
    def term_do_clear_eol(self):
        w = self._w()
        p1 = w.logical_cursor()
        p2 = Point(len(self.lines[p1.y]), p1.y)
        self.delete(p1, p2, force=True)
    def term_do_clear_eos(self):
        w = self._w()
        p1 = w.logical_cursor()
        p2 = self.get_buffer_end()
        self.delete(p1, p2, force=True)

    def term_do_backspace(self):
        self._w().backward()
    def term_do_tab(self):
        self._term_insert('    ')
    def term_do_newline(self):
        w = self._w()
        p = w.logical_cursor()
        if p.y < len(self.lines) - 1:
            w.start_of_line()
            w.next_line()
        else:
            w.end_of_line()
            self.insert_string(w.logical_cursor(), "\n", act=ACT_NONE, force=True)
    def term_do_creturn(self):
        self._w().start_of_line()
    def term_do_delete(self):
        self._w().delete_right()
    def term_handle_print(self, c):
        self._term_insert(c)
    def term_handle_ctl(self, c):
        n = ord(c)
        if n == 7:
            #bell
            pass
        elif n == 8:
            self.term_do_backspace()
        elif n == 9:
            self.term_do_tab()
        elif n == 10:
            self.term_do_newline()
        elif n == 13:
            self.term_do_creturn()
        elif n == 27:
            self.term_do_esc(c)
        elif n == 127:
            self.term_do_delete()
        else:
            self._term_insert('%03o' % n)

    def term_receive(self, s):
        for c in s:
            self.term_handle(c)

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

    def pipe_read(self):
        fd = self._pty

        pid, status = os.waitpid(self._pid, os.WNOHANG)
        if pid:
            return

        # wait until we are hooked up and ready to go
        while not self.windows:
            time.sleep(0.1)
        self.application.need_draw = True

        # ok, so start reading stuff!
        try:
            while not self._done:
                pid, status = os.waitpid(self._pid, os.WNOHANG)
                if pid:
                    break
                if self._towrite:
                    ifd, ofd, efd = select.select([fd], [fd], [fd], self.delay)
                else:
                    ifd, ofd, efd = select.select([fd], [], [fd], self.delay)
                if ifd:
                    data = os.read(ifd[0], self.bufsize)
                    self.term_receive(data)
                    self.application.need_draw = True
                if ofd:
                    self._lock.acquire()
                    n = os.write(ofd[0], self._towrite)
                    self._towrite = self._towrite[n:]
                    self._lock.release()
                if efd:
                    self.term_receive(repr(efd))
                    #raise Exception, "exception is ready: %s" % repr(efd)
                    pass
        except OSError:
            pass
        except TypeError:
            raise
        except AttributeError:
            raise
        if not pid:
            pid, status = os.waitpid(self._pid, os.WNOHANG)
        s = '\nprocess %d exited (%d)\n' % (pid, status >> 8)
        self._term_insert(s)
        self.application.need_draw = True
        os.close(fd)

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

    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"
    def close(self):
        self._done = True
        self._thread.join()