#!/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
perform_window_resizing = 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
	elif sys.argv[1] == '--resize':
		perform_window_resizing = 1
	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])

if perform_window_resizing:
	resizing_filename = '%s.sizing' % replay_filename

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

# Note that this may not work if your terminal emulator is maximized at the time it is used.
# But as soon as you unmaximize, the window may snap to the desired size.
def force_resize(lines, columns):
	sys.stdout.write('\033[8;%d;%dt' % (lines, columns))
	raise
	# This should also TIOCSWINSZ
	
class line_class:
	def __init__(self, text, line_start_position):
		self.text = text
		self.length = len(text)
		self.line_start_position = line_start_position

	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, (errno, error_message):
			sys.stderr.write('%s: %s: %s\n' % (sys.argv[0], replay_filename, error_message))
			sys.exit(1)
		self.bell = bell
		#self.line = map(line_class, \
		#	['-=> START OF FILE <=-\n'] + file.readlines() + ['-=> END OF FILE <=-\n'])
		self.line = []
		while 1:
			line = file.readline()
			if not line:
				break
			self.line.append(line_class(line, file.tell()))
		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()

