import binaryninja
import ctypes, os

from typing import Optional
from . import sharedcache_enums
# Load core module
import platform
core = None
core_platform = platform.system()

# By the time the debugger is loaded, binaryninja has not fully initialized.
# So we cannot call binaryninja.bundled_plugin_path()
from binaryninja._binaryninjacore import BNGetBundledPluginDirectory, BNFreeString
if core_platform == "Darwin":
    _base_path = BNGetBundledPluginDirectory()
    core = ctypes.CDLL(os.path.join(_base_path, "libsharedcache.dylib"))

elif core_platform == "Linux":
    _base_path = BNGetBundledPluginDirectory()
    core = ctypes.CDLL(os.path.join(_base_path, "libsharedcache.so"))

elif (core_platform == "Windows") or (core_platform.find("CYGWIN_NT") == 0):
    _base_path = BNGetBundledPluginDirectory()
    core = ctypes.CDLL(os.path.join(_base_path, "sharedcache.dll"))
else:
    raise Exception("OS not supported")

def cstr(var) -> Optional[ctypes.c_char_p]:
    if var is None:
        return None
    if isinstance(var, bytes):
        return var
    return var.encode("utf-8")

def pyNativeStr(arg):
    if isinstance(arg, str):
        return arg
    else:
        return arg.decode('utf8')

def free_string(value:ctypes.c_char_p) -> None:
    BNFreeString(ctypes.cast(value, ctypes.POINTER(ctypes.c_byte)))

# Type definitions
from binaryninja._binaryninjacore import BNBinaryView, BNBinaryViewHandle
class BNDSCBackingCache(ctypes.Structure):
	@property
	def path(self):
		return pyNativeStr(self._path)
	@path.setter
	def path(self, value):
		self._path = cstr(value)
BNDSCBackingCacheHandle = ctypes.POINTER(BNDSCBackingCache)
class BNDSCBackingCacheMapping(ctypes.Structure):
	pass
BNDSCBackingCacheMappingHandle = ctypes.POINTER(BNDSCBackingCacheMapping)
class BNDSCImage(ctypes.Structure):
	@property
	def name(self):
		return pyNativeStr(self._name)
	@name.setter
	def name(self, value):
		self._name = cstr(value)
BNDSCImageHandle = ctypes.POINTER(BNDSCImage)
class BNDSCImageMemoryMapping(ctypes.Structure):
	@property
	def filePath(self):
		return pyNativeStr(self._filePath)
	@filePath.setter
	def filePath(self, value):
		self._filePath = cstr(value)
	@property
	def name(self):
		return pyNativeStr(self._name)
	@name.setter
	def name(self, value):
		self._name = cstr(value)
BNDSCImageMemoryMappingHandle = ctypes.POINTER(BNDSCImageMemoryMapping)
class BNDSCMappedMemoryRegion(ctypes.Structure):
	@property
	def name(self):
		return pyNativeStr(self._name)
	@name.setter
	def name(self, value):
		self._name = cstr(value)
BNDSCMappedMemoryRegionHandle = ctypes.POINTER(BNDSCMappedMemoryRegion)
class BNDSCMemoryUsageInfo(ctypes.Structure):
	pass
BNDSCMemoryUsageInfoHandle = ctypes.POINTER(BNDSCMemoryUsageInfo)
class BNDSCSymbolRep(ctypes.Structure):
	@property
	def name(self):
		return pyNativeStr(self._name)
	@name.setter
	def name(self, value):
		self._name = cstr(value)
	@property
	def image(self):
		return pyNativeStr(self._image)
	@image.setter
	def image(self, value):
		self._image = cstr(value)
BNDSCSymbolRepHandle = ctypes.POINTER(BNDSCSymbolRep)
DSCViewLoadProgressEnum = ctypes.c_int
DSCViewStateEnum = ctypes.c_int
class BNSharedCache(ctypes.Structure):
	pass
BNSharedCacheHandle = ctypes.POINTER(BNSharedCache)

# Structure definitions
BNDSCBackingCache._fields_ = [
		("_path", ctypes.c_char_p),
		("isPrimary", ctypes.c_bool),
		("mappings", ctypes.POINTER(BNDSCBackingCacheMapping)),
		("mappingCount", ctypes.c_ulonglong),
	]
BNDSCBackingCacheMapping._fields_ = [
		("vmAddress", ctypes.c_ulonglong),
		("size", ctypes.c_ulonglong),
		("fileOffset", ctypes.c_ulonglong),
	]
BNDSCImage._fields_ = [
		("_name", ctypes.c_char_p),
		("headerAddress", ctypes.c_ulonglong),
		("mappings", ctypes.POINTER(BNDSCImageMemoryMapping)),
		("mappingCount", ctypes.c_ulonglong),
	]
BNDSCImageMemoryMapping._fields_ = [
		("_filePath", ctypes.c_char_p),
		("_name", ctypes.c_char_p),
		("vmAddress", ctypes.c_ulonglong),
		("size", ctypes.c_ulonglong),
		("loaded", ctypes.c_bool),
		("rawViewOffset", ctypes.c_ulonglong),
	]
BNDSCMappedMemoryRegion._fields_ = [
		("vmAddress", ctypes.c_ulonglong),
		("size", ctypes.c_ulonglong),
		("_name", ctypes.c_char_p),
	]
BNDSCMemoryUsageInfo._fields_ = [
		("sharedCacheRefs", ctypes.c_ulonglong),
		("mmapRefs", ctypes.c_ulonglong),
	]
BNDSCSymbolRep._fields_ = [
		("address", ctypes.c_ulonglong),
		("_name", ctypes.c_char_p),
		("_image", ctypes.c_char_p),
	]

# Function definitions
# -------------------------------------------------------
# _BNDSCFindSymbolAtAddressAndApplyToAddress

_BNDSCFindSymbolAtAddressAndApplyToAddress = core.BNDSCFindSymbolAtAddressAndApplyToAddress
_BNDSCFindSymbolAtAddressAndApplyToAddress.restype = None
_BNDSCFindSymbolAtAddressAndApplyToAddress.argtypes = [
		ctypes.POINTER(BNSharedCache),
		ctypes.c_ulonglong,
		ctypes.c_ulonglong,
		ctypes.c_bool,
	]


# noinspection PyPep8Naming
def BNDSCFindSymbolAtAddressAndApplyToAddress(
		cache: ctypes.POINTER(BNSharedCache), 
		symbolLocation: int, 
		targetLocation: int, 
		triggerReanalysis: bool
		) -> None:
	return _BNDSCFindSymbolAtAddressAndApplyToAddress(cache, symbolLocation, targetLocation, triggerReanalysis)


# -------------------------------------------------------
# _BNDSCViewFastGetBackingCacheCount

_BNDSCViewFastGetBackingCacheCount = core.BNDSCViewFastGetBackingCacheCount
_BNDSCViewFastGetBackingCacheCount.restype = ctypes.c_ulonglong
_BNDSCViewFastGetBackingCacheCount.argtypes = [
		ctypes.POINTER(BNBinaryView),
	]


# noinspection PyPep8Naming
def BNDSCViewFastGetBackingCacheCount(
		view: ctypes.POINTER(BNBinaryView)
		) -> int:
	return _BNDSCViewFastGetBackingCacheCount(view)


# -------------------------------------------------------
# _BNDSCViewFreeAllImages

_BNDSCViewFreeAllImages = core.BNDSCViewFreeAllImages
_BNDSCViewFreeAllImages.restype = None
_BNDSCViewFreeAllImages.argtypes = [
		ctypes.POINTER(BNDSCImage),
		ctypes.c_ulonglong,
	]


# noinspection PyPep8Naming
def BNDSCViewFreeAllImages(
		images: ctypes.POINTER(BNDSCImage), 
		count: int
		) -> None:
	return _BNDSCViewFreeAllImages(images, count)


# -------------------------------------------------------
# _BNDSCViewFreeBackingCaches

_BNDSCViewFreeBackingCaches = core.BNDSCViewFreeBackingCaches
_BNDSCViewFreeBackingCaches.restype = None
_BNDSCViewFreeBackingCaches.argtypes = [
		ctypes.POINTER(BNDSCBackingCache),
		ctypes.c_ulonglong,
	]


# noinspection PyPep8Naming
def BNDSCViewFreeBackingCaches(
		caches: ctypes.POINTER(BNDSCBackingCache), 
		count: int
		) -> None:
	return _BNDSCViewFreeBackingCaches(caches, count)


# -------------------------------------------------------
# _BNDSCViewFreeLoadedRegions

_BNDSCViewFreeLoadedRegions = core.BNDSCViewFreeLoadedRegions
_BNDSCViewFreeLoadedRegions.restype = None
_BNDSCViewFreeLoadedRegions.argtypes = [
		ctypes.POINTER(BNDSCMappedMemoryRegion),
		ctypes.c_ulonglong,
	]


# noinspection PyPep8Naming
def BNDSCViewFreeLoadedRegions(
		images: ctypes.POINTER(BNDSCMappedMemoryRegion), 
		count: int
		) -> None:
	return _BNDSCViewFreeLoadedRegions(images, count)


# -------------------------------------------------------
# _BNDSCViewFreeSymbols

_BNDSCViewFreeSymbols = core.BNDSCViewFreeSymbols
_BNDSCViewFreeSymbols.restype = None
_BNDSCViewFreeSymbols.argtypes = [
		ctypes.POINTER(BNDSCSymbolRep),
		ctypes.c_ulonglong,
	]


# noinspection PyPep8Naming
def BNDSCViewFreeSymbols(
		symbols: ctypes.POINTER(BNDSCSymbolRep), 
		count: int
		) -> None:
	return _BNDSCViewFreeSymbols(symbols, count)


# -------------------------------------------------------
# _BNDSCViewGetAllImages

_BNDSCViewGetAllImages = core.BNDSCViewGetAllImages
_BNDSCViewGetAllImages.restype = ctypes.POINTER(BNDSCImage)
_BNDSCViewGetAllImages.argtypes = [
		ctypes.POINTER(BNSharedCache),
		ctypes.POINTER(ctypes.c_ulonglong),
	]


# noinspection PyPep8Naming
def BNDSCViewGetAllImages(
		cache: ctypes.POINTER(BNSharedCache), 
		count: ctypes.POINTER(ctypes.c_ulonglong)
		) -> Optional[ctypes.POINTER(BNDSCImage)]:
	result = _BNDSCViewGetAllImages(cache, count)
	if not result:
		return None
	return result


# -------------------------------------------------------
# _BNDSCViewGetBackingCaches

_BNDSCViewGetBackingCaches = core.BNDSCViewGetBackingCaches
_BNDSCViewGetBackingCaches.restype = ctypes.POINTER(BNDSCBackingCache)
_BNDSCViewGetBackingCaches.argtypes = [
		ctypes.POINTER(BNSharedCache),
		ctypes.POINTER(ctypes.c_ulonglong),
	]


# noinspection PyPep8Naming
def BNDSCViewGetBackingCaches(
		cache: ctypes.POINTER(BNSharedCache), 
		count: ctypes.POINTER(ctypes.c_ulonglong)
		) -> Optional[ctypes.POINTER(BNDSCBackingCache)]:
	result = _BNDSCViewGetBackingCaches(cache, count)
	if not result:
		return None
	return result


# -------------------------------------------------------
# _BNDSCViewGetImageHeaderForAddress

_BNDSCViewGetImageHeaderForAddress = core.BNDSCViewGetImageHeaderForAddress
_BNDSCViewGetImageHeaderForAddress.restype = ctypes.POINTER(ctypes.c_byte)
_BNDSCViewGetImageHeaderForAddress.argtypes = [
		ctypes.POINTER(BNSharedCache),
		ctypes.c_ulonglong,
	]


# noinspection PyPep8Naming
def BNDSCViewGetImageHeaderForAddress(
		cache: ctypes.POINTER(BNSharedCache), 
		address: int
		) -> Optional[Optional[str]]:
	result = _BNDSCViewGetImageHeaderForAddress(cache, address)
	if not result:
		return None
	string = str(pyNativeStr(ctypes.cast(result, ctypes.c_char_p).value))
	BNFreeString(result)
	return string


# -------------------------------------------------------
# _BNDSCViewGetImageHeaderForName

_BNDSCViewGetImageHeaderForName = core.BNDSCViewGetImageHeaderForName
_BNDSCViewGetImageHeaderForName.restype = ctypes.POINTER(ctypes.c_byte)
_BNDSCViewGetImageHeaderForName.argtypes = [
		ctypes.POINTER(BNSharedCache),
		ctypes.c_char_p,
	]


# noinspection PyPep8Naming
def BNDSCViewGetImageHeaderForName(
		cache: ctypes.POINTER(BNSharedCache), 
		name: Optional[str]
		) -> Optional[Optional[str]]:
	result = _BNDSCViewGetImageHeaderForName(cache, cstr(name))
	if not result:
		return None
	string = str(pyNativeStr(ctypes.cast(result, ctypes.c_char_p).value))
	BNFreeString(result)
	return string


# -------------------------------------------------------
# _BNDSCViewGetImageNameForAddress

_BNDSCViewGetImageNameForAddress = core.BNDSCViewGetImageNameForAddress
_BNDSCViewGetImageNameForAddress.restype = ctypes.POINTER(ctypes.c_byte)
_BNDSCViewGetImageNameForAddress.argtypes = [
		ctypes.POINTER(BNSharedCache),
		ctypes.c_ulonglong,
	]


# noinspection PyPep8Naming
def BNDSCViewGetImageNameForAddress(
		cache: ctypes.POINTER(BNSharedCache), 
		address: int
		) -> Optional[Optional[str]]:
	result = _BNDSCViewGetImageNameForAddress(cache, address)
	if not result:
		return None
	string = str(pyNativeStr(ctypes.cast(result, ctypes.c_char_p).value))
	BNFreeString(result)
	return string


# -------------------------------------------------------
# _BNDSCViewGetInstallNames

_BNDSCViewGetInstallNames = core.BNDSCViewGetInstallNames
_BNDSCViewGetInstallNames.restype = ctypes.POINTER(ctypes.c_char_p)
_BNDSCViewGetInstallNames.argtypes = [
		ctypes.POINTER(BNSharedCache),
		ctypes.POINTER(ctypes.c_ulonglong),
	]


# noinspection PyPep8Naming
def BNDSCViewGetInstallNames(
		cache: ctypes.POINTER(BNSharedCache), 
		count: ctypes.POINTER(ctypes.c_ulonglong)
		) -> Optional[ctypes.POINTER(ctypes.c_char_p)]:
	result = _BNDSCViewGetInstallNames(cache, count)
	if not result:
		return None
	return result


# -------------------------------------------------------
# _BNDSCViewGetLoadProgress

_BNDSCViewGetLoadProgress = core.BNDSCViewGetLoadProgress
_BNDSCViewGetLoadProgress.restype = DSCViewLoadProgressEnum
_BNDSCViewGetLoadProgress.argtypes = [
		ctypes.c_ulonglong,
	]


# noinspection PyPep8Naming
def BNDSCViewGetLoadProgress(
		sessionID: int
		) -> DSCViewLoadProgressEnum:
	return _BNDSCViewGetLoadProgress(sessionID)


# -------------------------------------------------------
# _BNDSCViewGetLoadedRegions

_BNDSCViewGetLoadedRegions = core.BNDSCViewGetLoadedRegions
_BNDSCViewGetLoadedRegions.restype = ctypes.POINTER(BNDSCMappedMemoryRegion)
_BNDSCViewGetLoadedRegions.argtypes = [
		ctypes.POINTER(BNSharedCache),
		ctypes.POINTER(ctypes.c_ulonglong),
	]


# noinspection PyPep8Naming
def BNDSCViewGetLoadedRegions(
		cache: ctypes.POINTER(BNSharedCache), 
		count: ctypes.POINTER(ctypes.c_ulonglong)
		) -> Optional[ctypes.POINTER(BNDSCMappedMemoryRegion)]:
	result = _BNDSCViewGetLoadedRegions(cache, count)
	if not result:
		return None
	return result


# -------------------------------------------------------
# _BNDSCViewGetMemoryUsageInfo

_BNDSCViewGetMemoryUsageInfo = core.BNDSCViewGetMemoryUsageInfo
_BNDSCViewGetMemoryUsageInfo.restype = BNDSCMemoryUsageInfo
_BNDSCViewGetMemoryUsageInfo.argtypes = [
	]


# noinspection PyPep8Naming
def BNDSCViewGetMemoryUsageInfo(
		) -> BNDSCMemoryUsageInfo:
	return _BNDSCViewGetMemoryUsageInfo()


# -------------------------------------------------------
# _BNDSCViewGetNameForAddress

_BNDSCViewGetNameForAddress = core.BNDSCViewGetNameForAddress
_BNDSCViewGetNameForAddress.restype = ctypes.POINTER(ctypes.c_byte)
_BNDSCViewGetNameForAddress.argtypes = [
		ctypes.POINTER(BNSharedCache),
		ctypes.c_ulonglong,
	]


# noinspection PyPep8Naming
def BNDSCViewGetNameForAddress(
		cache: ctypes.POINTER(BNSharedCache), 
		address: int
		) -> Optional[Optional[str]]:
	result = _BNDSCViewGetNameForAddress(cache, address)
	if not result:
		return None
	string = str(pyNativeStr(ctypes.cast(result, ctypes.c_char_p).value))
	BNFreeString(result)
	return string


# -------------------------------------------------------
# _BNDSCViewGetState

_BNDSCViewGetState = core.BNDSCViewGetState
_BNDSCViewGetState.restype = DSCViewStateEnum
_BNDSCViewGetState.argtypes = [
		ctypes.POINTER(BNSharedCache),
	]


# noinspection PyPep8Naming
def BNDSCViewGetState(
		cache: ctypes.POINTER(BNSharedCache)
		) -> DSCViewStateEnum:
	return _BNDSCViewGetState(cache)


# -------------------------------------------------------
# _BNDSCViewLoadAllSymbolsAndWait

_BNDSCViewLoadAllSymbolsAndWait = core.BNDSCViewLoadAllSymbolsAndWait
_BNDSCViewLoadAllSymbolsAndWait.restype = ctypes.POINTER(BNDSCSymbolRep)
_BNDSCViewLoadAllSymbolsAndWait.argtypes = [
		ctypes.POINTER(BNSharedCache),
		ctypes.POINTER(ctypes.c_ulonglong),
	]


# noinspection PyPep8Naming
def BNDSCViewLoadAllSymbolsAndWait(
		cache: ctypes.POINTER(BNSharedCache), 
		count: ctypes.POINTER(ctypes.c_ulonglong)
		) -> Optional[ctypes.POINTER(BNDSCSymbolRep)]:
	result = _BNDSCViewLoadAllSymbolsAndWait(cache, count)
	if not result:
		return None
	return result


# -------------------------------------------------------
# _BNDSCViewLoadImageContainingAddress

_BNDSCViewLoadImageContainingAddress = core.BNDSCViewLoadImageContainingAddress
_BNDSCViewLoadImageContainingAddress.restype = ctypes.c_bool
_BNDSCViewLoadImageContainingAddress.argtypes = [
		ctypes.POINTER(BNSharedCache),
		ctypes.c_ulonglong,
		ctypes.c_bool,
	]


# noinspection PyPep8Naming
def BNDSCViewLoadImageContainingAddress(
		cache: ctypes.POINTER(BNSharedCache), 
		address: int, 
		skipObjC: bool
		) -> bool:
	return _BNDSCViewLoadImageContainingAddress(cache, address, skipObjC)


# -------------------------------------------------------
# _BNDSCViewLoadImageWithInstallName

_BNDSCViewLoadImageWithInstallName = core.BNDSCViewLoadImageWithInstallName
_BNDSCViewLoadImageWithInstallName.restype = ctypes.c_bool
_BNDSCViewLoadImageWithInstallName.argtypes = [
		ctypes.POINTER(BNSharedCache),
		ctypes.c_char_p,
		ctypes.c_bool,
	]


# noinspection PyPep8Naming
def BNDSCViewLoadImageWithInstallName(
		cache: ctypes.POINTER(BNSharedCache), 
		name: Optional[str], 
		skipObjC: bool
		) -> bool:
	return _BNDSCViewLoadImageWithInstallName(cache, cstr(name), skipObjC)


# -------------------------------------------------------
# _BNDSCViewLoadSectionAtAddress

_BNDSCViewLoadSectionAtAddress = core.BNDSCViewLoadSectionAtAddress
_BNDSCViewLoadSectionAtAddress.restype = ctypes.c_bool
_BNDSCViewLoadSectionAtAddress.argtypes = [
		ctypes.POINTER(BNSharedCache),
		ctypes.c_ulonglong,
	]


# noinspection PyPep8Naming
def BNDSCViewLoadSectionAtAddress(
		cache: ctypes.POINTER(BNSharedCache), 
		name: int
		) -> bool:
	return _BNDSCViewLoadSectionAtAddress(cache, name)


# -------------------------------------------------------
# _BNDSCViewProcessAllObjCSections

_BNDSCViewProcessAllObjCSections = core.BNDSCViewProcessAllObjCSections
_BNDSCViewProcessAllObjCSections.restype = None
_BNDSCViewProcessAllObjCSections.argtypes = [
		ctypes.POINTER(BNSharedCache),
	]


# noinspection PyPep8Naming
def BNDSCViewProcessAllObjCSections(
		cache: ctypes.POINTER(BNSharedCache)
		) -> None:
	return _BNDSCViewProcessAllObjCSections(cache)


# -------------------------------------------------------
# _BNDSCViewProcessObjCSectionsForImageWithInstallName

_BNDSCViewProcessObjCSectionsForImageWithInstallName = core.BNDSCViewProcessObjCSectionsForImageWithInstallName
_BNDSCViewProcessObjCSectionsForImageWithInstallName.restype = None
_BNDSCViewProcessObjCSectionsForImageWithInstallName.argtypes = [
		ctypes.POINTER(BNSharedCache),
		ctypes.c_char_p,
		ctypes.c_bool,
	]


# noinspection PyPep8Naming
def BNDSCViewProcessObjCSectionsForImageWithInstallName(
		cache: ctypes.POINTER(BNSharedCache), 
		name: Optional[str], 
		deallocName: bool
		) -> None:
	return _BNDSCViewProcessObjCSectionsForImageWithInstallName(cache, cstr(name), deallocName)


# -------------------------------------------------------
# _BNFreeSharedCacheReference

_BNFreeSharedCacheReference = core.BNFreeSharedCacheReference
_BNFreeSharedCacheReference.restype = None
_BNFreeSharedCacheReference.argtypes = [
		ctypes.POINTER(BNSharedCache),
	]


# noinspection PyPep8Naming
def BNFreeSharedCacheReference(
		cache: ctypes.POINTER(BNSharedCache)
		) -> None:
	return _BNFreeSharedCacheReference(cache)


# -------------------------------------------------------
# _BNGetSharedCache

_BNGetSharedCache = core.BNGetSharedCache
_BNGetSharedCache.restype = ctypes.POINTER(BNSharedCache)
_BNGetSharedCache.argtypes = [
		ctypes.POINTER(BNBinaryView),
	]


# noinspection PyPep8Naming
def BNGetSharedCache(
		data: ctypes.POINTER(BNBinaryView)
		) -> Optional[ctypes.POINTER(BNSharedCache)]:
	result = _BNGetSharedCache(data)
	if not result:
		return None
	return result


# -------------------------------------------------------
# _BNNewSharedCacheReference

_BNNewSharedCacheReference = core.BNNewSharedCacheReference
_BNNewSharedCacheReference.restype = ctypes.POINTER(BNSharedCache)
_BNNewSharedCacheReference.argtypes = [
		ctypes.POINTER(BNSharedCache),
	]


# noinspection PyPep8Naming
def BNNewSharedCacheReference(
		cache: ctypes.POINTER(BNSharedCache)
		) -> Optional[ctypes.POINTER(BNSharedCache)]:
	result = _BNNewSharedCacheReference(cache)
	if not result:
		return None
	return result



# Helper functions
def handle_of_type(value, handle_type):
	if isinstance(value, ctypes.POINTER(handle_type)) or isinstance(value, ctypes.c_void_p):
		return ctypes.cast(value, ctypes.POINTER(handle_type))
	raise ValueError('expected pointer to %s' % str(handle_type))
