# -*-python-*-
# vim: ts=4

import handlerbase
from utilities import print_error

class MOCHandler(handlerbase.HandlerBase):
	def __init__(self):
		pass

	def handle(self, amfile):

		for program in amfile.binaries.values():
			program.has_auto_moc = 0
			# taking over program specifics in global set
			for ui in program.ui_files:
				amfile.ui_files[ui] = 1
		
		amfile.has_auto_moc = 0

		value = amfile.value_list('METASOURCES')
		if 'USE_AUTOMOC' in value:
			print_error("%s: USE_AUTOMOC is unsupported\n" % amfile.filename)
		if 'AUTO' in value:
			amfile.has_auto_moc = 1
		for program in amfile.binaries.values():
			value = amfile.value_list(program.canon_name + '_METASOURCES')
			if 'USE_AUTOMOC' in value:
				print_error("%s: USE_AUTOMOC is unsupported\n" % amfile.filename)
			if 'AUTO' in value:
				if amfile.has_auto_moc:
					sys.stderr.write("%s: defines METASOURCES = AUTO twice\n" %
									 amfile.filename)
				amfile.binaries[program.name].has_auto_moc = 1
				amfile.has_auto_moc = 1
			
		self.find_moc_candidates(amfile)
		self.prune_moc_candidates(amfile)
		self.check_moc_candidates(amfile)
		for base in amfile.ui_files.keys():
			amfile.moc_files[base] = ['', base + ".h", base + ".cpp"]
		self.add_moc_rules(amfile)
		
	def tag_META_INCLUDES(self, amfile):
		# Gets the list of user defined directories -
		# relative to $srcdir - where header files could be located.

		headerdirs = ['.']
		if not amfile.is_defined("META_INCLUDES"):
			return headerdirs

		for dir in amfile.definition_rec("META_INCLUDES"):
			dir = string.replace(dir, "$(srcdir)", ".")

			if not amfile.in_srcdir(dir):
				sys.stderr.write("%s: Warning: %s [META_INCLUDES] can't "
								 "be found. Must be a relative path to "
								 "$(srcdir)!\n" % (amfile.filename, dir))
			else:
				headerdirs.append(dir)
				
		return headerdirs
	
	def find_moc_candidates(self, amfile):

		headerdirs = self.tag_META_INCLUDES(amfile)
		amfile.moc_files = {}
		commentre = re.compile('^\s*//\s*Q_OBJECT')
		hfilere = re.compile("([^.].+)\.(h|H|hh|hxx|hpp|h\+\+)$")
		
		for dir in headerdirs:
			hfiles = []
			for file in os.listdir(amfile.dirname + "/" + dir):
				match = hfilere.match(file)
				if match:
					hfiles.append((match.group(1), match.group(2)))

			moc_headers = []
			for hf, ext in hfiles:
				if amfile.ui_files.has_key(hf):
					continue

				hfData =  open( os.path.join(amfile.dirname,
											 dir, hf + "." + ext)).readlines()
				for line in hfData:
					if not string.find(line, "Q_OBJECT") == -1:
						if not commentre.match(line):
							moc_headers.append((hf, ext))
							break

			for hf, ext in moc_headers:
				if amfile.moc_files.has_key(hf):
					sys.stderr.write("%s: Multiple header files found for %s\n"
									 % (amfile.filename, hf) )
					continue
				amfile.moc_files[hf] = [dir, "%s.%s" % ( hf, ext) ]

	def prune_moc_candidates(self, amfile):
		
		if amfile.has_auto_moc or not len(amfile.moc_files):
			return
		
		# The programmer has specified a moc list. Prune out the moc candidates
		# list that we found based on looking at the header files. This generates
		# a warning if the programmer gets the list wrong, but this doesn't have
		# to be fatal here.

		pruned_mocs = {}

		moc_list = amfile.value_list("METASOURCES")
		
		for prog in amfile.binaries.values():
			if prog.has_auto_moc:
				continue

			moc_list.extend(amfile.value_list(prog.canon_name + "_METASOURCES"))

		if len(moc_list):
			for mocname in moc_list:
				match = extre.match(mocname)
				if not match:
					print_error('%s: no moc file (nor AUTO): %s\n'
								% (amfile.filename, mocname))
					continue
				mocname = match.group(1) # without extension
				assert(match.group(2) == '.moc')

				if amfile.moc_files.has_key(mocname):
					pruned_mocs[mocname] = amfile.moc_files[mocname]
				else:
					sys.stderr.write("%s: Warning: no moc-able header file for %s\n"
									 % (amfile.filename, mocname))

			amfile.moc_files = pruned_mocs

	def check_moc_candidates(self, amfile):
		# Finds the cpp files (If they exist).
		# The cpp files get appended to the header file

		if not len(amfile.moc_files):
			return

		import utilities
		cppfiles, hfiles = utilities.list_source_files(amfile.dirname)
		
		if not len(cppfiles):
			return

		moc2cpp = {}
		mocre = re.compile("[ \t]*#include\s*[<\"](.*\.moc)[>\"]")
		for (base, ext) in cppfiles:
			file = base + ext
			lines = open(amfile.dirname + "/" + file).readlines()
			for line in lines:
				match = mocre.match(line)
				if match:
					try:
						moc2cpp[match.group(1)].append(file)
					except:
						moc2cpp[match.group(1)] = [file]

		deped = []
		new_objs = []
		for mocfile in amfile.moc_files.keys():
			try:
				cppfiles = moc2cpp[mocfile + ".moc"]
			except:
				cppfiles = []
			if len(cppfiles) == 1:
				amfile.moc_files[mocfile].append(cppfiles[0])
				deped.append(mocfile)
			elif len(cppfiles) == 0:
				new_objs.append(mocfile)
				if amfile.has_auto_moc:
					continue
				sys.stderr.write("%s: Warning: No c++ file includes %s.moc\n" % (amfile.filename, mocfile))
			else:
				# We can't decide which file to use, so it's fatal. Although as a
				# guess we could use the mocFile.cpp file if it's in the list???
				sys.stderr.write("%s: Error: Multiple c++ files include %s.moc\n" % (amfile.filename, mocfile))
				print_error("%s: \t%s\n" % (amfile.filename, string.join(cppfiles)))
				return

		if amfile.has_auto_moc and len(new_objs):
			hits = []
			for prog in amfile.binaries.values():
				if prog.has_auto_moc:
					hits.append(prog)
			if not len(hits) and len(amfile.binaries) == 1:
				hits.extend(amfile.binaries.values())
			if not len(hits) == 1:
				print_error("%s: not all moc files are included in cpp files\n"
							"\tand there are more than one targets that could link\n"
							"\tto them: %s\n" % (amfile.filename, string.join(new_objs)))
		
			else:
				self.add_meta_objs(new_objs, hits[0])
		
	def add_moc_rules(self, amfile):

		# Add the rules for generating moc source from header files
		# For Automoc output *.moc.cpp but normally we'll output *.moc
		# (We must compile *.moc.cpp separately. *.moc files are included
		# in the appropriate *.cpp file by the programmer)

		mocs = []
		rulef = amfile.rulef
		
		for mocfile, list in amfile.moc_files.items():
			dir = list[0]
			if len(dir) and dir[0] == '.':
				subdir_dir = rulef.source + list[0][1:]
				if not subdir_dir[-1] == '/':
					subdir_dir = subdir_dir + '/'
			else:
				subdir_dir = rulef.build
				
			hfile = list[1]

			if len(list) == 3:
				cppfile = list[2]

				rulef.insertTarget(rulef.build + mocfile + ".moc",
								   subdir_dir + hfile,
								  ["@echo 'creating " + rulef.build + mocfile + ".moc'",
								   "@$(MOC) $? -o $@"])
				mocs.append(rulef.build + mocfile + ".moc")
				
				match = extre.match(cppfile)
				rulef.insertTarget(rulef.build + match.group(1) + ".o", rulef.build + mocfile + ".moc")
				rulef.insertTarget(rulef.build + match.group(1) + ".lo", rulef.build + mocfile + ".moc")
			else:
				rulef.insertTarget(rulef.build + mocfile + ".moc.cpp", subdir_dir + hfile,
								   "$(MOC) $? -o $@")
				mocs.append(rulef.build + mocfile + ".moc.cpp")

		if len(mocs):
			rulef.add_define(amfile.canon_subdir + "_MOCS", mocs)
			rulef.insertTarget("mocs", "$(%s_MOCS)" % amfile.canon_subdir, phony=1)
			rulef.insertTarget("clean-metasources-%s" % amfile.canon_subdir, "", "-rm -f $(%s_MOCS)" % amfile.canon_subdir, phony=1)
			rulef.insertTarget("clean", "clean-metasources-%s" % amfile.canon_subdir, phony=1)

	def add_meta_objs(self, objs, program):
		for obj in objs:
			program.handle_source(obj + ".moc", ".cpp", 1)

	def collect(self, amfile):
		# adding default targets
		amfile.insertTarget("mocs", "", phony=1)
		
register_handler(MOCHandler())
