'''
Logging service for tracking dr tree changes from root objective 
and record every step that incrementally changes the dr tree

'''
import os, sys, time
import json
import psutil

import scipy.sparse as sp
import numpy as np
import reordering

_TWO_20 = float(2 **20)

'''
memory utils

'''
def pdb_mem():
    from monitor import get_current_memory
    mem = get_current_memory()
    if mem > 7000:
        import pdb;pdb.set_trace()

def get_peak_mem():
    '''
    this returns peak memory use since process starts till the moment its called
    '''
    import resource
    rusage_denom = 1024.
    if sys.platform == 'darwin':
        # ... it seems that in OSX the output is different units ...
        rusage_denom = rusage_denom * rusage_denom
    mem = resource.getrusage(resource.RUSAGE_SELF).ru_maxrss / rusage_denom
    return mem

def get_current_memory():
    p = psutil.Process(os.getpid())
    mem = p.memory_info()[0]/_TWO_20

    return mem

'''
Helper for Profiler
'''

def build_cache_info(k, v, info_dict):
    if v is not None:
        issparse = sp.issparse(v)
        size = v.size
        if issparse:
            nonzero = len(v.data)
        else:
            nonzero = np.count_nonzero(v)
        info_dict[k.short_name] = {
            'sparse': issparse,
            'size' : str(size),
            'nonzero' : nonzero,
        }


def cache_info(ch_node):
    result = {}
    if isinstance(ch_node, reordering.Concatenate) and hasattr(ch_node, 'dr_cached') and len(ch_node.dr_cached) > 0:
        for k, v in ch_node.dr_cached.iteritems():
            build_cache_info(k, v, result)
    elif len(ch_node._cache['drs']) > 0:
        for k, v in ch_node._cache['drs'].iteritems():
            build_cache_info(k, v, result)

    return result

class DrWrtProfiler(object):
    base_path = os.path.abspath('profiles')
    
    def __init__(self, root, base_path=None):
        self.root = root.obj
        self.history = []

        ts = time.time()
        if base_path:
            self.base_path = base_path
                       
        self.path = os.path.join(self.base_path, 'profile_%s.json' % str(ts))
        self.root_path = os.path.join(self.base_path, 'root_%s.json' % str(ts))

        
        with open(self.root_path, 'w') as f:
            json.dump(self.dump_tree(self.root), f, indent=4)

    def dump_tree(self, node):
        if not hasattr(node, 'dterms'):
            return []

        node_dict = self.serialize_node(node, verbose=False)
        if hasattr(node, 'visited') and node.visited:
            node_dict.update({'indirect':True})
            return node_dict

        node.visited = True
        children_list = []
        for dterm in node.dterms:
            if hasattr(node, dterm):
                child = getattr(node, dterm)
                if hasattr(child, 'dterms') or hasattr(child, 'terms'):
                    children_list.append(self.dump_tree(child))
        node_dict.update({'children':children_list})
        return node_dict

    def serialize_node(self, ch_node, verbose=True):
        node_id = id(ch_node)
        name = ch_node.short_name
        ts = time.time()
        status = ch_node._status
        mem = get_current_memory()
        node_cache_info = cache_info(ch_node)

        rec = {
            'id': str(node_id),
            'indirect' : False,
        }
        if verbose:
            rec.update({
                'name':name,
                'ts' : ts,
                'status':status,
                'mem': mem,
                'cache': node_cache_info,
            })
        return rec

    def show_tree(self, label):
        '''
        show tree from the root node
        '''
        self.root.show_tree_cache(label)

    def record(self, ch_node):
        '''
        Incremental changes
        '''
        rec = self.serialize_node(ch_node)
        self.history.append(rec)

    def harvest(self):
        print 'collecting and dump to file %s' % self.path
        with open(self.path, 'w') as f:
            json.dump(self.history, f, indent=4)