"""Contains knowledge to build a COM object definition.

This module is used by both the @dynamic@ and @makepy@ modules to build
all knowledge of a COM object.

This module contains classes which contain the actual knowledge of the object.
This include parameter and return type information, the COM dispid and CLSID, etc.

Other modules may use this information to generate .py files, use the information
dynamically, or possibly even generate .html documentation for objects.
"""

#
# NOTES: DispatchItem and MapEntry used by dynamic.py.
#        the rest is used by makepy.py
#
#        OleItem, DispatchItem, MapEntry, BuildCallList() is used by makepy

import sys
import string
from keyword import iskeyword

import pythoncom
from pywintypes import TimeType
import winerror
import datetime

# A string ending with a quote can not be safely triple-quoted.
def _safeQuotedString(s):
	if s[-1]=='"': s = s[:-1]+'\\"'
	return '"""%s"""' % s

error = "PythonCOM.Client.Build error"
class NotSupportedException(Exception): pass # Raised when we cant support a param type.
DropIndirection="DropIndirection"

NoTranslateTypes = [
	pythoncom.VT_BOOL,          pythoncom.VT_CLSID,        pythoncom.VT_CY,
	pythoncom.VT_DATE,          pythoncom.VT_DECIMAL,       pythoncom.VT_EMPTY,
	pythoncom.VT_ERROR,         pythoncom.VT_FILETIME,     pythoncom.VT_HRESULT,
	pythoncom.VT_I1,            pythoncom.VT_I2,           pythoncom.VT_I4,
	pythoncom.VT_I8,            pythoncom.VT_INT,          pythoncom.VT_NULL,
	pythoncom.VT_R4,            pythoncom.VT_R8,           pythoncom.VT_NULL,
	pythoncom.VT_STREAM,
	pythoncom.VT_UI1,            pythoncom.VT_UI2,           pythoncom.VT_UI4,
	pythoncom.VT_UI8,            pythoncom.VT_UINT,          pythoncom.VT_VOID,
]

NoTranslateMap = {}
for v in NoTranslateTypes:
	NoTranslateMap[v] = None

class MapEntry:
	"Simple holder for named attibutes - items in a map."
	def __init__(self, desc_or_id, names=None, doc=None, resultCLSID=pythoncom.IID_NULL, resultDoc = None, hidden=0):
		if type(desc_or_id)==type(0):
			self.dispid = desc_or_id
			self.desc = None
		else:
			self.dispid = desc_or_id[0]
			self.desc = desc_or_id

		self.names = names
		self.doc = doc
		self.resultCLSID = resultCLSID
		self.resultDocumentation = resultDoc
		self.wasProperty = 0 # Have I been transformed into a function so I can pass args?
		self.hidden = hidden
	def GetResultCLSID(self):
		rc = self.resultCLSID
		if rc == pythoncom.IID_NULL: return None
		return rc
	# Return a string, suitable for output - either "'{...}'" or "None"
	def GetResultCLSIDStr(self):
		rc = self.GetResultCLSID()
		if rc is None: return "None"
		return repr(str(rc)) # Convert the IID object to a string, then to a string in a string.

	def GetResultName(self):
		if self.resultDocumentation is None:
			return None
		return self.resultDocumentation[0]

class OleItem:
  typename = "OleItem"

  def __init__(self, doc=None):
    self.doc = doc
    if self.doc:
        self.python_name = MakePublicAttributeName(self.doc[0])
    else:
        self.python_name = None
    self.bWritten = 0
    self.bIsDispatch = 0
    self.bIsSink = 0
    self.clsid = None
    self.co_class = None

class DispatchItem(OleItem):
	typename = "DispatchItem"

	def __init__(self, typeinfo=None, attr=None, doc=None, bForUser=1):
		OleItem.__init__(self,doc)
		self.propMap = {}
		self.propMapGet = {}
		self.propMapPut = {}
		self.mapFuncs = {}
		self.defaultDispatchName = None
		self.hidden = 0

		if typeinfo:
			self.Build(typeinfo, attr, bForUser)

	def _propMapPutCheck_(self,key,item):
		ins, outs, opts = self.CountInOutOptArgs(item.desc[2])
		if ins>1: # if a Put property takes more than 1 arg:
			if opts+1==ins or ins==item.desc[6]+1:
				newKey = "Set" + key
				deleteExisting = 0 # This one is still OK
			else:
				deleteExisting = 1 # No good to us
				if key in self.mapFuncs or key in self.propMapGet:
					newKey = "Set" + key
				else:
					newKey = key
			item.wasProperty = 1
			self.mapFuncs[newKey] = item
			if deleteExisting:
				del self.propMapPut[key]

	def _propMapGetCheck_(self,key,item):
		ins, outs, opts = self.CountInOutOptArgs(item.desc[2])
		if ins > 0: # if a Get property takes _any_ in args:
			if item.desc[6]==ins or ins==opts:
				newKey = "Get" + key
				deleteExisting = 0 # This one is still OK
			else:
				deleteExisting = 1 # No good to us
				if key in self.mapFuncs:
					newKey = "Get" + key
				else:
					newKey = key
			item.wasProperty = 1
			self.mapFuncs[newKey] = item
			if deleteExisting:
				del self.propMapGet[key]

	def	_AddFunc_(self,typeinfo,fdesc,bForUser):
		id = fdesc.memid
		funcflags = fdesc.wFuncFlags
		try:
			names = typeinfo.GetNames(id)
			name=names[0]
		except pythoncom.ole_error:
			name = ""
			names = None

		doc = None
		try:
			if bForUser:
				doc = typeinfo.GetDocumentation(id)
		except pythoncom.ole_error:
			pass

		if id==0 and name:
			self.defaultDispatchName = name

		invkind = fdesc.invkind

		# We need to translate any Alias', Enums, structs etc in result and args
		typerepr, flag, defval = fdesc.rettype
