import array, gc, inspect, types, struct
from pprint import pprint as pp

from twisted.python.components import Interface, registerAdapter, Adapter

# Bah on Python
def _getACell(o):
    x = o
    def y():
        y = x
    return [o for o in gc.get_referrers(x) if type(o).__name__ == 'cell'][0]

def f():
    types.CellType = type(_getACell(object()))
f()

import dl
libc = dl.open('/lib/libc.so.6')
def memcpy(dst, src, n):
    if not isinstance(dst, (int, long)):
        dst = id(dst)
    if not isinstance(src, (int, long)):
        src = id(src)
    return libc.call('memcpy', dst, src, n)

def memwrite(dst, bytes):
    if not isinstance(dst, (int, long)):
        dst = id(dst)
    for i, ch in enumerate(bytes):
        ch = ord(ch)
        libc.call('memset', dst + i, ch, 1)

def memread(src, n):
    if not isinstance(src, (int, long)):
        src = id(src)
    a = array.array('c', '\0' * n)
    memcpy(a.buffer_info()[0], src, n)
    return a.tostring()

PTR_SIZE = struct.calcsize('P')

def refcountOffset():
    def f(b):
        x = []
        if b:
            y = x
        else:
            y = None
        return memread(x, 64)
    L1, L2 = f(False), f(True)
    for i, (a, b) in enumerate(zip(L1, L2)):
        if ord(a) + 1 == ord(b):
            return i
    raise RuntimeError("Could not find refcount field offset")

REFCOUNT_OFFSET = refcountOffset()

def tupleOffset():
    x = (object(), object(), object())
    
    bytes = memread(x, 64)
    offset = bytes.find(struct.pack('P', id(x[1])))
    if offset == -1:
        raise RuntimeError("Could not find tuple elements field offset")
    return offset - PTR_SIZE
        
TUPLE_OFFSET = tupleOffset()

def cellPointerOffset():
    a, b, c = object(), object(), object()
    cell = _getACell(b)
    
    bytes = memread(cell, 64)
    offset = bytes.find(struct.pack('P', id(b)))
    if offset == -1:
        raise RuntimeError("Could not find cell pointer field offset")
    return offset
    
CELL_PTR_OFFSET = cellPointerOffset()

def _getRefCount(obj):
    return struct.unpack('=i', memread(id(obj) + REFCOUNT_OFFSET, PTR_SIZE))[0]

def _setRefCount(obj, count):
    return memwrite(id(obj) + REFCOUNT_OFFSET, struct.pack('=i', count))

# These functions are correct.  Don't touch them, you idiot.
def Py_Incref(obj):
    count = _getRefCount(obj)
    _setRefCount(obj, count + 1)

def Py_Decref(obj):
    count = _getRefCount(obj)
    _setRefCount(obj, count - 1)

class IBecomer(Interface):
    def become(self, orig, new):
        """Replace a reference to an existing object with a reference to the given object.
        """

class Becomer(Adapter):
    temporaryAdapter = True

class CellBecomer(Becomer):
    def become(self, orig, new):
        ptr = struct.unpack('P', memread(id(self.original) + CELL_PTR_OFFSET, PTR_SIZE))[0]
        if ptr == id(orig):
            Py_Incref(new)
            memwrite(id(self.original) + CELL_PTR_OFFSET, struct.pack('P', id(new)))
            Py_Decref(orig)

registerAdapter(CellBecomer, types.CellType, IBecomer)

class DictBecomer(Becomer):
    def _set(self, key, value):
        self.original[key] = value
    
    def _del(self, key):
        del self.original[key]

    def become(self, orig, new):
        for (k, v) in self.original.items():
            if k is v is orig:
                self._del(k)
                self._set(new, new)
            elif k is orig:
                self._del(k)
                self._set(new, v)
            elif v is orig:
                self._set(k, new)

registerAdapter(DictBecomer, types.DictType, IBecomer)

class ListBecomer(Becomer):
    def become(self, orig, new):
        for i, v in enumerate(self.original):
            if v is orig:
                self.original[i] = new
registerAdapter(ListBecomer, types.ListType, IBecomer)


class TupleBecomer(Becomer):
    def _set(self, idx, value):
        Py_Incref(value)
        old = self.original[idx]
        memwrite(id(self.original) + TUPLE_OFFSET + (idx * PTR_SIZE), struct.pack('=i', id(value)))
        Py_Decref(old)
        
    def become(self, orig, new):
        for i, v in enumerate(self.original):
            if v is orig:
                self._set(i, new)
registerAdapter(TupleBecomer, types.TupleType, IBecomer)

def nLocalsOffset():
    def f():
        x = None
        return inspect.currentframe()
    def g():
        x = y = None
        return inspect.currentframe()
    def h():
        x = y = z = None
        return inspect.currentframe()
    f1, f2, f3 = f(), g(), h()
    for i, (a, b, c) in enumerate(zip(*[map(ord, memread(o, 512)) for o in (f1, f2, f3)])):
        if a + 2 == b + 1 == c:
            return i
    raise RuntimeError("Failed to find nlocals field offset")

NLOCALS_OFFSET = nLocalsOffset()
NCELLS_OFFSET = NLOCALS_OFFSET + PTR_SIZE
NFREEVARS_OFFSET = NCELLS_OFFSET + PTR_SIZE
STACKSIZE_OFFSET = NFREEVARS_OFFSET + PTR_SIZE
FASTLOCALS_OFFSET = STACKSIZE_OFFSET + PTR_SIZE

class FrameBecomer(Becomer):
    def become(self, orig, new):
        if not self.original.f_locals.get('initial_become_frame_please_do_not_twiddle_me'):
            stackSize = 0
            for offset in NLOCALS_OFFSET, NCELLS_OFFSET, NFREEVARS_OFFSET, STACKSIZE_OFFSET:
                stackSize += struct.unpack('=i', memread(id(frame) + offset, PTR_SIZE))[0]

            offBase = id(frame) + FASTLOCALS_OFFSET
            for idx in range(stackSize):
                off = offBase + (idx * PTR_SIZE)
                ptr = struct.unpack('P', memread(off, PTR_SIZE))[0]
                if ptr == id(orig):
                    Py_Incref(new)
                    memwrite(off, struct.pack('P', id(new)))
                    Py_Decref(orig)

registerAdapter(FrameBecomer, types.FrameType, IBecomer)

def become(x, y):
    """Replace all references to x with references to y
    """
    initial_become_frame_please_do_not_twiddle_me = True
    referrers = gc.get_referrers(x)
    for r in referrers:
        a = IBecomer(r, persist=False)
        a.become(x, y)

def test():
    testObj = 'test Obj Value'
    testObjID = id(testObj)

    class Class:
        classAttr = testObj
    
        def __init__(self):
            self.instanceAttr = testObj
    instance = Class()
    
    
    listElement = [testObj]
    tupleElement = (testObj,)
    dictKey = {testObj: None}
    dictValue = {None: testObj}
    
    def defaultArgumentValue(x=testObj):
        pass
    
    newObj = 'new Obj Value'
    newObjID = id(newObj)

    become(testObj, newObj)
    
    assert Class.classAttr is newObj
    assert instance.instanceAttr is newObj
    assert listElement[0] is newObj
    assert tupleElement[0] is newObj
    assert dictKey.keys()[0] is newObj
    assert dictValue[None] is newObj
    assert inspect.getargspec(defaultArgumentValue)[3][0] is newObj
    assert newObj is testObj
    assert id(newObj) == newObjID
    assert id(testObj) == newObjID
    
    print 'Tests passed'

__all__ = ['become', 'test']

if __name__ == '__main__':
    test()


