"""
Defines a class value which encapsulates the basic lldb Scripting Bridge APIs. This provides an easy
wrapper to extract information from C based constructs.
 |------- core.value------------|
 | |--lldb Scripting Bridge--|  |
 | |    |--lldb core--|      |  |
 | |-------------------------|  |
 |------------------------------|
Use the member function GetSBValue() to access the base Scripting Bridge value.
"""
import lldb
import re
from lazytarget import *

_cstring_rex = re.compile("((?:\s*|const\s+)\s*char(?:\s+\*|\s+[A-Za-z_0-9]*\s*\[|)\s*)",re.MULTILINE|re.DOTALL)

class value(object):
    '''A class designed to wrap lldb.SBValue() objects so the resulting object
    can be used as a variable would be in code. So if you have a Point structure
    variable in your code in the current frame named "pt", you can initialize an instance
    of this class with it:

    pt = lldb.value(lldb.frame.FindVariable("pt"))
    print pt
    print pt.x
    print pt.y

    pt = lldb.value(lldb.frame.FindVariable("rectangle_array"))
    print rectangle_array[12]
    print rectangle_array[5].origin.x'''
    def __init__(self, sbvalue):
        #_sbval19k84obscure747 is specifically chosen to be obscure.
        #This avoids conflicts when attributes could mean any field value in code
        self._sbval19k84obscure747 = sbvalue
        self._sbval19k84obscure747_type = sbvalue.GetType()
        self._sbval19k84obscure747_is_ptr = sbvalue.GetType().IsPointerType()
        self.sbvalue = sbvalue

    def __nonzero__(self):
        return ( self._sbval19k84obscure747.__nonzero__() and self._GetValueAsUnsigned() != 0 )

    def __repr__(self):
        return self._sbval19k84obscure747.__str__()

    def __cmp__(self, other):
        if type(other) is int or type(other) is long:
            me = int(self)
            if type(me) is long:
                other = long(other)
            return me.__cmp__(other)
        if type(other) is value:
            try:
                return int(self).__cmp__(int(other))
            except TypeError: # Try promoting to long
                return long(self).__cmp__(long(other))
        raise TypeError("Cannot compare value with type {}".format(type(other)))

    def __str__(self):
        global _cstring_rex
        type_name = self._sbval19k84obscure747_type.GetName()
        if len(_cstring_rex.findall(type_name)) > 0 :
            return self._GetValueAsString()
        summary = self._sbval19k84obscure747.GetSummary()
        if summary:
            return summary.strip('"')
        return self._sbval19k84obscure747.__str__()

    def __getitem__(self, key):
        # Allow array access if this value has children...
        if type(key) is slice:
            _start = int(key.start)
            _end = int(key.stop)
            _step = 1
            if key.step != None:
                _step = int(key.step)
            retval = []
            while _start < _end:
                retval.append(self[_start])
                _start += _step
            return retval
        if type(key) in (int, long):
            return value(self._sbval19k84obscure747.GetValueForExpressionPath("[%i]" % key))
        if type(key) is value:
            return value(self._sbval19k84obscure747.GetValueForExpressionPath("[%i]" % int(key)))
        raise TypeError("Cannot fetch Array item for this type")

    def __getattr__(self, name):
        child_sbvalue = self._sbval19k84obscure747.GetChildMemberWithName (name)
        if child_sbvalue:
            return value(child_sbvalue)
        raise AttributeError("No field by name: "+name )

    def __add__(self, other):
        return int(self) + int(other)

    def __radd__(self, other):
        return int(self) + int(other)

    def __sub__(self, other):
        return int(self) - int(other)

    def __rsub__(self, other):
        return int(other) - int(self)

    def __mul__(self, other):
        return int(self) * int(other)

    def __rmul__(self, other):
        return int(self) * int(other)

    def __floordiv__(self, other):
        return int(self) // int(other)

    def __mod__(self, other):
        return int(self) % int(other)

    def __rmod__(self, other):
        return int(other) % int(self)

    def __divmod__(self, other):
        return int(self) % int(other)

    def __rdivmod__(self, other):
        return int(other) % int(self)

    def __pow__(self, other):
        return int(self) ** int(other)

    def __lshift__(self, other):
        return int(self) << int(other)

    def __rshift__(self, other):
        return int(self) >> int(other)

    def __and__(self, other):
        return int(self) & int(other)

    def __rand(self, other):
        return int(self) & int(other)

    def __xor__(self, other):
        return int(self) ^ int(other)

    def __or__(self, other):
        return int(self) | int(other)

    def __div__(self, other):
        return int(self) / int(other)

    def __rdiv__(self, other):
        return int(other)/int(self)

    def __truediv__(self, other):
        return int(self) / int(other)

    def __iadd__(self, other):
        result = self.__add__(other)
        self._sbval19k84obscure747.SetValueFromCString (str(result))
        return result

    def __isub__(self, other):
        result = self.__sub__(other)
        self._sbval19k84obscure747.SetValueFromCString (str(result))
        return result

    def __imul__(self, other):
        result = self.__mul__(other)
        self._sbval19k84obscure747.SetValueFromCString (str(result))
        return result

    def __idiv__(self, other):
        result = self.__div__(other)
        self._sbval19k84obscure747.SetValueFromCString (str(result))
        return result

    def __itruediv__(self, other):
        result = self.__truediv__(other)
        self._sbval19k84obscure747.SetValueFromCString (str(result))
        return result

    def __ifloordiv__(self, other):
        result =  self.__floordiv__(self, other)
        self._sbval19k84obscure747.SetValueFromCString (str(result))
        return result

    def __imod__(self, other):
        result =  self.__and__(self, other)
        self._sbval19k84obscure747.SetValueFromCString (str(result))
        return result

    def __ipow__(self, other):
        result = self.__pow__(self, other)
        self._sbval19k84obscure747.SetValueFromCString (str(result))
        return result

    def __ipow__(self, other, modulo):
        result = self.__pow__(self, other, modulo)
        self._sbval19k84obscure747.SetValueFromCString (str(result))
        return result

    def __ilshift__(self, other):
        result = self.__lshift__(other)
        self._sbval19k84obscure747.SetValueFromCString (str(result))
        return result

    def __irshift__(self, other):
        result =  self.__rshift__(other)
        self._sbval19k84obscure747.SetValueFromCString (str(result))
        return result

    def __iand__(self, other):
        result =  self.__and__(self, other)
        self._sbval19k84obscure747.SetValueFromCString (str(result))
        return result

    def __ixor__(self, other):
        result =  self.__xor__(self, other)
        self._sbval19k84obscure747.SetValueFromCString (str(result))
        return result

    def __ior__(self, other):
        result =  self.__ior__(self, other)
        self._sbval19k84obscure747.SetValueFromCString (str(result))
        return result

    def __neg__(self):
        return -int(self)

    def __pos__(self):
        return +int(self)

    def __abs__(self):
        return abs(int(self))

    def __invert__(self):
        return ~int(self)

    def __complex__(self):
        return complex (int(self))

    def __int__(self):
        if self._sbval19k84obscure747_is_ptr:
            return self._GetValueAsUnsigned()
        tname= self._sbval19k84obscure747_type.GetName()
        if tname.find('uint') >= 0 or tname.find('unsigned') >= 0:
            return self._GetValueAsUnsigned()
        retval = self._sbval19k84obscure747.GetValueAsSigned()
        # <rdar://problem/12481949> lldb python: GetValueAsSigned does not return the correct value
        if (retval & 0x80000000):
            retval = retval - 0x100000000
        return retval

    def __long__(self):
        return self._sbval19k84obscure747.GetValueAsSigned()

    def __float__(self):
        return float (self._sbval19k84obscure747.GetValueAsSigned())

    def __oct__(self):
        return '0%o' % self._GetValueAsUnsigned()

    def __hex__(self):
        return '0x%x' % self._GetValueAsUnsigned()

    def __eq__(self, other):
        self_val = self._GetValueAsUnsigned()
        if type(other) is value:
            other_val = other._GetValueAsUnsigned()
            return self_val == other_val
        if type(other) is int:
            return int(self) == other
        raise TypeError("Equality operation is not defined for this type.")

    def __neq__(self, other):
        return not self.__eq__(other)

    def GetSBValue(self):
        return self._sbval19k84obscure747

    def __getstate__(self):
        err = lldb.SBError()
        if self._sbval19k84obscure747_is_ptr:
            addr = self._sbval19k84obscure747.GetValueAsAddress()
            size = self._sbval19k84obscure747_type.GetPointeeType().GetByteSize()
        else:
            addr = self._sbval19k84obscure747.AddressOf().GetValueAsAddress()
            size = self._sbval19k84obscure747_type.GetByteSize()

        content = LazyTarget.GetProcess().ReadMemory(addr, size, err)
        if err.fail:
            content = ''
        return content

    def _GetValueAsSigned(self):
        if self._sbval19k84obscure747_is_ptr:
            print "ERROR: You cannot get 'int' from pointer type %s, please use unsigned(obj) for such purposes." % str(self._sbval19k84obscure747_type)
            raise ValueError("Cannot get signed int for pointer data.")
        serr = lldb.SBError()
        retval = self._sbval19k84obscure747.GetValueAsSigned(serr)
        if serr.success:
            return retval
        raise ValueError("Failed to read signed data. "+ str(self._sbval19k84obscure747) +"(type =" + str(self._sbval19k84obscure747_type) + ") Error description: " + serr.GetCString())

    def _GetValueAsCast(self, dest_type):
        if type(dest_type) is not lldb.SBType:
            raise ValueError("Invalid type for dest_type: {}".format(type(dest_type)))
        addr = self._GetValueAsUnsigned()
        sbval = self._sbval19k84obscure747.target.CreateValueFromExpression("newname", "(void *)"+str(addr))
        val = value(sbval.Cast(dest_type))
        return val

    def _GetValueAsUnsigned(self):
        serr = lldb.SBError()
        if self._sbval19k84obscure747_is_ptr:
            retval = self._sbval19k84obscure747.GetValueAsAddress()
        else:
            retval = self._sbval19k84obscure747.GetValueAsUnsigned(serr)
        if serr.success:
            return retval
        raise ValueError("Failed to read unsigned data. "+ str(self._sbval19k84obscure747) +"(type =" + str(self._sbval19k84obscure747_type) + ") Error description: " + serr.GetCString())

    def _GetValueAsString(self, offset = 0, maxlen = 1024):
        serr = lldb.SBError()
        sbdata = None
        if self._sbval19k84obscure747_is_ptr:
            sbdata = self._sbval19k84obscure747.GetPointeeData(offset, maxlen)
        else:
            sbdata = self._sbval19k84obscure747.GetData()

        retval = ''
        bytesize = sbdata.GetByteSize()
        if bytesize == 0 :
            #raise ValueError('Unable to read value as string')
            return ''
        for i in range(0, bytesize) :
            serr.Clear()
            ch = chr(sbdata.GetUnsignedInt8(serr, i))
            if serr.fail :
                raise ValueError("Unable to read string data: " + serr.GetCString())
            if ch == '\0':
                break
            retval += ch
        return retval

    def __format__(self, format_spec):
        ret_format = "{0:"+format_spec+"}"
        # typechar is last char. see http://www.python.org/dev/peps/pep-3101/
        type_spec=format_spec.strip().lower()[-1]
        if type_spec == 'x':
            return ret_format.format(self._GetValueAsUnsigned())
        if type_spec == 'd':
            return ret_format.format(int(self))
        if type_spec == 's':
            return ret_format.format(str(self))
        if type_spec == 'o':
            return ret_format.format(int(oct(self)))
        if type_spec == 'c':
            return ret_format.format(int(self))

        return "unknown format " + format_spec + str(self)


