# -*- coding: utf-8 -*-
# h-client, a client for an h-source server (such as http://www.h-node.org/)
# Copyright (C) 2011  Antonio Gallo
#
#
# h-client is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# h-client is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with h-client.  If not, see <http://www.gnu.org/licenses/>.


"""
Classes representing devices for h-client.
"""


from __future__ import absolute_import

from hclient.devicetypes import DeviceType, Unknown
from hclient.machine import user_distribution


class Device(object):

	_status = True

	errors = []

	def __init__(self, type_=Unknown):
		self._type = type_
		self._vendor = ''
		self._model = ''
		self._otherNames = []
		self._kernel = ''
		self._distributions = [user_distribution()]
		self._interface = 0
		self._year = None
		self._vendorId = ''
		self._productId = ''
		self._howItWorks = type_.default_how_it_works
		self._driver = ''
		self._description = ''
		self._subtype = 0
		self._it_tracks_users = 0
		self._subsystemVendor = ''
		self._subsystemName = ''
		self._subVendorId = ''
		self._subProductId = ''
		self._bus = ''

	@property
	def data(self):
		"""A dictionary representation of the device."""
		post = {}
		post["model"] = self._model
		post["other_names"] = self.createOtherNamesEntry().replace("\n",
																   "\r\n")
		post["kernel"] = self._kernel
		post["distribution"] = self.createDistroEntry()
		if self._year is not None:
			post["comm_year"] = str(self._year)
		else:
			post["comm_year"] = "not-specified"
		post["pci_id"] = self._vendorId + ":" + self._productId
		post["interface"] = self._type.interfaces_post[self._interface]
		post["description"] = self._description.replace("\n", "\r\n")
		post["driver"] = self._driver
		post.update(self._type.post_data(self))
		return post

	def createOtherNamesEntry(self):
		"""Create the other_names entry."""

		if self._bus == "PCI" and self._subVendorId:
			self._otherNames.append("%s:%s\t%s\t%s" % (self._subVendorId,self._subProductId,self._subsystemVendor,self._subsystemName))
		
		cleanNames = []
		for name in self._otherNames:
			name = name.strip()
			if name:
				cleanNames.append(name)

		cleanNames = set(cleanNames)

		names = "\n".join(cleanNames)
		
		return names
		
		
	def createDistroEntry(self):
		"""Return the canonical representation of the set of distros tested."""
		can_distros = set(distro.strip() for distro in self._distributions)
		can_distros.discard("")
		can_distros = sorted(can_distros)
		return " , ".join(can_distros)

	def addDistribution(self,distroName):
		"""Add a distribution."""
		self._distributions.append(distroName)

	def addOtherName(self,newName):
		"""Add a subsystem name."""
		subCode = "%s:%s" % (self._subVendorId,self._subProductId)
		if subCode not in newName:
			self._otherNames.append(newName)

	def addOtherNames(self,otherNamesList):
		"""Add many subsystem names.

		otherNamesList: comma separated list of names (type: string)."""
		otherNames = otherNamesList.split('\n')
		for otherName in otherNames:
			self.addOtherName(otherName)
		
	def addDistributions(self,distroList):
		"""Add many distributions.

		distroList: comma separated list of distros (type: string)."""
		distros = distroList.split(',')
		for distro in distros:
			self.addDistribution(distro)

	def getBus(self):
		return self._bus

	@property
	def subtype(self):
		"""Index in the subtypes tuple of this device type."""
		return self._subtype

	@subtype.setter
	def subtype(self, index):
		"""Set the subtype."""
		if not self.type.subtypes_post:
			return  # ignore setting it if there are no options
		if not (0 <= index < len(self.type.subtypes_post)):
			raise ValueError("subtype must be in type's range")
		self._subtype = index

	@property
	def tracks_users(self):
		"""Index of type's "tracks users" tuple."""
		return self._it_tracks_users

	@tracks_users.setter
	def tracks_users(self, index):
		"""Set tracks users."""
		if not self.type.tracks_users_post:
			return  # ignore setting it if there are no options
		if not (0 <= index < len(self.type.tracks_users_post)):
			raise ValueError("tracks users must be in type's range")
		self._it_tracks_users = index

	@property
	def type(self):
		"""A subclass of `hclient.devicetypes.DeviceType` describing
		devices like this one."""
		return self._type

	@type.setter
	def type(self, new_type):
		"""Change type of the device."""
		try:
			is_subclass = issubclass(new_type, DeviceType)
		except TypeError:
			is_subclass = False
		if not is_subclass:
			raise TypeError("device type must be a subclass of DeviceType")
		self._type = new_type

	def getVendor(self):
		return self._vendor

	def getModel(self):
		return self._model

	def getOtherNames(self):
		return self._otherNames

	@property
	def kernel(self):
		"""Version number of first kernel with which the device was tested."""
		return self._kernel

	@kernel.setter
	def kernel(self, value):
		"""Set kernel version."""
		# TODO validate it, since not all values are allowed
		self._kernel = value

	def getDistributions(self):
		return self._distributions

	@property
	def interface(self):
		"""Index in interface tuple of the device type."""
		return self._interface

	@interface.setter
	def interface(self, index):
		"""Set interface."""
		if not (0 <= index < len(self.type.interfaces)):
			raise ValueError("interface must be in type's range")
		self._interface = index

	def getYear(self):
		return self._year

	def getVendorId(self):
		return self._vendorId

	def getProductId(self):
		return self._productId

	@property
	def how_it_works(self):
		"""Return the index of how it works in type's tuple."""
		return self._howItWorks

	@how_it_works.setter
	def how_it_works(self, index):
		"""Set how the device works."""
		if not (0 <= index < len(self.type.how_it_works)):
			raise ValueError("how_it_works must be in type's range")
		self._howItWorks = index

	def getDriver(self):
		return self._driver

	def getDescription(self):
		return self._description

	def getSubsystemVendor(self):
		return self._subsystemVendor
		
	def getSubsystemName(self):
		return self._subsystemName

	def getSubVendorId(self):
		return self._subVendorId

	def getSubProductId(self):
		return self._subProductId

	def setBus(self,bus):
		self._bus = bus

	def setVendor(self,vendor):
		self._vendor = vendor

	def setModel(self,model):
		self._model = model

	def setOtherNames(self,otherNames):
		self._otherNames = otherNames

	def setDistributions(self,distributions):
		self._distributions = list(distributions)

	def setYear(self,year):
		self._year = year

	def setVendorId(self,vendorId):
		if isinstance(vendorId, int):
			vendorId = "%04x" % vendorId
		self._vendorId = vendorId

	def setProductId(self,productId):
		if isinstance(productId, int):
			productId = "%04x" % productId
		self._productId = productId

	def setDriver(self,driver):
		self._driver = driver

	def setDescription(self,description):
		self._description = description

	def setSubsystemVendor(self,subsystemVendor):
		self._subsystemVendor = subsystemVendor

	def setSubsystemName(self,subsystemName):
		self._subsystemName = subsystemName

	def setSubVendorId(self,subVendorId):
		if isinstance(subVendorId, int):
			subVendorId = "%04x" % subVendorId
		self._subVendorId = subVendorId

	def setSubProductId(self,subProductId):
		if isinstance(subProductId, int):
			subProductId = "%04x" % subProductId
		self._subProductId = subProductId

	def getStatus(self):
		return self._status


