#!/usr/bin/env pythonw

# Copyright (C) 2005, Thomas Leonard
# Copyright (C) 2010, Anders F Bjorklund
#
#     This library is free software; you can redistribute it and/or
#     modify it under the terms of the GNU Lesser General Public
#     License as published by the Free Software Foundation; either
#     version 2.1 of the License, or (at your option) any later version.
#
#     This library is distributed in the hope that it will be useful,
#     but WITHOUT ANY WARRANTY; without even the implied warranty of
#     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
#     Lesser General Public License for more details.
#
#     You should have received a copy of the GNU Lesser General Public
#     License along with this library; if not, write to the Free Software
#     Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307  USA

import os, sys

from xml.dom import minidom
import urllib2
import shutil, os, tempfile, subprocess
from logging import info, warn
from cStringIO import StringIO

from zeroinstall.injector import qdom, selections
from zeroinstall.injector import policy, namespaces, model
from zeroinstall.injector.config import load_config

from optparse import OptionParser

parser = OptionParser(usage="usage: %prog [options] [URI [target]]")
parser.add_option("--run-in-terminal", help="execute URI in a terminal emulator", action='store_true')
parser.add_option("--show-help", help="open URI's help directory", action='store_true')
parser.add_option("--no-download", help="don't actually download it", action='store_true')
parser.disable_interspersed_args()

(options, args) = parser.parse_args()

########################################################################

import Tkinter

root = Tkinter.Tk()
root.withdraw()

def mac_alert(msg):
	import tkMessageBox
	tkMessageBox.showwarning('', msg)

def mac_report_exception():
	import tkMessageBox
	import traceback
	tkMessageBox.showerror('', traceback.format_exc())

def mac_chooser():
	import tkSimpleDialog
	return tkSimpleDialog.askstring('', 'Enter the URI of a program available '
	                                'through the Zero Install Injector system.')

def mac_savebox(path, name):
	import tkFileDialog
	return tkFileDialog.asksaveasfilename(initialdir=path, initialfile=name)

########################################################################

if os.getenv('JHBUILD_PREFIX'):
	injector = ['~/.local/bin/jhbuild', 'run', '0launch']
else:
	injector = ['0launch']

if os.path.exists('/opt/local/bin/png2icns'):
	png2icns = '/opt/local/bin/png2icns'
else:
	png2icns = '/usr/local/bin/png2icns'

########################################################################

def try_interface(uri):
	if options.no_download:
		action = 'select'
	else:
		action = 'download'
	child = subprocess.Popen(
		[os.environ.get("ZEROINSTALL", "0install"), action, "--xml", "--", uri],
		stdout = subprocess.PIPE, stderr = subprocess.PIPE)

	stdout, stderr = child.communicate()
	code = child.wait()
	if code:
		mac_alert('0install download %s failed: error code = %d\n%s' % (uri, code, stderr))
		return None
	return selections.Selections(qdom.parse(StringIO(stdout)))

saveas = None
if not args:
	assert not options.run_in_terminal
	assert not options.show_help

	while True:
		uri = mac_chooser()
		if not uri:
			sys.exit(1)
		sels = try_interface(uri)
		if sels:
			break
else:
	uri = args[0]

	if options.run_in_terminal:
		import terminal
		terminal.run_in_terminal(args)
		sys.exit(1)

	if options.show_help:
		import help
		help.show_help(uri)
		sys.exit(0)

	sels = try_interface(uri)
	if not sels:
		sys.exit(1)

assert sels

########################################################################

addapp_uri = "http://afb.users.sourceforge.net/zero-install/interfaces/AddApp.xml"

class Policy(policy.Policy):
	pass

class NoMain(Exception):
	pass

