# -*- coding: utf-8 -*-
#######################################################################################
#   Photo is a part of Plinn - http://plinn.org                                       #
#   Copyright (C) 2008  Benoît PIN <benoit.pin@ensmp.fr>                              #
#                                                                                     #
#   This program is free software; you can redistribute it and/or                     #
#   modify it under the terms of the GNU General Public License                       #
#   as published by the Free Software Foundation; either version 2                    #
#   of the License, or (at your option) any later version.                            #
#                                                                                     #
#   This program 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 General Public License for more details.                                      #
#                                                                                     #
#   You should have received a copy of the GNU General Public License                 #
#   along with this program; if not, write to the Free Software                       #
#   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.   #
#######################################################################################
""" Memoization utils



"""

import inspect
from BTrees.OOBTree import OOBTree

def memoizedmethod(*indexes, **options) :
	""" Used as decorator, this function stores result
		of method m inside self._methodResultsCache or
		self._v__methodResultsCache if volatile.
		This decorator may be used inside a class which provides
		a mapping object named _methodResultsCache and / or
		_v__methodResultsCache.
		
		example :
		
		1 - simple metdhod memoization
		
		@memoizedmethod()
		def methodWithNoParameters(self): pass
		
		2 - indexed memoisation:
		Parameters names are passed to memoizedmethod are
		evaluated to construct an indexed cache.
		Names must be a subset of the memoized method signature.
		
		@memoizedmethod('arg1', 'arg2')
		def methodWithParameters(self, arg1, arg2=None): pass
	"""
	volatile = options.get('volatile', False)
	cacheVarName = '_methodResultsCache'
	if volatile==True :
		cacheVarName = '_v_%s' % cacheVarName
	
	def makeMemoizedMethod(m) :
		methodname = m.__name__
		
		if not indexes :
			def memoizedMethod(self) :
				if not hasattr(self, cacheVarName) :
					setattr(self, cacheVarName, OOBTree())
				cache = getattr(self, cacheVarName)
				if cache.has_key(methodname) :
					return cache[methodname]
				else :
					res = m(self)
					cache[methodname] = res
					return res

			memoizedMethod.__name__ = methodname
			memoizedMethod.__doc__ = m.__doc__
			return memoizedMethod
		
		else :
			args, varargs, varkw, defaults = inspect.getargspec(m)
			args = list(args)
			if defaults is None :
				defaults = []
			mandatoryargs = args[1:-len(defaults)]
			optargs = args[-len(defaults):]
			defaultValues = dict(zip([name for name in args[-len(defaults):]], [val for val in defaults]))
			
			indexPositions = []
			for index in indexes :
				try :
					indexPositions.append((index, args.index(index)))
				except ValueError :
					raise ValueError("%r argument is not in signature of %r" % (index, methodname))
			
			if indexPositions :
				indexPositions.sort(lambda a, b : cmp(a[1], b[1]))
			
			indexPositions = tuple(indexPositions)
				
			
			def memoizedMethod(self, *args, **kw) :
				# test if m if called by ZPublished
				if len(args) < len(mandatoryargs) and hasattr(self, 'REQUEST') :
					assert not kw
					args = list(args)
					get = lambda name : self.REQUEST[name]
					for name in mandatoryargs :
						try :
							args.append(get(name))
						except KeyError :
							exactOrAtLeast = defaults and 'exactly' or 'at least'
							raise TypeError('%(methodname)s takes %(exactOrAtLeast)s %(mandatoryArgsLength)d argument (%(givenArgsLength)s given)' % \
											{ 'methodname': methodname
											, 'exactOrAtLeast': exactOrAtLeast
											, 'mandatoryArgsLength': len(mandatoryargs)
											, 'givenArgsLength': len(args)})
					
					for name in optargs :
						get = self.REQUEST.get
						args.append(get(name, defaultValues[name]))

					args = tuple(args)
				
				if not hasattr(self, cacheVarName) :
					setattr(self, cacheVarName, OOBTree())
				cache = getattr(self, cacheVarName)
				if not cache.has_key(methodname) :
					cache[methodname] = OOBTree()
				
				cache = cache[methodname]
				index = aggregateIndex(indexPositions, args)
				
				if cache.has_key(index) :
					return cache[index]
				else :
					res = m(self, *args, **kw)
					cache[index] = res
					return res

			memoizedMethod.__name__ = methodname
			memoizedMethod.__doc__ = m.__doc__
			return memoizedMethod

	return makeMemoizedMethod

def aggregateIndex(indexPositions, args):
	'''
	Returns the index to be used when looking for or inserting
	a cache entry.
	view_name is a string.
	local_keys is a mapping or None.
	'''
	
	agg_index = []
	
	for name, pos in indexPositions :
		val = args[pos-1]
		agg_index.append((name, str(val)))
	
	return tuple(agg_index) 
