#!/usr/bin/env python

import os
import sys
import time
import signal

def usage(retval):
    sys.stderr.write('Usage: %s --filename replay_filename --help --turn-off-bell\n' % \
        sys.argv[0])
    sys.stderr.write('\t--add-carriage-returns --guess-carriage-returns --gtk --curses\n')
    sys.stderr.write('\n--gtk is the default.  --curses is the other option.\n')
    sys.stderr.write('\n--curses implies --add-carriage-returns\n')
    sys.stderr.write('\nPlease note that --turn-off-bell can break some xterm escape sequences,\n')
    sys.stderr.write('making some lines seem not to display properly.  The "set xterm title"\n')
    sys.stderr.write('function is known to have this problem.  Others may as well.\n')
    sys.exit(retval)

replay_filename='./typescript'
bell = 1
add_cr = 0
guess_cr = 0
use_gtk = 0
use_curses = 0

while sys.argv[1:]:
    if sys.argv[1] == '--filename':
        replay_filename = sys.argv[2] 
        del sys.argv[1]
    elif sys.argv[1] == '--help':
        usage(0)
    elif sys.argv[1] == '--gtk':
        use_gtk = 1
    elif sys.argv[1] == '--curses':
        use_curses = 1
    elif sys.argv[1] == '--turn-off-bell':
        bell = 0
    elif sys.argv[1] == '--add-carriage-returns':
        add_cr = 1
    elif sys.argv[1] == '--guess-carriage-returns':
        add_cr = 2
    else:
        sys.stderr.write('%s: Illegal option: %s\n' % (sys.argv[0], sys.argv[1]))
        usage(1)
    del sys.argv[1]

if use_gtk + use_curses != 1:
    sys.stderr.write('%s: You must use exactly one of --gtk or --curses\n' % sys.argv[0])
    usage(1)

if use_gtk:
    import pygtk
    import gtk
elif use_curses:
    import curses
    import signal
    add_cr = 1
else:
    sys.stderr.write('%s: Internal error: No display method selected\n' % sys.argv[0])

def ctrl(ch):
    o = ord(ch)
    if ch >= 'a' and ch <= 'z':
        return chr(o - ord('a') + 1)
    elif ch >= 'A' and ch <= 'Z':
        return chr(o - ord('A') + 1)
    else:
        return ch

class line_class:
    def __init__(self, text):
        self.text = text
        self.length = len(text)

    def __str__(self):
        return 'length %d, ===> %s <===' % (self.length, self.text)

