"""
generic workspace analysis module to seek through the undiscovered
country looking for pointers to interesting things.

in a previous life, this analysis code lived inside VivWorkspace.analyze()
"""
import logging

import envi.exc as e_exc
import visgraph.exc as g_exc
import visgraph.graphcore as g_core
from vivisect.const import RTYPE_BASEPTR, LOC_POINTER

logger = logging.getLogger(__name__)

def analyze(vw):

    logger.info('...analyzing pointers.')

    done = {}

    # Let's analyze Relocations we know are pointers
    for rva, rtype in vw.reloc_by_va.items():
        if rtype != RTYPE_BASEPTR:
            continue

        for xfr, xto, xtype, xinfo in vw.getXrefsFrom(rva):
            logger.debug('pointer(xref): 0x%x -> 0x%x', xfr, xto)
            try:
                vw.analyzePointer(xto)
            except e_exc.SegmentationViolation:
                continue
            done[xfr] = xto

    # Now, we'll analyze the pointers placed by the file wrapper (ELF, PE, MACHO, etc...)
    for pva, tva, fname, pname in vw.getVaSetRows('PointersFromFile'):
        if vw.getLocation(pva) is None:
            if tva is None:
                logger.debug('making pointer(fromFile) 0x%x (%r)', pva, pname)
            else:
                logger.debug('making pointer(fromFile) 0x%x -> 0x%x (%r)', pva, tva, pname)
            vw.makePointer(pva, tva, follow=True)
            done[pva] = tva

    for lva, lsz, lt, li in vw.getLocations(LOC_POINTER):
        tva = vw.readMemoryPtr(lva)
        if not vw.isValidPointer(tva):
            continue

        if vw.getLocation(tva) is not None:
            # event if it's a pointer, we may not have made a name for it
            if vw.getName(lva) is None:
                done[lva] = tva
            continue

        logger.debug('following previously discovered pointer 0x%x -> 0x%x', lva, tva)
        try:
            logger.debug('pointer: 0x%x -> 0x%x', lva, tva)
            vw.followPointer(tva)
            done[lva] = tva
        except Exception as e:
            logger.error('followPointer() failed for 0x%.8x (pval: 0x%.8x) (err: %s)', lva, tva, e)

    # Now, lets find likely free-hanging pointers
    for addr, pval in vw.findPointers():
        if vw.isDeadData(pval):
            continue
        try:
            logger.debug('make pointer(found): 0x%x -> 0x%x', addr, pval)
            vw.makePointer(addr, follow=True)
            done[addr] = pval
        except Exception as e:
            logger.error('makePointer() failed for 0x%.8x (pval: 0x%.8x) (err: %s)', addr, pval, e)

    # link the possible pointers up
    pgraph = g_core.Graph()
    while done:
        ptr, tgt = done.popitem()
        try:
            pgraph.addNode(ptr)
        except g_exc.DuplicateNode:
            pass
        try:
            pgraph.addNode(tgt)
        except g_exc.DuplicateNode:
            pass
        pgraph.addEdgeByNids(ptr, tgt)

    leafs = [node for node in pgraph.getNodes() if pgraph.isLeafNode(node)]
    for leaf in leafs:
        va, props = leaf
        name = vw.getName(va)
        # if there's not a name at the bottom, not work making ptr names
        if not name:
            continue
        links = [(ptr, tgt) for edge, ptr, tgt, props in pgraph.getRefsToByNid(va)]
        while links:
            ptr, tgt = links.pop(0)
            pname = vw.getName(ptr)
            if pname:
                continue
            if not vw.isLocType(ptr, LOC_POINTER):
                continue

            tgtname = vw.getName(tgt)
            if tgtname:
                name = vw._addNamePrefix(tgtname, tgt, 'ptr', '_') + '_%.8x' % ptr
                logger.debug('0x%x: adding name prefix: %r  (%r)', tgt, tgtname, name)
                vw.makeName(ptr, name)

            for edge, n1, n2, props in pgraph.getRefsToByNid(ptr):
                links.append((n1, n2))
