#!/usr/bin/python
#
#    Scyther caching mechanism
#
#    Uses md5 hashes to store previously calculated results.
#
#    (c)2005 Cas Cremers
#
#

import md5
import commands
import os
import sys
import time
from tempfile import NamedTemporaryFile, gettempdir

#----------------------------------------------------------------------------
# Global definitions
#----------------------------------------------------------------------------

# Minimum duration for a test to get into the cache (in seconds)
CacheTimer = 0.1
ScytherProgram = "scyther"
RetrieveFromCache = True
LastArguments = ""

#----------------------------------------------------------------------------
# How to override Scyther program setting
#----------------------------------------------------------------------------

def scytheroverride (newprg):
    global ScytherProgram

    ScytherProgram = newprg
    if not os.path.exists(ScytherProgram):
        print "Cannot find any file at", ScytherProgram, " and it cannot be used as a Scyther executable."
        sys.exit()

def cacheoverride ():
    global RetrieveFromCache

    RetrieveFromCache = False

#----------------------------------------------------------------------------
# How to call Scyther
#----------------------------------------------------------------------------

#    scyther should reside in $PATH
def scythercall (argumentstring, inputfile):
    global ScytherProgram

    clstring = ScytherProgram + " " + argumentstring + " " + inputfile
    (status,scout) = commands.getstatusoutput(clstring)
    return (status,scout)

#----------------------------------------------------------------------------
# Cached evaluation
#----------------------------------------------------------------------------

#    cached results
#    input:    a large string (consisting of read input files)
#    argstring:    a smaller string
def evaluate (argumentstring, inputstring):

    global LastArguments

    def getversion():
        # Determine scyther version
        (status, scout) = scythercall ("--version", "")
        if status == 1 or status < 0:
            # some problem
            print "Problem with determining scyther version!"
            os.exit()
        ll = scout.splitlines()
        versionline = ll[1].split()
        if versionline[0] != "Revision":
            print "Could not find revision ID in:"
            print scout

        return versionline[1]

    def cacheid():
        m = md5.new()
    
        # Determine scyther version
        m.update (version)

        # Add inputfile to hash
        m.update (inputstring)

        # Add arguments to hash
        m.update (argumentstring)

        # Return a readable ID (good for a filename)
        return m.hexdigest()

    # slashcutter
    # Takes 'str': cuts of the first 'depth' strings of length
    # 'width' and puts 'substr' in between
    def slashcutter(str,substr,width,depth):
        res = ""
        while len(str)>width and depth>0:
            res = res + str[0:width] + substr
            str = str[width:]
            depth = depth-1
        return res + str

    # Determine name
    def cachefilename(id):
        fn = gettempdir() + "/scyther/" + version + "/"
        fn += slashcutter(id,"/",3,2)
        fn += ".txt"
        return fn

    # Ensure directory
    def ensureDirectory (path):
        if not os.path.exists(path):
            os.mkdir(path)

    # Ensure directories for a file
    def ensureDirectories (filename):
        for i in range(1,len(filename)):
            if filename[i] == '/':
                np = i+1
                ensureDirectory(filename[:np])

    def compute_and_cache(cachefile):
        # Hmm, we need to compute this result
        # Compute duration (in seconds)
        h = NamedTemporaryFile()
        h.write(inputstring)
        h.flush()
        starttime = time.time()
        (status, scout) = scythercall (argumentstring, h.name)
        duration = time.time() - starttime
        h.close()

        # Only cache if it took some time
        if duration >= CacheTimer:
            # Write cache file even if it's wrong
            ensureDirectories(cachefile)
            f = open(cachefile,'w')
            f.write(scout)
            f.close()

        return (status,scout)

    def retrieve_from_cache(file):
        f = open(file,'r')
        res = f.read()
        f.close()
        # TODO technically, we should store the status in the
        # cache file as well. For now, we just return 0 status.
        #print "Retrieved cached version for [%s]." % argumentstring
        return (0,res)

    # Retrieve version
    version = getversion()

    # Store for later refernce
    LastArguments = argumentstring

    # Determine the unique filename for this test
    cachefile = cachefilename(cacheid())
    if os.path.exists(cachefile) and RetrieveFromCache:
        return retrieve_from_cache(cachefile)
    else:
        return compute_and_cache(cachefile)

#----------------------------------------------------------------------------
# Standalone usage
#----------------------------------------------------------------------------

def main():
    print "This module has currently no standalone functionality."

# Only if main stuff
if __name__ == '__main__':
    main()