class cursor_class:
    def __init__(self, replay_filename, bell, add_cr, replay_lines=300):
        # IOError: [Errno 2] No such file or directory: 'input_fil'
        try:
            file = open(replay_filename, 'r')
        except IOError, (x, y):
            sys.stderr.write('%s: %s: %s\n' % (sys.argv[0], replay_filename, y))
            sys.exit(1)
        self.bell = bell
        self.line = map(line_class, \
            ['-=> START OF FILE <=-\n'] + file.readlines() + ['-=> END OF FILE <=-\n'])
        file.close()
        self.num_lines = len(self.line)
        print "Got %d lines of input" % self.num_lines
        #time.sleep(10)
        # there will always be at least two lines.  If this is an empty file,
        # they'll be numbered 0 and 1
        self.lineno = 0
        self.chrno = self.line[self.lineno].length
        if add_cr == 2:
            self.add_cr = 1
            for i in xrange(self.num_lines):
                if '\r' in self.line[i].text:
                    self.add_cr = 0
                    break
        else:
            self.add_cr = add_cr
        if use_gtk:
            self.window = gtk.Window(gtk.WINDOW_TOPLEVEL)
            self.window.set_title(sys.argv[0])
            self.window.connect("delete_event", self.delete_event)
            self.window.show()
            self.vbox = gtk.VBox()
            self.vbox.show()
            self.window.add(self.vbox)
            self.quit_button = gtk.Button('Quit')
            self.quit_button.show()
            self.quit_button.connect('clicked', self.delete_event)
            self.vbox.pack_start(self.quit_button)
            self.table = gtk.Table(2, 2, False)
            self.table.show()
            self.vbox.pack_start(self.table)
            self.back_chr_button = gtk.Button('Back character')
            self.back_line_button = gtk.Button('Back line')
            self.forward_chr_button = gtk.Button('Forward character')
            self.forward_line_button = gtk.Button('Forward line')
            self.back_chr_button.show()
            self.back_line_button.show()
            self.forward_chr_button.show()
            self.forward_line_button.show()
            self.table.attach(self.back_chr_button, 0, 1, 0, 1)
            self.table.attach(self.back_line_button, 1, 2, 0, 1)
            self.table.attach(self.forward_chr_button, 0, 1, 1, 2)
            self.table.attach(self.forward_line_button, 1, 2, 1, 2)
            self.back_chr_button.connect("clicked", self.back_chr)
            self.back_line_button.connect("clicked", self.back_line)
            self.forward_chr_button.connect("clicked", self.forward_chr)
            self.forward_line_button.connect("clicked", self.forward_line)
            self.beginning_button = gtk.Button('Beginning')
            self.end_button = gtk.Button('End')
            self.beginning_button.show()
            self.end_button.show()
            self.end_button.connect("clicked", self.end)
            self.beginning_button.connect("clicked", self.beginning)
            self.vbox.pack_start(self.end_button)
            self.vbox.pack_start(self.beginning_button)
            self.line_chr_label = gtk.Label('foo!')
            self.line_chr_label.show()
            self.vbox.pack_start(self.line_chr_label)
        else:
            self.screen = curses.initscr()
            curses.noecho()
            curses.cbreak()
            self.screen.keypad(1)
            self.help()
        self.show_status()
        self.clear()
        self.replay_lines = replay_lines
        self.replay()

    def show_status(self):
        if use_gtk:
            self.line_chr_label.set_text('Line %d, character %d' % (self.lineno, self.chrno))

    def delete_event(self, *ignored_arguments):
        gtk.main_quit()
        sys.exit(0)

    def clear(self):
        if use_curses:
            self.screen.clear()
            self.screen.refresh()
        else:
            # I'm sure there's a better way...
            os.system('tput clear')

    def putch(self, ch):
        if ch == ctrl('g'):
            # beeps can be too annoying :)
            if self.bell:
                os.write(1,ch)
        elif ch == '\n':
            if self.add_cr:
                os.write(1,'\r')
            os.write(1,ch)
        else:
            os.write(1,ch)

    def puts(self, s):
        for i in range(len(s)):
            self.putch(s[i])

    def replay(self):
        self.clear()
        first_lineno = self.lineno - self.replay_lines
        if first_lineno < 0:
            first_lineno = 0
        last_lineno = self.lineno
        if last_lineno >= self.num_lines:
            last_lineno = self.num_lines - 1
        # put whole lines from a ways back.  If we go back far enough, there'll either
        # be a clear screen or enough lines to scroll away ancient history
        for temp_lineno in range(first_lineno, last_lineno):
            self.puts(self.line[temp_lineno].text)
        # put the fractional current line
        self.puts(self.line[self.lineno].text[:self.chrno+1])

    def beep(self):
        self.puts(ctrl('g'))

    def forward_line(self, *dummy):
        self.lineno += 1
        if self.lineno >= self.num_lines:
            self.lineno = self.num_lines - 1
        self.chrno = len(self.line[self.lineno].text)
        self.replay()
        self.show_status()

    def back_line(self, *dummy):
        self.lineno -= 1
        if self.lineno < 0:
            self.lineno = 0
        self.chrno = self.line[self.lineno].length
        self.replay()
        self.show_status()

    def forward_chr(self, *dummy):
        old_chrno = self.chrno
        old_lineno = self.lineno
        self.chrno += 1
        if self.chrno >= self.line[self.lineno].length:
            self.lineno += 1
            self.chrno = 0
            if self.lineno >= self.num_lines:
                self.lineno = old_lineno
                self.chrno = old_chrno
        self.replay()
        self.show_status()

    def back_chr(self, *dummy):
        old_chrno = self.chrno
        old_lineno = self.lineno
        self.chrno -= 1
        if self.chrno < 0:
            self.lineno -= 1
            self.chrno = self.line[self.lineno].length - 1
            if self.lineno < 1:
                self.lineno = old_lineno
                self.chrno = old_chrno
        self.replay()
        self.show_status()

    def cleanup(self, *ignored_arguments):
        if use_gtk:
            gtk.main_quit()
        else:
            curses.nocbreak()
            self.screen.keypad(0)
            curses.echo()
            curses.endwin()
        sys.exit(0)

    def beginning(self, *ignored_arguments):
        self.lineno = 0
        self.chrno = 0
        self.replay()
        self.show_status()

    def end(self, *ignored_arguments):
        self.lineno = self.num_lines - 1
        self.chrno = self.line[self.lineno].length - 1
        self.replay()
        self.show_status()

    if use_curses:
        def getch(self):
            return self.screen.getch()

        def help(self):
            self.puts("Use ? for help\n")
            self.puts('\n')
            self.puts("Use j, control-n or down arrow to go down a line\n")
            self.puts("Use k, control-p or up arrow to go up a line\n")
            self.puts('\n')
            self.puts("Use l, control-f or right arrow to go forward one character\n")
            self.puts("Use h, control-f, left arrow or backspace to go back one character\n")
            self.puts('\n')
            self.puts("Use q or control-C (your interrupt character) to exit the program\n")
            self.puts('\n')
            self.puts("Hit any key to continue\n")
            dummy = sys.stdin.read(1)

cursor = cursor_class(replay_filename, bell, add_cr)

#def sigint_handler(x, y):
#    cursor.cleanup()
#    sys.exit(0)

signal.signal(signal.SIGINT, cursor.cleanup)

if use_gtk:
    gtk.main()
else:
    while 1:
        ch = cursor.getch()
        #print chr(8),'got',ch
        #time.sleep(2)
        if ch in [ord('h'), ord(ctrl('b')), curses.KEY_LEFT, curses.KEY_BACKSPACE]:
            cursor.back_chr()
        elif ch in [ord('l'), ord(ctrl('f')), curses.KEY_RIGHT]:
            cursor.forward_chr()
        elif ch in [ord('k'), ord(ctrl('p')), curses.KEY_UP]:
            cursor.back_line()
        elif ch in [ord('j'), ord(ctrl('n')), ord('\n'), curses.KEY_DOWN]:
            cursor.forward_line()
        elif ch in [ord('0'), curses.KEY_HOME]:
            cursor.beginning()
        elif ch in [ord('9'), curses.KEY_END]:
            cursor.end()
        elif ch == ord('?'):
            cursor.clear()
            cursor.help()
            cursor.replay()
        elif ch == ord('q'):
            cursor.cleanup()
            sys.exit(0)
#        else:
#            cursor.beep()