class AppLauncher():
	def __init__(self, iface_cache, stores, sels):
		self.uri = sels.interface
		self.iface_cache = iface_cache
		self.stores = stores
		self.icon = None
		self._icon_tmp = None

		self.impl = sels.selections[self.uri]
		if not self.impl:
			raise Exception("Failed to select an implementation of %s" % self.uri)

	def identifier(self, uri):
		""" Convert a feed uri to a suitable app identifier """
		from urlparse import urlparse
		result = urlparse(uri)
		part1 = result.netloc.split('.')
		part1.reverse()
		part2 = os.path.splitext(result.path)[0].lstrip('/').split('/')
		part2 = [part2[-1]]
		return ('.'.join(part1 + part2)).lower()

	def save_to_file(self, path):
		if not path.endswith('.app'):
			path += '.app'
		assert "'" not in self.uri
		iface = self.iface_cache.get_interface(self.uri)

		_template = """#!/bin/sh
exec %(0launch)s -- '%(iface)s'
"""

		_plist_template = """<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	<key>CFBundleDevelopmentRegion</key>
	<string>English</string>
	<key>CFBundleExecutable</key>
	<string>%(executable)s</string>
	<key>CFBundleIconFile</key>
	<string>%(iconfile)s</string>
	<key>CFBundleIdentifier</key>
	<string>%(identifier)s</string>
	<key>CFBundleInfoDictionaryVersion</key>
	<string>6.0</string>
	<key>CFBundleName</key>
	<string>%(name)s</string>
</dict>
</plist>
"""

		tmpdir = tempfile.mkdtemp(prefix = 'zero2desktop-')
		try:
			executable = iface.get_name().lower().replace(os.sep, '-').replace(' ', '')
			bundle_dir = os.path.join(tmpdir, "%s.app" % executable)
			os.mkdir(bundle_dir)
			contents = os.path.join(bundle_dir, 'Contents')
			os.mkdir(contents)
			plist_name = os.path.join(contents, 'Info.plist')
			plist = file(plist_name, 'w')
			plist.write(_plist_template % {'name': executable,
										   'executable': executable,
										   'iconfile': "%s.icns" % executable,
										   'comment': iface.summary,
										   'identifier': self.identifier(iface.uri)})
			plist.close()
			os.mkdir(os.path.join(contents, 'MacOS'))
			script_name = os.path.join(contents, 'MacOS', executable)
			script = file(script_name, 'w')
			script.write(_template % {'0launch': " ".join(injector),
									  'iface': iface.uri })
			script.close()
			os.chmod(script_name, 0755)
			icon_path = self.icon
			if icon_path:
				os.mkdir(os.path.join(contents, 'Resources'))
				icns_name = os.path.join(contents, 'Resources', "%s.icns" % executable)
				status = os.spawnlp(os.P_WAIT, png2icns, 'png2icns', icns_name, icon_path)
				if status:
					print 'Failed to convert icon (error code %d)' % status
	
			shutil.move(bundle_dir, path)
		finally:
			#shutil.rmtree(tmpdir)
			pass

	def get_appdir(self, impl):
		impl_path = impl.local_path or self.stores.lookup_any(impl.digests)
		main = impl.main
		if not main:
			return None
		assert not main.startswith('/'), main
		return os.path.dirname(os.path.join(impl_path, main))

	def is_applet(self, iface):
		appdir = self.get_appdir(self.impl)
		if appdir:
			applet_run = os.path.join(appdir, 'AppletRun')
			return os.path.exists(applet_run)
		else:
			return False

	def get_icon(self):
		"""Sets self.icon to a filename of an icon for the app (downloading it first), or None."""
		iface = self.iface_cache.get_interface(self.uri)
		path = self.iface_cache.get_icon_path(iface)
		if path:
			self.icon = path
		return
	
	def delete_icon(self):
		"""Remove the temporary icon file, if any"""
		self._icon_tmp = None


########################################################################

saveas = None
if len(args) == 2:
	saveas = args[1]
elif len(args) > 2:
	parser.print_help()
	sys.exit(1)

try:
	config = load_config()
	launcher = AppLauncher(config.iface_cache, config.stores, sels)

	if saveas:
		if os.path.exists(saveas):
			raise Exception("'%s' already exists!" % saveas)
		launcher.get_icon()
		launcher.save_to_file(saveas)
		sys.exit(0)

	default_name = os.path.basename(sels.interface)
	if default_name.endswith('.xml'):
		default_name = default_name[:-4]
	saveas = mac_savebox(os.path.expanduser('~/Desktop'), default_name)
	if not saveas:
		sys.exit(1)

	launcher.get_icon()
	launcher.save_to_file(saveas)
	launcher.delete_icon()
except NoMain, ex:
	mac_alert(str(ex))
	sys.exit(1)
except SystemExit:
	raise
except:
	mac_report_exception()
	sys.exit(1)