#		sys.stderr.write("%s result - %s -> " % (name, typerepr))
		typerepr, resultCLSID, resultDoc = _ResolveType(typerepr, typeinfo)
#		sys.stderr.write("%s\n" % (typerepr,))
		fdesc.rettype = typerepr, flag, defval, resultCLSID
		# Translate any Alias or Enums in argument list.
		argList = []
		for argDesc in fdesc.args:
			typerepr, flag, defval = argDesc
#			sys.stderr.write("%s arg - %s -> " % (name, typerepr))
			arg_type, arg_clsid, arg_doc = _ResolveType(typerepr, typeinfo)
			argDesc = arg_type, flag, defval, arg_clsid
#			sys.stderr.write("%s\n" % (argDesc[0],))
			argList.append(argDesc)
		fdesc.args = tuple(argList)

		hidden = (funcflags & pythoncom.FUNCFLAG_FHIDDEN) != 0
		if invkind == pythoncom.INVOKE_PROPERTYGET:
			map = self.propMapGet
		# This is not the best solution, but I dont think there is
		# one without specific "set" syntax.
		# If there is a single PUT or PUTREF, it will function as a property.
		# If there are both, then the PUT remains a property, and the PUTREF
		# gets transformed into a function.
		# (in vb, PUT=="obj=other_obj", PUTREF="set obj=other_obj
		elif invkind in (pythoncom.INVOKE_PROPERTYPUT, pythoncom.INVOKE_PROPERTYPUTREF):
			# Special case
			existing = self.propMapPut.get(name, None)
			if existing is not None:
				if existing.desc[4]==pythoncom.INVOKE_PROPERTYPUT: # Keep this one
					map = self.mapFuncs
					name = "Set"+name
				else: # Existing becomes a func.
					existing.wasProperty = 1
					self.mapFuncs["Set"+name]=existing
					map = self.propMapPut # existing gets overwritten below.
			else:
				map = self.propMapPut # first time weve seen it.

		elif invkind == pythoncom.INVOKE_FUNC:
			map = self.mapFuncs
		else:
			map = None
		if not map is None: 
#				if map.has_key(name):
#					sys.stderr.write("Warning - overwriting existing method/attribute %s\n" % name)
			map[name] = MapEntry(tuple(fdesc), names, doc, resultCLSID, resultDoc, hidden)
			# any methods that can't be reached via DISPATCH we return None
			# for, so dynamic dispatch doesnt see it.
			if fdesc.funckind != pythoncom.FUNC_DISPATCH:
				return None
			return (name,map)
		return None

	def _AddVar_(self,typeinfo,fdesc,bForUser):
		### need pythoncom.VARFLAG_FRESTRICTED ...
		### then check it

		if fdesc.varkind == pythoncom.VAR_DISPATCH:
			id = fdesc.memid
			names = typeinfo.GetNames(id)
			# Translate any Alias or Enums in result.
			typerepr, flags, defval = fdesc.elemdescVar
			typerepr, resultCLSID, resultDoc = _ResolveType(typerepr, typeinfo)
			fdesc.elemdescVar = typerepr, flags, defval
			doc = None
			try:
				if bForUser: doc = typeinfo.GetDocumentation(id)
			except pythoncom.ole_error:
				pass

			# handle the enumerator specially
			map = self.propMap
			# Check if the element is hidden.
			hidden = 0
			if hasattr(fdesc,"wVarFlags"):
				hidden = (fdesc.wVarFlags & 0x40) != 0 # VARFLAG_FHIDDEN
			map[names[0]] = MapEntry(tuple(fdesc), names, doc, resultCLSID, resultDoc, hidden)
			return (names[0],map)
		else:
			return None

	def Build(self, typeinfo, attr, bForUser = 1):
		self.clsid = attr[0]
		self.bIsDispatch = (attr.wTypeFlags & pythoncom.TYPEFLAG_FDISPATCHABLE) != 0
		if typeinfo is None: return
		# Loop over all methods
		for j in range(attr[6]):
			fdesc = typeinfo.GetFuncDesc(j)
			self._AddFunc_(typeinfo,fdesc,bForUser)

		# Loop over all variables (ie, properties)
		for j in range(attr[7]):