"""Utilities for selecting and enumerating the Type Libraries installed on the system
"""

import win32api, win32con, pythoncom

class TypelibSpec:
	def __init__(self, clsid, lcid, major, minor, flags=0):
		self.clsid = str(clsid)
		self.lcid = int(lcid)
		# We avoid assuming 'major' or 'minor' are integers - when
		# read from the registry there is some confusion about if
		# they are base 10 or base 16 (they *should* be base 16, but
		# how they are written is beyond our control.)
		self.major = major
		self.minor = minor
		self.dll = None
		self.desc = None
		self.ver_desc = None
		self.flags = flags
	# For the SelectList
	def __getitem__(self, item):
		if item==0:
			return self.ver_desc
		raise IndexError("Cant index me!")

	def __lt__(self, other): # rich-cmp/py3k-friendly version
		me = (self.ver_desc or "").lower(), (self.desc or "").lower(), self.major, self.minor
		them = (other.ver_desc or "").lower(), (other.desc or "").lower(), other.major, other.minor
		return me < them

	def __eq__(self, other): # rich-cmp/py3k-friendly version
		return ((self.ver_desc or "").lower() == (other.ver_desc or "").lower() and
			(self.desc or "").lower() == (other.desc or "").lower() and
			self.major == other.major and
			self.minor == other.minor)

	def Resolve(self):
		if self.dll is None:
			return 0
		tlb = pythoncom.LoadTypeLib(self.dll)
		self.FromTypelib(tlb, None)
		return 1

	def FromTypelib(self, typelib, dllName = None):
		la = typelib.GetLibAttr()
		self.clsid = str(la[0])
		self.lcid = la[1]
		self.major = la[3]
		self.minor = la[4]
		if dllName:
			self.dll = dllName

def EnumKeys(root):
	index = 0
	ret = []
	while 1:
		try:
			item = win32api.RegEnumKey(root, index)
		except win32api.error:
			break
		try:
			# Note this doesn't handle REG_EXPAND_SZ, but the implementation
			# here doesn't need to - that is handled as the data is read.
			val = win32api.RegQueryValue(root, item)
		except win32api.error:
			val = "" # code using this assumes a string.
			
		ret.append((item, val))
		index = index + 1
	return ret

FLAG_RESTRICTED=1
FLAG_CONTROL=2
FLAG_HIDDEN=4

def EnumTlbs(excludeFlags = 0):
	"""Return a list of TypelibSpec objects, one for each registered library.
	"""
	key = win32api.RegOpenKey(win32con.HKEY_CLASSES_ROOT, "Typelib")
	iids = EnumKeys(key)
	results = []
	for iid, crap in iids:
		try:
			key2 = win32api.RegOpenKey(key, str(iid))
		except win32api.error:
			# A few good reasons for this, including "access denied".
			continue
		for version, tlbdesc in EnumKeys(key2):
			major_minor = version.split('.', 1)
			if len(major_minor) < 2:
				major_minor.append('0')
			# For some reason, this code used to assume the values were hex.
			# This seems to not be true - particularly for CDO 1.21
			# *sigh* - it appears there are no rules here at all, so when we need
			# to know the info, we must load the tlb by filename and request it.
			# The Resolve() method on the TypelibSpec does this.
			# For this reason, keep the version numbers as strings - that
			# way we can't be wrong!  Let code that really needs an int to work
			# out what to do.  FWIW, http://support.microsoft.com/kb/816970 is
			# pretty clear that they *should* be hex.
			major = major_minor[0]
			minor = major_minor[1]
			key3 = win32api.RegOpenKey(key2, str(version))
			try:
				# The "FLAGS" are at this point
				flags = int(win32api.RegQueryValue(key3, "FLAGS"))
			except (win32api.error, ValueError):
				flags = 0
			if flags & excludeFlags==0:
				for lcid, crap in EnumKeys(key3):
					try:
						lcid = int(lcid)
					except ValueError: # not an LCID entry
						continue
					# Only care about "{lcid}\win32" key - jump straight there.
					try:
						key4 = win32api.RegOpenKey(key3, "%s\\win32" % (lcid,))
					except win32api.error:
						continue
					try:
						dll, typ = win32api.RegQueryValueEx(key4, None)
						if typ==win32con.REG_EXPAND_SZ:
							dll = win32api.ExpandEnvironmentStrings(dll)
					except win32api.error:
						dll = None
					spec = TypelibSpec(iid, lcid, major, minor, flags)
					spec.dll = dll
					spec.desc = tlbdesc
					spec.ver_desc = tlbdesc + " (" + version + ")"
					results.append(spec)
	return results

def FindTlbsWithDescription(desc):
	"""Find all installed type libraries with the specified description
	"""
	ret = []
	items = EnumTlbs()
	for item in items:
		if item.desc==desc:
			ret.append(item)
	return ret

def SelectTlb(title="Select Library", excludeFlags = 0):
	"""Display a list of all the type libraries, and select one.   Returns None if cancelled
	"""
	import pywin.dialogs.list
	items = EnumTlbs(excludeFlags)
	# fixup versions - we assume hex (see __init__ above)
	for i in items:
		i.major = int(i.major, 16)
		i.minor = int(i.minor, 16)
	items.sort()
	rc = pywin.dialogs.list.SelectFromLists(title, items, ["Type Library"])
	if rc is None:
		return None
	return items[rc]

# Test code.
if __name__=='__main__':
	print SelectTlb().__dict__