#: Dictionary mapping a device class to a subclass of `Device`.
_CLASS_TO_DEVICE = {}


def _get_device(func):
	"""A decorator returning a `DeviceType` subclass for which the
	decorated function returns `True`.

	The decorated function gets the checked subclass in its first
	argument and the arguments passed to the outer function in the
	rest of its arguments.

	The result is cached.

	If *func* returns `False` for all subclasses of `Device`, then
	`None` is returned.
	"""
	cache = {}

	def inner(*args, **kwargs):
		key = (tuple(args), tuple(sorted(kwargs.iteritems())))
		try:
			return cache[key]
		except KeyError:
			for subclass in DeviceType.__subclasses__():
				if func(subclass, *args, **kwargs):
					cache[key] = subclass
					return subclass
		return None

	return inner


@_get_device
def get_device_type_for_class(subclass, class_):
	"""Return a `DeviceType` subclass used for a class of devices.

	The argument is an `int` specifying the device class.  Its
	typically a two octet number for PCI devices or a three octet
	number for USB devices.

	If no subclass of `DeviceType` handles this device class, `None` is
	returned.
	"""
	return class_ in subclass.classes


@_get_device
def get_device_type_for_type(subclass, type_):
	"""Return a `DeviceType` subclass of type name specified in argument.

	If no subclass of `DeviceType` handles this type, `None` is returned.
	"""
	return subclass.type_name == type_


def get_types():
	"""Return a sequence of type names used for `DeviceType` subclasses."""
	types = []
	for subclass in DeviceType.__subclasses__():
		types.append(subclass.type_name)
	return types


def get_type_for_class(class_):
	"""Return the type name of specified PCI device class.

	If no type is known to handle this class, `None` is returned.
	"""
	for subclass in DeviceType.__subclasses__():
		if class_ in subclass.classes:
			return subclass.type_name


def get_class_for_type(type_):
	"""Return a PCI device class for specified type name.

	If no class is known to be used for this type, `None` is returned.
	"""
	for subclass in DeviceType.__subclasses__():
		if type_ == subclass.type_name and subclass.classes:
			return subclass.classes[0]


# Local Variables:
# indent-tabs-mode: t
# python-guess-indent: nil
# python-indent: 4
# tab-width: 4
# End:
