#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# Pyntor - Python presentation program
# Copyright (C) 2004 - 2006 Josef Spillner <josef@coolprojects.org>
# Published under GNU GPL conditions

import pygame
from pygame.locals import *

import imp
import sys
import os
import os.path
import tarfile
import tempfile
import shutil
import getopt
import time
import atexit

prefix = "/usr/local"
pyntorpath = prefix + "/share/pyntor"

version = "0.6"

starttime = time.time()

origdir = os.getcwdu()
chrootdir = None

class Script:
	def __init__(self):
		self.slots = []
		self.options = {}
		self.components = {}
		self.mods = {}
		self.index = 0
		self.basepages = {}
		self.pages = {}

	def addcomponent(self, fullstring):
		ar = fullstring.split(" ")
		component = ar[0]
		options = ar[1:]
		self.slots.append(self.index)
		self.components[self.index] = component
		self.options[self.index] = options
		self.mods[self.index] = None
		self.basepages[self.index] = -1
		self.pages[self.index] = -1
		self.index += 1

def pagesplash(dowait):
	screen = pygame.display.get_surface()
	try:
		splash = pygame.image.load(pyntorpath + "/pyntor-splash.png")
	except:
		splash = pygame.Surface((screen.get_width(), screen.get_height()))
		splash.fill((200, 200, 255))
	screen.blit(splash, (0, 0))
	pygame.display.update()

	if dowait:
		pygame.event.clear()
		pygame.event.pump()
		event = pygame.event.wait()

def pageselect(maxpages, currentpage, pagegroups, script):
	screen = pygame.display.get_surface()
	surface = pygame.Surface((screen.get_width(), screen.get_height()))
	surface.blit(screen, (0, 0))

	font = pygame.font.SysFont("Vera Sans", 26)

	f = font.render("Page selection", 1, (0, 100, 180))
	surface.blit(f, (50, 50))

	for page in range(1, maxpages + 1):
		f = font.render("Page " + str(page), 1, (0, 100, 180))
		surface.blit(f, (50, 60 + page * 25))

		# Display headings (merge/unify!)
		for index in pagegroups[page]:
			mod = script.mods[index]
			if "headings" in dir(mod):
				headings = mod.headings
				basepage = script.basepages[index]
				xpage = page - basepage
				if headings.has_key(xpage):
					t = headings[xpage]
					f = font.render(t, 1, (0, 100, 180))
					surface.blit(f, (250, 60 + page * 25))

	newcurrentpage = currentpage
	pygame.event.clear()
	while 1:
		screen.blit(surface, (0, 0))
		f = font.render("<=", 1, (0, 100, 180))
		screen.blit(f, (150, 60 + newcurrentpage * 25))

		pygame.display.update()

		pygame.event.pump()
		event = pygame.event.wait()

		if event.type == KEYDOWN:
			key = event.key
			if key == K_ESCAPE or pygame.event.peek(QUIT):
				return currentpage
			if key == K_RETURN:
				return newcurrentpage
			if key == K_UP:
				if newcurrentpage > 1:
					newcurrentpage -= 1
			if key == K_DOWN:
				if newcurrentpage < maxpages:
					newcurrentpage += 1