def unsigned(val):
    """ Helper function to get unsigned value from core.value
        params: val - value (see value class above) representation of an integer type
        returns: int which is unsigned.
        raises : ValueError if the type cannot be represented as unsigned int.
    """
    if type(val) is value:
        return val._GetValueAsUnsigned()
    return int(val)

def sizeof(t):
    """ Find the byte size of a type.
        params: t - str : ex 'time_spec' returns equivalent of sizeof(time_spec) in C
                t - value: ex a value object. returns size of the object
        returns: int - byte size length
    """
    if type(t) is value :
        return t.GetSBValue().GetByteSize()
    if type(t) is str:
        return gettype(t).GetByteSize()
    raise ValueError("Cannot get sizeof. Invalid argument")


def dereference(val):
    """ Get a dereferenced obj for a pointer type obj
        params: val - value object representing a pointer type C construct in lldb
        returns: value - value
        ex. val = dereference(ptr_obj) #python
        is same as
            obj_ptr = (int *)0x1234  #C
            val = *obj_ptr           #C
    """
    if type(val) is value and val._sbval19k84obscure747_is_ptr:
        return value(val.GetSBValue().Dereference())
    raise TypeError('Cannot dereference this type.')

def addressof(val):
    """ Get address of a core.value object.
        params: val - value object representing a C construct in lldb
        returns: value - value object referring to 'type(val) *' type
        ex. addr = addressof(hello_obj)  #python
        is same as
           uintptr_t addr = (uintptr_t)&hello_obj  #C
    """
    if type(val) is value:
        return value(val.GetSBValue().AddressOf())
    raise TypeError("Cannot do addressof for non-value type objects")

def cast(obj, target_type):
    """ Type cast an object to another C type.
        params:
            obj - core.value  object representing some C construct in lldb
            target_type - str : ex 'char *'
                        - lldb.SBType :
    """
    dest_type = target_type
    if type(target_type) is str:
        dest_type = gettype(target_type)
    elif type(target_type) is value:
        dest_type = target_type.GetSBValue().GetType()

    if type(obj) is value:
        return obj._GetValueAsCast(dest_type)
    elif type(obj) is int:
        print "ERROR: You cannot cast an 'int' to %s, please use kern.GetValueFromAddress() for such purposes." % str(target_type)
    raise TypeError("object of type %s cannot be casted to %s" % (str(type(obj)), str(target_type)))

def containerof(obj, target_type, field_name):
    """ Type cast an object to another C type from a pointer to a field.
        params:
            obj - core.value  object representing some C construct in lldb
            target_type - str : ex 'struct thread'
                        - lldb.SBType :
            field_name - the field name within the target_type obj is a pointer to
    """
    addr = int(obj) - getfieldoffset(target_type, field_name)
    obj = value(obj.GetSBValue().CreateValueFromExpression(None,'(void *)'+str(addr)))
    return cast(obj, target_type + " *")


_value_types_cache={}

def gettype(target_type):
    """ Returns lldb.SBType of the given target_type
        params:
            target_type - str, ex. 'char', 'uint32_t' etc
        returns:
            lldb.SBType - SBType corresponding to the given target_type
        raises:
            NameError  - Incase the type is not identified
    """
    global _value_types_cache
    target_type = str(target_type).strip()
    if target_type in _value_types_cache:
        return _value_types_cache[target_type]

    target_type = target_type.strip()

    requested_type_is_struct = False
    m = re.match(r'\s*struct\s*(.*)$', target_type)
    if m:
        requested_type_is_struct = True
        target_type = m.group(1)

    tmp_type = None
    requested_type_is_pointer = False
    if target_type.endswith('*') :
        requested_type_is_pointer = True

    # tmp_type = LazyTarget.GetTarget().FindFirstType(target_type.rstrip('*').strip())
    search_type = target_type.rstrip('*').strip()
    type_arr = [t for t in LazyTarget.GetTarget().FindTypes(search_type)]

    if requested_type_is_struct:
        type_arr = [t for t in type_arr if t.type == lldb.eTypeClassStruct]

    # After the sort, the struct type with more fields will be at index [0].
    # This hueristic helps selecting struct type with more fields compared to ones with "opaque" members
    type_arr.sort(reverse=True, key=lambda x: x.GetNumberOfFields())
    if len(type_arr) > 0:
        tmp_type = type_arr[0]
    else:
        raise NameError('Unable to find type '+target_type)

    if not tmp_type.IsValid():
        raise NameError('Unable to Cast to type '+target_type)

    if requested_type_is_pointer:
        tmp_type = tmp_type.GetPointerType()
    _value_types_cache[target_type] = tmp_type

    return _value_types_cache[target_type]


