# -*- coding: utf-8 -*-

"""
Module implementing a function to run the Cyclops cycle finder on a module. 
"""

import HTMLCyclops
try:
    from DebugClients.Python.DebugProtocol import ResponseCyclopsError
except ImportError:
    from DebugProtocol import ResponseCyclopsError

import types, sys, string
import os.path

def mod_refs(x):
    """
    Function returning a sequence of all objects directly reachable from x.
    
    @param x object to check
    @return sequence of all objects directly reachable from x
    """
    return x.__dict__.values()

def mod_tag(x, i):
    """
    Function returning a string describing how the reference was obtained from x.
    
    @param x object to check
    @param i index into list of references
    @return a string describing how the reference was obtained from x
    """
    return "." + x.__dict__.keys()[i]

def func_refs(x):
    """
    Function returning a sequence of all objects directly reachable from x.
    
    @param x object to check
    @return sequence of all objects directly reachable from x
    """
    return x.func_globals, x.func_defaults

def func_tag(x, i):
    """
    Function returning a string describing how the reference was obtained from x.
    
    @param x object to check
    @param i index into list of references
    @return a string describing how the reference was obtained from x
    """
    return (".func_globals", ".func_defaults")[i]

def instance_filter(cycle):
    """
    Function to filter the cycles.
    
    @return flag indicating that at least one instance object was found
    """
    for obj, index in cycle:
        if type(obj) is types.InstanceType:
            return 1
    return 0

def generateReport(z, reports):
    """
    Function to generate the report.
    """
    z.find_cycles()

    if reports & CyclopsStats:
        z.show_stats(z.stats_list())  # Statistics
    if reports & CyclopsCycles:
        z.show_cycles()               # All cycles
    if reports & CyclopsCycleObjs:
        z.show_cycleobjs()            # Objects involved in cycles
    if reports & CyclopsSCCS:
        z.show_sccs()                 # Cycle objects partitioned into maximal SCCs
    if reports & CyclopsArcs:
        z.show_arcs()                 # Arc types involved in cycles
    if reports & CyclopsIterate:
        z.iterate_til_steady_state(show_objs=0) # Repeatedly purge until there are no more dead roots
        
def run(filename, modfunc, reports, dbgClient):
    """
    Function to run the program in the cyclops cycle finder.
    
    @param filename filename of the module to be run (string)
    @param modfunc module function which is the main entry point (string)
    @param reports bit mask specifying the reports wanted (integer)
    """
    mod_name = os.path.splitext(filename)[0]
    f = open(mod_name+'.cycles.html', 'w')
    mod_name = os.path.basename(mod_name)
    try:
        z = HTMLCyclops.CycleFinderHTML()
        mod = __import__(mod_name)

        # Comment out any of the following lines to not add a chaser or filter
        z.chase_type(types.ModuleType, mod_refs, mod_tag)
        z.chase_type(types.FunctionType, func_refs, func_tag)
        z.install_cycle_filter(instance_filter)

        if not hasattr(mod, modfunc):
            dbgClient.write('%s%s\n' % (ResponseCyclopsError, \
                str((filename, modfunc))))
        else:
            # Execute the module and trace the first round of cycles
            try:
                exec "z.run(mod.%s)" % modfunc
            except SystemExit:
                generateReport(z, reports)

                # Write out the report
                f.write(z.get_page())
            except:
                import traceback

                tp, vl, tb = sys.exc_info()
                err = '<font color="#FF4444"><h3>Error:</h3></font>'+\
                  string.join(traceback.format_exception(tp, vl, tb), '<br>')
                f.write(err)
            else:
                generateReport(z, reports)

                # Write out the report
                f.write(z.get_page())
    finally:
        f.close()

# variable defining the different report types
CyclopsStats = 1 << 0
CyclopsCycles = 1 << 1
CyclopsCycleObjs = 1 << 2
CyclopsSCCS = 1 << 3
CyclopsArcs = 1 << 4
CyclopsIterate = 1 << 5