def pagesearch(maxpages, pagegroups, script):
	screen = pygame.display.get_surface()
	surface = pygame.Surface((screen.get_width(), screen.get_height()))
	surface.blit(screen, (0, 0))

	font = pygame.font.SysFont("Vera Sans", 26)

	f = font.render("Full-text page search", 1, (0, 100, 180))
	surface.blit(f, (50, 50))

	f = font.render("Search term:", 1, (0, 100, 180))
	surface.blit(f, (50, 85))

	#newcurrentpage = currentpage
	newcurrentpage = 0
	foundpages = []
	searchterm = ""

	pygame.event.clear()
	while 1:
		screen.blit(surface, (0, 0))
		f = font.render(searchterm, 1, (0, 100, 180))
		screen.blit(f, (210, 85))

		i = 0
		for page in foundpages:
			i += 1
			f = font.render("Page " + str(page), 1, (0, 100, 180))
			screen.blit(f, (50, 120 + i * 25))

			# Display headings (merge/unify!)
			for index in pagegroups[page]:
				mod = script.mods[index]
				if "headings" in dir(mod):
					headings = mod.headings
					basepage = script.basepages[index]
					xpage = page - basepage
					if headings.has_key(xpage):
						t = headings[xpage]
						f = font.render(t, 1, (0, 100, 180))
						screen.blit(f, (250, 120 + i * 25))

		if newcurrentpage != 0:
			f = font.render("<=", 1, (0, 100, 180))
			screen.blit(f, (150, 120 + newcurrentpage * 25))

		pygame.display.update()

		pygame.event.pump()
		event = pygame.event.wait()

		if event.type == KEYDOWN:
			key = event.key
			if key == K_ESCAPE or pygame.event.peek(QUIT):
				return 0
			if key == K_RETURN:
				if newcurrentpage == 0:
					return 0
				return foundpages[newcurrentpage - 1]
			if key == K_UP:
				if newcurrentpage > 1:
					newcurrentpage -= 1
			if key == K_DOWN:
				if newcurrentpage < len(foundpages):
					newcurrentpage += 1
			if key == K_BACKSPACE:
				searchterm = searchterm[:-1]
				newcurrentpage = 0

			character = event.unicode
			if character:
				if ord(character) >= 32:
					searchterm += character
					newcurrentpage = 0

		xfound = {}
		for page in range(1, maxpages + 1):
			for index in pagegroups[page]:
				#print "PPP", page, "=>", index
				mod = script.mods[index]
				if "fulltext" in dir(mod):
					#print "MOD", mod, "has fulltext!"
					ft = mod.fulltext
					basepage = script.basepages[index]
					xpage = page - basepage
					#print "search", page, "as", xpage
					if ft.has_key(xpage):
						for line in ft[xpage]:
							if line.find(searchterm) != -1:
								xfound[page] = 1

		foundpages = []
		for page in xfound:
			foundpages.append(page)

def pageclock():
	global starttime

	screen = pygame.display.get_surface()

	font = pygame.font.SysFont("Vera Sans", 16)

	timestr = time.strftime("%H:%M:%S")
	starttimestr = str(int(time.time() - starttime) / 60) + u"”"

	f = font.render("Time: " + timestr + " - " + starttimestr, 1, (255, 255, 255))
	x = screen.get_width() - f.get_width() - 2
	y = screen.get_height() - f.get_height() - 2
	target = pygame.Surface((f.get_width(), f.get_height()))
	target.fill((0, 0, 0))
	target.blit(f, (0, 0))
	target.set_alpha(30)
	screen.blit(target, (x, y))

def execute(cmd):
	cmdstr = cmd.encode("utf-8")
	# FIXME: python os.system() doesn't handle unicode
	#print ">>", type(cmdstr), cmdstr
	return os.system(cmdstr)

def finishexport(export, exportdir):
	print "Finishing export..."
	if export != exportdir:
		for f in os.listdir(exportdir):
			fn = os.path.join(exportdir, f)
			cmd = "convert " + fn + " " + fn + ".png"
			execute(cmd)
			cmd = "rm -f " + fn
			execute(cmd)
		cmd = "convert " + exportdir + "/*.png " + export
		execute(cmd)
		cmd = "rm -rf " + exportdir
		execute(cmd)
	else:
		for f in os.listdir(exportdir):
			fn = os.path.join(exportdir, f)
			cmd = "convert " + fn + " " + fn + ".png"
			execute(cmd)
			cmd = "rm -f " + fn
			execute(cmd)
		print "You might want to run pyntor-minigal.sh"
		print "or another gallery script to create the HTML page!"

def loadscript(scriptfile):
	try:
		f = open(scriptfile)
	except:
		print "Error: Script file ('" + scriptfile + "') not found"
		sys.exit(1)

	script = Script()

	reading = 1
	while reading:
		line = f.readline()
		if not line:
			reading = 0
		else:
			line = line.replace("\n", "")
			if line == "":
				continue
			elif line[0] == "#":
				continue
			script.addcomponent(line)
	f.close()

	return script

def parseparameters(params, args):
	#params = line.split(" ")
	#print params

	options = {}
	arguments = []
	skip = 0

	for i in range(len(params)):
		#if i == 0:
		#	continue
		if skip:
			skip = 0
			continue
		#print "arg", params[i]
		if params[i][0] == "-":
			for arg in args:
				(option, help, default) = arg
				if params[i][1:] == option:
					#print "option!", option
					if options.has_key(option):
						print "-> ewww, multiple occurence of", option
						return None
					value = params[i + 1]
					options[option] = value
					#print "value", value
					skip = 1
		if not skip:
			#print "hm, which option?"
			found = 0
			for arg in args:
				(option, help, default) = arg
				if not options.has_key(option):
					#print "got it!", option
					value = params[i]
					if option is None:
						arguments.append(value)
					else:
						options[option] = value
					found = 1
					break
			if not found:
				print "-> uff, dropping superfluous parameter", params[i]
				return None

	for arg in args:
		(option, help, default) = arg
		if not options.has_key(option):
			if default is not None:
				if len(arguments) > 0:
					#print "-> set arguments", arguments
					options[option] = arguments
				else:
					#print "-> defaulting to", default, "for", option
					options[option] = default
			else:
				print "-> missing non-default option", option
				return None

	return options

def main(script, fullscreen, components, export):
	""" Presentation preparation """

	scriptpos = os.path.join(origdir, __file__)
	scriptdir = os.path.dirname(scriptpos)
	localpath = os.path.join(scriptdir, "components")

	installpath = os.path.join(scriptdir, "..", "share", "pyntor", "components")
	installpath = os.path.normpath(installpath)

	homepath = os.path.join(os.getenv("HOME"), ".pyntor", "components")

	""" Component initialization """

	searchpaths = ["components", localpath, homepath, installpath]

	for index in script.slots:
		component = script.components[index]
		options = script.options[index]
		try:
			(fileobj, filename, desc) = imp.find_module(component, searchpaths)
			mod = imp.load_module(component, fileobj, filename, desc)
			fileobj.close()
		except:
			print "Error: Cannot load component", component
			mod = None
			sys.exit(1)

		if "parameters" in dir(mod):
			#print "NEW-STYLE!", options
			xoptions = component + " " + " ".join(options)
			options = parseparameters(options, mod.parameters)
			if options is None:
				print "Error: wrong component parameters in '" + xoptions + "'"
				sys.exit(1)

		try:
			mod.component.init(options)
			script.mods[index] = mod.component
			if "disabled" in dir(mod.component):
				if mod.component.disabled == 1:
					sys.exit(1)
		except:
			print "Error: Cannot initialize component", component
			print "Error:", sys.exc_info()[1]
			sys.exit(1)

	basepage = 0
	maxpages = 0
	newinteractive = 0
	for index in script.slots:
		mod = script.mods[index]
		if mod:
			pages = 0
			render = 0
			interactive = 0
			d = dir(mod)
			if "pages" in d:
				pages = mod.pages
			if "render" in d:
				render = 1
			if "interactive" in d:
				interactive = 1
			if not render and newinteractive:
				newinteractive = 0
				pages = 1
				if "pages" in d:
					if mod.pages == 0:
						pages = 0
			if not interactive:
				newinteractive = 1

			#print "XXX", basepage, pages
			script.pages[index] = pages
			script.basepages[index] = basepage
			pages = script.pages[index]
			basepage += pages
			maxpages = basepage

	pagegroups = {}
	pending = []

	if components:
		fmt = ("Component", "Status", "Render", "Interactive", "Pages")
		print "%15s %10s %8s %12s %8s" % fmt
	for index in script.slots:
		component = script.components[index]
		mod = script.mods[index]
		interactive = ""
		render = ""
		init = ""
		status = "broken!"
		pagestr = "???"
		if mod:
			d = dir(mod)
			if "interactive" in d:
				interactive = "[x]"
			if "render" in d:
				render = "[x]"
			if "init" in d:
				init = "[x]"
			if init and (interactive or render):
				status = "ok"
				basepage = script.basepages[index] + 1
				pages = script.pages[index]
				if pages > 0:
					if pages == 1:
						pagestr = str(basepage)
					else:
						pagestr = str(basepage) + "-" + str(basepage + pages - 1)
					for xpage in range(basepage, basepage + pages):
						if not pagegroups.has_key(xpage):
							pagegroups[xpage] = []
						for pindex in pending:
							pagegroups[xpage].append(pindex)
						pagegroups[xpage].append(index)
					if not (render and interactive):
						pending = []
				else:
					if render:
						pagestr = "---"
						pending.append(index)
					else:
						pagestr = "+++"
						pending = []
		if components:
			fmt = (component, status, render, interactive, pagestr)
			print "%15s %10s %8s %12s %8s" % fmt
	if components:
		return

	if not pagegroups.has_key(1):
		return

	""" Pygame setup """

	if export:
		pygame.init()
		screen = pygame.Surface((1024, 768))
		#screen = pygame.display.set_mode((1024, 768), DOUBLEBUF)
		#print "SCREEN!", screen
	else:
		pygame.init()
		pygame.display.set_caption("Presentation")

		if fullscreen:
			screen = pygame.display.set_mode((1024, 768), DOUBLEBUF)
			pygame.display.toggle_fullscreen()
			pygame.mouse.set_visible(0)
		else:
			screen = pygame.display.set_mode((640, 480), DOUBLEBUF)

	""" Splash """

	#pagesplash(1)

	""" Main loop """

	if export:
		exportdir = export
		if export[-4:] == ".pdf":
			print "Exporting to PDF file", export
			exportdir = export + ".tmp"
		else:
			print "Exporting to HTML+PNG directory", export
			export = os.path.join(origdir, exportdir)
		exportdir = os.path.join(origdir, exportdir)
		print "Using temporary directory", exportdir

		try:
			os.mkdir(exportdir)
		except:
			print "Error: could not create directory", exportdir
			print "If it exists, delete it first"
			return

	#for page in range(1, maxpages + 1):
	page = 1
	mouse = 0

	while 1:
		for index in pagegroups[page]:
			#print "PPP", page, "=>", index
			mod = script.mods[index]
			needtopoll = 0
			if "polling" in dir(mod):
				if mod.polling == 1:
					needtopoll = 1
			if "render" in dir(mod):
				xpage = page - script.basepages[index]
				if script.pages[index] <= 0:
					xpage = -1
					#page = script.pages[index]
				#print "PAGE", xpage
				#mod.render(page - script.basepages[index])
				#mod.render(xpage)
				mod.render(screen, xpage, page)
			if export:
				if script.pages[index] > 0:
					print "export page", page
					pagestr = str(page).zfill(3)
					filename = "slide" + pagestr + ".bmp"
					fn = os.path.join(exportdir, filename)
					# FIXME: pygame cannot handle unicode filenames here
					# FIXME: therefore we have to use tiff
					# FIXME: but 'convert' doesn't handle tiff!

					pygame.image.save(screen, fn)
					#fo = open(fn, "w")
					#pygame.image.save(screen, fo)
					#fo.close()

		if not export:
			pygame.display.update()
			pygame.event.clear()
			origpage = page

			savescreen = pygame.Surface((screen.get_width(), screen.get_height()))
			savescreen.blit(screen, (0, 0))

			reload = 0
			polled = 0
			while page == origpage and not reload:
				pygame.event.pump()
				if not polled:
					event = pygame.event.poll()
					if not needtopoll:
						polled = 1
				else:
					event = pygame.event.wait()

				if event.type == KEYDOWN:
					key = event.key
					if key == K_t:
						pageclock()
						pygame.display.update()
						continue
					if key == K_f:
						mouse = 1 - mouse
						pygame.mouse.set_visible(mouse)
						pygame.display.toggle_fullscreen()
						continue
					if key == K_s:
						pagesplash(0)
						page = pagesearch(maxpages, pagegroups, script)
						if page == 0:
							page = origpage
						if page == origpage:
							screen.blit(savescreen, (0, 0))
							pygame.display.update()
						continue
					if key == K_p:
						pagesplash(0)
						page = pageselect(maxpages, page, pagegroups, script)
						if page == origpage:
							screen.blit(savescreen, (0, 0))
							pygame.display.update()
						continue

				for index in pagegroups[page]:
					#print "PPP", page, "=>", index
					mod = script.mods[index]

					# FIXME: update or flip?
					if "interactive" in dir(mod):
						r = mod.interactive(event)
						if r == "exit":
							return
						if r == "next":
							if page < maxpages:
								page += 1
						if r == "previous":
							if page > 1:
								page -= 1
						if r == "reload":
							reload = 1

			screen.blit(savescreen, (0, 0))
		else:
			page += 1
			if page == maxpages + 1:
				break

	if export:
		if chrootdir:
			os.chdir(origdir)
		finishexport(export, exportdir)

def cleanup(tmpdir):
	if tmpdir is not None:
		#print "(cleanup...)", tmpdir
		shutil.rmtree(tmpdir)

if __name__ == "__main__":
	fullscreen = 1
	components = 0
	scriptfile = "script"
	export = None
	direct = None

	try:
		longopts = ["help", "windowed", "components", "usage", "version"]
		longopts += ["export=", "direct="]
		options = getopt.gnu_getopt(sys.argv[1:], "hwcuvx:d:", longopts)
	except:
		print "Error:", sys.exc_info()[1]
		sys.exit(1)

	(options, arguments) = options

	for opt in options:
		(optkey, optval) = opt

		if optkey in ("-h", "--help"):
			print "Pyntor - componentized presentation program"
			print "Copyright (C) 2004-2006 Josef Spillner <josef@coolprojects.org>"
			print "Published under GNU GPL conditions"
			print ""
			print "Synopsis: pyntor [options] <presentation-file>"
			print ""
			print "Options:"
			print "[-c | --components] No presentation, but list needed components"
			print "[-x | --export=...] Export to <file.pdf> or <htmldir>"
			print "[-d | --direct=...] Take script instructions from the prompt"
			print "[-w | --windowed  ] Do not run in fullscreen mode"
			print "[-v | --version   ] Print Pyntor's version"
			print "[-h | --help      ] Display this help"
			print "[-u | --usage     ] Short usage examples"
			sys.exit(0)
		elif optkey in ("-u", "--usage"):
			print "Run: pyntor [--windowed] <scriptfile/pyntor archive>"
			print "     pyntor --components <scriptfile/pyntor archive>"
			print "     pyntor --direct '<component> <component-options>'"
			sys.exit(0)
		elif optkey in ("-v", "--version"):
			print version
			sys.exit(0)
		elif optkey in ("-w", "--windowed"):
			fullscreen = 0
		elif optkey in ("-c", "--components"):
			components = 1
		elif optkey in ("-x", "--export"):
			export = optval
		elif optkey in ("-d", "--direct"):
			direct = optval

	if len(arguments) == 0:
		#print "Error: no presentation given"
		#sys.exit(1)
		pass
	elif len(arguments) == 1:
		scriptfile = arguments[0]
	else:
		print "Error: more than one presentation given"
		sys.exit(1)

	tmpdir = None

	try:
		tar = tarfile.open(scriptfile)
		tmpdir = tempfile.mkdtemp()
	except:
		pass

	if tmpdir:
		atexit.register(cleanup, tmpdir)
		for tarinfo in tar:
			tar.extract(tarinfo, tmpdir)
		os.chdir(tmpdir)
		chrootdir = None
		for d in os.listdir(tmpdir):
			if os.path.isdir(d):
				if chrootdir:
					print "Error: presentation archive contains many subdirs"
					sys.exit(1)
				chrootdir = d
		if chrootdir:
			os.chdir(chrootdir)
		scriptfile = "script"

	if direct:
		script = Script()
		script.addcomponent(direct)
		script.addcomponent("wait")
	else:
		script = loadscript(scriptfile)

	main(script, fullscreen, components, export)

