#!/usr/bin/python

# Copyright (c) 2009 Benjamin Drung <bdrung@ubuntu.com>
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
# 
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.

import csv
import glob
import optparse
import os
import subprocess
import sys

from moz_version import compare_versions

from rdflib import Namespace
from rdflib.Graph import Graph

# error codes
COMMAND_LINE_SYNTAX_ERROR = 1
MULTIPLE_INSTALL_RDFs = 2

def get_xul_apps():
	csvfile = open("/usr/share/mozilla-devscripts/xul-app-data.csv")
	csv_reader = csv.DictReader(csvfile)
	rows = []
	for row in csv_reader:
		rows.append(row)
	return rows

def get_supported_apps(script_name, xul_apps, install_rdf, package, verbose=False):
	# create array of id_max_min triples
	id_max_min = []
	rdf_graph = Graph()
	rdf_graph.parse(install_rdf)
	results = rdf_graph.query(
	  """
		SELECT ?id ?max ?min
		WHERE {
			?x1 em:targetApplication ?x2 .
			?x2 em:id ?id .
			OPTIONAL {
				?x2 em:maxVersion ?max .
				?x2 em:minVersion ?min .
			} .
		}
	  """, initNs=dict(em=Namespace("http://www.mozilla.org/2004/em-rdf#")))

	# append to id_max_min tripe to array
	for target in results:
		id_max_min.append (( str(target[0]), str(target[1]), str (target[2]) ))

	if verbose:
		print "%s: %s supports %i XUL application(s):" % (script_name, package, len(id_max_min))
		for (appid, max_version, min_version) in id_max_min:
			print "%s %s to %s" % (appid, min_version, max_version)

	# find supported apps/packages
	supported_apps = list()
	for xul_app in xul_apps:
		supported_app = filter(lambda x: x[0] == xul_app["id"], id_max_min)
		if len(supported_app) == 1:
			# package is supported by extension
			(appid, max_version, min_version) = supported_app.pop()
			if compare_versions(xul_app["sol"], max_version) <= 0:
				if compare_versions(xul_app["eol"], min_version) >= 0:
					supported_apps.append(xul_app["package"])
					if verbose:
						print "%s: %s supports %s." % (script_name, package, xul_app["package"])
				elif verbose:
					print "%s: %s does not support %s (any more)." % (script_name, package, xul_app["package"])
			elif verbose:
				print "%s: %s does not support %s (yet)." % (script_name, package, xul_app["package"])
		elif len(supported_app) > 1:
			print "%s: Found error in %s. There are multiple entries for application ID %s." % \
				(script_name, install_rdf, xul_app["id"])

	return supported_apps

def get_all_packages():
	lines = open("debian/control").readlines()
	package_lines = filter(lambda x: x.find("Package:") >= 0, lines)
	packages = map(lambda x: x[x.find(":")+1:].strip(), package_lines)
	return packages

def get_source_package_name():
	source = None
	f = open("debian/control")
	for line in f:
		if line.startswith("Source:"):
			source = line[line.find(":")+1:].strip()
			break
	return source


def get_provided_package_names(package, supported_apps):
	ext_name = package
	for prefix in ("firefox-", "iceweasel-", "mozilla-", "xul-ext-"):
		if ext_name.startswith(prefix):
			ext_name = ext_name[len(prefix):]

	# check if MOZ_XPI_EXT_NAME is defined in debian/rules
	lines = open("debian/rules").readlines()
	lines = filter(lambda x: x.find("MOZ_XPI_EXT_NAME") != -1, lines)
	if len(lines) > 0:
		ext_name = lines[-1][line.find("=")+1:].strip()

	provides = set()
	provides.add("xul-ext-" + ext_name)
	if ext_name == get_source_package_name():
		provides.add(ext_name)

	for app in supported_apps:
		for i in xrange(len(app) - 1, -1, -1):
			if app[i] == '-':
				app = app[:i]
			elif not app[i].isdigit() and not app[i] == '.':
				break
		provides.add(app + "-" + ext_name)

	# remove package name from provide list
	provides.discard(package)

	return list(provides)

def find_install_rdfs(path):
	install_rdfs = set()

	if os.path.isfile(path) and os.path.basename(path) == "install.rdf":
		install_rdfs.add(os.path.realpath(path))

	if os.path.isdir(path):
		# recursive walk
		content = map(lambda d: os.path.join(path, d), os.listdir(path))
		install_rdfs = reduce(lambda x, d: x.union(find_install_rdfs(d)), content, install_rdfs)

	return install_rdfs

def generate_substvars(script_name, xul_apps, package, verbose=False):
	install_rdfs = find_install_rdfs("debian/" + package)
	if len(install_rdfs) == 0:
		# this package does not contain a xul extension
		return
	elif len(install_rdfs) > 1:
		print >> sys.stderr, "%s: %s contains multiple install.rdf files. That's not supported." % (script_name, package)
		sys.exit(MULTIPLE_INSTALL_RDFs)
	install_rdf = install_rdfs.pop()

	filename = "debian/" + package + ".substvars"
	if os.path.exists(filename):
		f = open(filename)
		lines = f.readlines()
		f.close()
	else:
		lines = list()

	# remove existing varibles
	lines = filter(lambda s: not s.startswith("xpi:"), lines)

	packages = get_supported_apps(script_name, xul_apps, install_rdf, package, verbose)
	lines.append("xpi:Recommends=" + " | ".join(packages) + "\n")
	lines.append("xpi:Enhances=" + ", ".join(sorted(packages)) + "\n")
	packages = get_provided_package_names(package, packages)
	lines.append("xpi:Provides=" + ", ".join(sorted(packages)) + "\n")

	# write new variables
	f = open(filename, "w")
	f.writelines(lines)
	f.close()


class UnknownOptionIgnoringOptionParser(optparse.OptionParser):
	def __init__ (self, **options):
		optparse.OptionParser.__init__(self, **options)
		self.unknown_options = []

	def _process_long_opt(self, rargs, values):
		option = rargs[0].split("=")[0]
		if not option in self._long_opt:
			self.unknown_options.append(option)
			del rargs[0]
		else:
			optparse.OptionParser._process_long_opt(self, rargs, values)

	def _process_short_opts(self, rargs, values):
		option = rargs[0][0:2]
		if not self._short_opt.get(option):
			self.unknown_options.append(option)
			del rargs[0]
		else:
			optparse.OptionParser._process_short_opts(self, rargs, values)


if __name__ == "__main__":
	epilog = "See %s(1) for more info." % (os.path.basename(sys.argv[0]))
	parser = UnknownOptionIgnoringOptionParser(epilog=epilog)
	parser.add_option("-p", "--package", dest="packages", metavar="PACKAGE",
			action="append", default=[],
			help="calculate substvars only for the specified PACKAGE")
	parser.add_option("-v", "--verbose", action="store_true", dest="verbose",
			default=False, help="print more information")

	(options, args) = parser.parse_args()

	if len(options.packages) == 0:
		options.packages = get_all_packages()

	script_name = os.path.basename(sys.argv[0])

	if options.verbose:
		for unknown_option in parser.unknown_options:
			sys.stderr.write("%s: warning: no such option: %s\n" % (script_name, unknown_option))
		print script_name + ": packages:", ", ".join(options.packages)

	xul_apps = get_xul_apps()
	if options.verbose and len(xul_apps) > 0:
		print script_name + ": found %i Xul applications:" % (len(xul_apps))
		for xul_app in xul_apps:
			print xul_app["id"] + ": " + xul_app["package"] + " (" + xul_app["sol"] + " to " + xul_app["eol"] + ")"

	for package in options.packages:
		generate_substvars(script_name, xul_apps, package, options.verbose)
