import logging
import os
import re

logger = logging.getLogger("CryptoBox")


def getAvailablePartitions():
	"retrieve a list of all available containers"
	ret_list = []
	try:
		"the following reads all lines of /proc/partitions and adds the mentioned devices"
		fpart = open("/proc/partitions", "r")
		try:
			line = fpart.readline()
			while line:
				p_details = line.split()
				if (len(p_details) == 4):
					"the following code prevents double entries like /dev/hda and /dev/hda1"
					(p_major, p_minor, p_size, p_device) = p_details
					## ignore lines with: invalid minor/major or extend partitions (size=1)
					if  re.search('^[0-9]*$', p_major) and re.search('^[0-9]*$', p_minor) and (p_size != "1"):
						p_parent = re.sub('[1-9]?[0-9]$', '', p_device)
						if p_parent == p_device:
							if [e for e in ret_list if re.search('^' + p_parent + '[1-9]?[0-9]$', e)]:
								"major partition - its children are already in the list"
								pass
							else:
								"major partition - but there are no children for now"
								ret_list.append(p_device)
						else:
							"minor partition - remove parent if necessary"
							if p_parent in ret_list: ret_list.remove(p_parent)
							ret_list.append(p_device)
				line = fpart.readline()
		finally:
			fpart.close()
		return map(getAbsoluteDeviceName, ret_list)
	except IOError:
		logger.warning("Could not read /proc/partitions")
		return []


def getAbsoluteDeviceName(shortname):
	"""	returns the absolute file name of a device (e.g.: "hda1" -> "/dev/hda1")
		this does also work for device mapper devices
		if the result is non-unique, one arbitrary value is returned"""
	if re.search('^/', shortname): return shortname
	default = os.path.join("/dev", shortname)
	if os.path.exists(default): return default
	result = findMajorMinorOfDevice(shortname)
	"if no valid major/minor was found -> exit"
	if not result: return default
	(major, minor) = result
	"for device-mapper devices (major == 254) ..."
	if major == 254:
		result = findMajorMinorDeviceName("/dev/mapper", major, minor)
		if result: return result[0]
	"now check all files in /dev"
	result = findMajorMinorDeviceName("/dev", major, minor)
	if result: return result[0]
	return default


def findMajorMinorOfDevice(device):
	"return the major/minor numbers of a block device"
	if re.match("/", device) or not os.path.exists(os.path.join(os.path.sep,"sys","block",device)):
		## maybe it is an absolute device name
		if not os.path.exists(device): return None
		## okay - it seems to to a device node
		rdev = os.stat(device).st_rdev
		return (os.major(rdev), os.minor(rdev))
	blockdev_info_file = os.path.join(os.path.join(os.path.sep,"sys","block", device), "dev")
	try:
		f_blockdev_info = open(blockdev_info_file, "r")
		blockdev_info = f_blockdev_info.read()
		f_blockdev_info.close()
		(str_major, str_minor) = blockdev_info.split(":")
		"numeric conversion"
		try:
			major = int(str_major)
			minor = int(str_minor)
			return (major, minor)
		except ValueError:
			"unknown device numbers -> stop guessing"
			return None
	except IOError:
		pass
		return None


def findMajorMinorDeviceName(dir, major, minor):
	"returns the names of devices with the specified major and minor number"
	collected = []
	try:
		subdirs = [os.path.join(dir, e) for e in os.listdir(dir) if (not os.path.islink(os.path.join(dir, e))) and os.path.isdir(os.path.join(dir, e))]
		"do a recursive call to parse the directory tree"
		for dirs in subdirs:
			collected.extend(findMajorMinorDeviceName(dirs, major, minor))
		"filter all device inodes in this directory"
		collected.extend([os.path.realpath(os.path.join(dir, e)) for e in os.listdir(dir) if (os.major(os.stat(os.path.join(dir, e)).st_rdev) == major) and (os.minor(os.stat(os.path.join(dir, e)).st_rdev) == minor)])
		## remove double entries
		result = []
		for e in collected:
			if e not in result: result.append(e)
		return result
	except OSError:
		return []


def getParentBlockDevices():
	devs = []
	for line in file("/proc/partitions"):
		p_details = line.split()
		## we expect four values - otherwise continue with next iteration
		if len(p_details) != 4: continue
		(p_major, p_minor, p_size, p_device) = p_details
		## we expect numeric values in the first two columns
		if re.search(u'\D',p_major) or re.search(u'\D',p_minor): continue
		## now let us check, if it is a (parent) block device or a partition
		if not os.path.isdir(os.path.join(os.path.sep, "sys", "block", p_device)): continue
		devs.append(p_device)
	return map(getAbsoluteDeviceName, devs)


def isPartOfBlockDevice(parent, subdevice):
	"""check if the given block device is a parent of 'subdevice'
	e.g. for checking if a partition belongs to a block device"""
	try:
		(par_major, par_minor) = findMajorMinorOfDevice(parent)
		(sub_major, sub_minor) = findMajorMinorOfDevice(subdevice)
	except TypeError:
		## at least one of these devices did not return a valid major/minor combination
		return False
	## search the entry below '/sys/block' belonging to the parent
	root = os.path.join(os.path.sep, 'sys', 'block')
	for bldev in os.listdir(root):
		blpath = os.path.join(root, bldev, 'dev')
		if os.access(blpath, os.R_OK):
			try:
				if (str(par_major), str(par_minor)) == tuple([e for e in file(blpath)][0].strip().split(":",1)):
					parent_path = os.path.join(root, bldev)
					break
			except (IndexError, OSError):
				pass
	else:
		## no block device with this major/minor combination found below '/sys/block'
		return False
	for subbldev in os.listdir(parent_path):
		subblpath = os.path.join(parent_path, subbldev, "dev")
		if os.access(subblpath, os.R_OK):
			try:
				if (str(sub_major), str(sub_minor)) == tuple([e for e in file(subblpath)][0].strip().split(":",1)):
					## the name of the subdevice node is not important - we found it!
					return True
			except (IndexError, OSError):
				pass
	return False


def getBlockDeviceSize(device):
	if not device: return -1
	try:
		rdev = os.stat(device).st_rdev
	except OSError:
		return -1
	minor = os.minor(rdev)
	major = os.major(rdev)
	for f in file("/proc/partitions"):
		try:
			elements = f.split()
			if len(elements) != 4: continue
			if (int(elements[0]) == major) and (int(elements[1]) == minor):
				return int(elements[2])/1024
		except ValueError:
			pass
	return -1


def getBlockDeviceSizeHumanly(device):
	size = getBlockDeviceSize(device)
	if size > 5120:
		return "%sGB" % size/1024
	else:
		return "%sMB" % size

