#!/usr/bin/env python
import inspect, os, cPickle
#import inspect, os, pickle

def cachefn( pickledir="pickles", retval=None, args=None):
    callee_stack = inspect.stack()[1]
    callee_name = callee_stack[3]

    # if no arguments are passed, use all from the calling function
    # FIXME: this probably breaks when there are non-hashable arguments
    
    # FIXME: None is not guaranteed to hash the same.  It depends
    # upon a memory address that changes, by instance and machine.
    # We'll substitute the string "None" for None objects.  Of course,
    # this could pose a problem when both None and "None" are
    # reasonable arguments.

    # It's difficult to replace None in every possible object,
    # recursively.  Maybe it's just easier to use a string of all arguments

    #print "callee_name", callee_name
    #print "args", args
    #print "type(args)", type(args)
    
    if args is None:
        argvalues = inspect.getargvalues( callee_stack[0])
        args = argvalues[0]
        params_dict = {}
        for arg in args:
            if arg == 'self': continue
            params_dict[ arg] = argvalues[3][arg]
            
        params_items = params_dict.items()
        params_items = [ "None" if a is None else a for a in params_items]
        params_items.sort()
        params_tup = tuple( params_items)
        params_str = str( params_tup)
        
        # WARNING: None hashes to id(), the address of an object.  So,
        # at different runs, and especially on different machines, it
        # is possible for hashed objects that contain None to result
        # in different hashes.  I'm not sure how to be more
        # deterministic about this.
        params_hash = str(hash( params_str))
        
    # if args is a string, don't bother hashing.
    # WARNING: no spaces!    
    elif type(args) is str:
        params_hash = args

    else:
        params_tup = tuple(args)
        params_str = str( params_tup)
        params_hash = str(hash( params_str))
    
    fname = callee_name+'-'+params_hash+'.pickle'
    fpathname = os.path.join( pickledir, fname)

    #
    #if type(args) is not str:
    #    print params_tup
    #    print params_str, type(params_str)
    #print params_hash
    #print fpathname
    
    # store the pickle
    if retval:
        # FIXME: detect and warn about other access problems
        if not os.access( pickledir, os.W_OK):
            os.makedirs( pickledir)
        
        fd = open( fpathname, 'wb')
        cPickle.dump( retval, fd, protocol=2)
        fd.close()
        return None

    # load the pickle
    else:
        if os.access(fpathname, os.R_OK):
            #print "returning value"
            #print fpathname
            fd = open( fpathname, 'rb')
            retval = cPickle.load( fd)
            fd.close()
            return retval

        #print "returning None"
        return None
