import binascii
import numpy
import struct
import binary16

def isclose(a, b, rel_tol=1e-09, abs_tol=0.0):
    return abs(a-b) <= max(rel_tol * max(abs(a), abs(b)), abs_tol)

class hex_file(object):
    def __init__(self, fileobj, endianess = 'little'):
        self.fileobj   = fileobj
        if type(self.fileobj) == file and self.fileobj.mode != "rb+":
            print "Warning: rb+ mode not specified"
        self.endianess = endianess
        self.fileobj.seek(0)
        self.data      = bytearray(self.fileobj.read())
        return
        
    def read_int(self, offset, size):
        a = self.data[offset:offset+size]
        if self.endianess == 'little':
            a = a[::-1]
        b = binascii.hexlify(a)
        return int(b, 16)
    
    def read_float(self, offset, size):
        a = self.data[offset:offset+size]
        if self.endianess == 'big':
            a = a[::-1]
        if size == 2:
            return numpy.frombuffer(a, '<f2')[0]
        elif size == 4:
            return struct.unpack('<f', a)[0]
        elif size == 8:
            return struct.unpack('<d', a)[0]
        else:
            raise AssertionError, "Size of float must be 2,4 or 8 bytes"
            
    def read_string(self, offset, size):
        return str(self.data[offset:offset+size])
    
    def write_string(self, offset, str):
        self.data[offset:offset+len(str)] = str
        return
       
    def write_int(self, offset, number, size):
        hex_string = self.int_to_string(number, size)
        self.data[offset:offset+size] = hex_string
        return
        
    def delete_string(self, offset, size):
        del self.data[offset:offset+size]
        return
    
    def insert_string(self, offset, str):
        for x in str[::-1]:
            self.data.insert(offset, x)
        return
    
    def find_float(self, value,size=4, precision=None, from_=0, to=None, step=1):
        if to is None:
            to = len(self.data)
        for x in xrange(from_, to , step):
            if (x+size) <= to:
                data = self.read_float(x, size)
                if precision is not None:
                    data = round(data, precision)
                if data == value:
                    return x
            else:
                return -1
        return -1
            
    def update(self):
        self.fileobj.seek(0)
        self.fileobj.truncate()
        self.fileobj.write(self.data)
        self.fileobj.flush()
        return
    
    def reset(self):
        self.fileobj.seek(0)
        self.data      = bytearray(self.fileobj.read())
        return
        
    def close(self):
        self.fileobj.close()
        return
        
    def int_to_string(self, number, size):
        size = size * 2
        a = "%c" % 0x25 
        c = "."
        d = "%d" % size
        e = "X"
        f = (a + c + d + e) % number
        b = binascii.unhexlify(f)
        if self.endianess == 'little':
            b = b[::-1]
        return b

    def float_to_string(self, number, size):
        if size == 2:
            b = binary16.binary16(number)
        if size == 4:
            b = struct.pack("<f", number)
        elif size == 8:
            b = struct.pack("<d", number) 
        else:
            raise AssertionError, "Size of float must be 2,4 or 8 bytes"
        if self.endianess == 'big':
            b = b[::-1]
        return b    