def getfieldoffset(struct_type, field_name):
    """ Returns the byte offset of a field inside a given struct
        Understands anonymous unions and field names in sub-structs
        params:
            struct_type - str or lldb.SBType, ex. 'struct ipc_port *' or port.gettype()
            field_name  - str, name of the field inside the struct ex. 'ip_messages'
        returns:
            int - byte offset of the field_name inside the struct_type
        raises:
            TypeError  - - In case the struct_type has no field with the name field_name
    """

    if type(struct_type) == str:
        struct_type = gettype(struct_type)

    if '.' in field_name :
        # Handle recursive fields in sub-structs
        components = field_name.split('.', 1)
        for field in struct_type.get_fields_array():
            if str(field.GetName()) == components[0]:
                return getfieldoffset(struct_type, components[0]) + getfieldoffset(field.GetType(), components[1])
        raise TypeError('Field name "%s" not found in type "%s"' % (components[0], str(struct_type)))

    offset = 0
    for field in struct_type.get_fields_array():
        if str(field.GetName()) == field_name:
            return field.GetOffsetInBytes()

        # Hack for anonymous unions - the compiler does this, so cvalue should too
        if field.GetName() is None and field.GetType().GetTypeClass() == lldb.eTypeClassUnion :
            for union_field in field.GetType().get_fields_array():
                if str(union_field.GetName()) == field_name:
                    return union_field.GetOffsetInBytes() + field.GetOffsetInBytes()
    raise TypeError('Field name "%s" not found in type "%s"' % (field_name, str(struct_type)))

def islong(x):
    """ Returns True if a string represents a long integer, False otherwise
    """
    try:
        long(x,16)
    except ValueError:
        try:
            long(x)
        except ValueError:
            return False
    return True

def readmemory(val):
    """ Returns a string of hex data that is referenced by the value.
        params: val - a value object.
        return: str - string of hex bytes.
        raises: TypeError if val is not a valid type
    """
    if not type(val) is value:
        raise TypeError('%s is not of type value' % str(type(val)))
    return val.__getstate__()

def getOSPtr(cpp_obj):
    """ Returns a core.value created from an intrusive_shared_ptr or itself, cpp_obj
        params: cpp_obj - core.value object representing a C construct in lldb
        return: core.value - newly created core.value or cpp_obj
    """
    child = cpp_obj._sbval19k84obscure747.GetChildAtIndex(0)
    if 'intrusive_shared_ptr' in str(child):
        return value(child.GetChildMemberWithName('ptr_'))
    return cpp_obj
