from Components.config import config, ConfigSubsection, ConfigSelection, ConfigPIN, ConfigYesNo, ConfigSubList, ConfigInteger
#from Screens.ChannelSelection import service_types_tv
from Screens.InputBox import PinInput
from Screens.MessageBox import MessageBox
from Tools.BoundFunction import boundFunction
from ServiceReference import ServiceReference
from Tools import Notifications
from Tools.Directories import resolveFilename, SCOPE_CONFIG
from enigma import eTimer, eServiceCenter, iServiceInformation, eServiceReference
import time
from os import fsync

TYPE_SERVICE = "SERVICE"
TYPE_BOUQUETSERVICE = "BOUQUETSERVICE"
TYPE_BOUQUET = "BOUQUET"
LIST_BLACKLIST = "blacklist"
LIST_WHITELIST = "whitelist"

IMG_WHITESERVICE = LIST_WHITELIST + "-" + TYPE_SERVICE
IMG_WHITEBOUQUET = LIST_WHITELIST + "-" + TYPE_BOUQUET
IMG_BLACKSERVICE = LIST_BLACKLIST + "-" + TYPE_SERVICE
IMG_BLACKBOUQUET = LIST_BLACKLIST + "-" + TYPE_BOUQUET

Notifications.notificationQueue.registerDomain("ParentalControl", _("Parental control"), Notifications.ICON_DEFAULT)

def InitParentalControl():
	global parentalControl
	parentalControl = ParentalControl()
	config.ParentalControl = ConfigSubsection()
	config.ParentalControl.configured = ConfigYesNo(default = False)
	config.ParentalControl.mode = ConfigSelection(default = "simple", choices = [("simple", _("simple")), ("complex", _("complex"))])
	config.ParentalControl.storeservicepin = ConfigSelection(default = "never", choices = [("never", _("never")), ("5", _("5 minutes")), ("30", _("30 minutes")), ("60", _("60 minutes")), ("standby", _("until standby/restart"))])
	config.ParentalControl.storeservicepincancel = ConfigSelection(default = "never", choices = [("never", _("never")), ("5", _("5 minutes")), ("30", _("30 minutes")), ("60", _("60 minutes")), ("standby", _("until standby/restart"))])
	config.ParentalControl.servicepinactive = ConfigYesNo(default = False)
	config.ParentalControl.setuppinactive = ConfigYesNo(default = False)
	config.ParentalControl.type = ConfigSelection(default = "blacklist", choices = [(LIST_WHITELIST, _("whitelist")), (LIST_BLACKLIST, _("blacklist"))])
	config.ParentalControl.setuppin = ConfigPIN(default = -1)
	
	config.ParentalControl.retries = ConfigSubsection()
	config.ParentalControl.retries.setuppin = ConfigSubsection()
	config.ParentalControl.retries.setuppin.tries = ConfigInteger(default = 3)
	config.ParentalControl.retries.setuppin.time = ConfigInteger(default = 3)	
	config.ParentalControl.retries.servicepin = ConfigSubsection()
	config.ParentalControl.retries.servicepin.tries = ConfigInteger(default = 3)
	config.ParentalControl.retries.servicepin.time = ConfigInteger(default = 3)
#	config.ParentalControl.configured = configElement("config.ParentalControl.configured", configSelection, 1, (("yes", _("yes")), ("no", _("no"))))
	#config.ParentalControl.mode = configElement("config.ParentalControl.mode", configSelection, 0, (("simple", _("simple")), ("complex", _("complex"))))
	#config.ParentalControl.storeservicepin = configElement("config.ParentalControl.storeservicepin", configSelection, 0, (("never", _("never")), ("5_minutes", _("5 minutes")), ("30_minutes", _("30 minutes")), ("60_minutes", _("60 minutes")), ("restart", _("until restart"))))
	#config.ParentalControl.servicepinactive = configElement("config.ParentalControl.servicepinactive", configSelection, 1, (("yes", _("yes")), ("no", _("no"))))
	#config.ParentalControl.setuppinactive = configElement("config.ParentalControl.setuppinactive", configSelection, 1, (("yes", _("yes")), ("no", _("no"))))
	#config.ParentalControl.type = configElement("config.ParentalControl.type", configSelection, 0, (("whitelist", _("whitelist")), ("blacklist", _("blacklist"))))
	#config.ParentalControl.setuppin = configElement("config.ParentalControl.setuppin", configSequence, "0000", configSequenceArg().get("PINCODE", (4, "")))

	config.ParentalControl.servicepin = ConfigSubList()

	for i in (0, 1, 2):
		config.ParentalControl.servicepin.append(ConfigPIN(default = -1))
		#config.ParentalControl.servicepin.append(configElement("config.ParentalControl.servicepin.level" + str(i), configSequence, "0000", configSequenceArg().get("PINCODE", (4, ""))))

class ParentalControl:
	def __init__(self):
		#Do not call open on init, because bouquets are not ready at that moment 
		self.open()
		self.serviceLevel = {}
		#Instead: Use Flags to see, if we already initialized config and called open
		self.configInitialized = False
		#This is the timer that is used to see, if the time for caching the pin is over
		#Of course we could also work without a timer and compare the times every
		#time we call isServicePlayable. But this might probably slow down zapping, 
		#That's why I decided to use a timer
		self.sessionPinTimer = eTimer()
		self.sessionPinTimer_conn = self.sessionPinTimer.timeout.connect(self.resetSessionPin)
	
	def serviceMethodWrapper(self, service, method, *args):
		#This method is used to call all functions that need a service as Parameter: 
		#It takes either a Service- Reference or a Bouquet- Reference and passes 
		#Either the service or all services contained in the bouquet to the method given
		#That way all other functions do not need to distinguish between service and bouquet. 
		if "FROM BOUQUET" in service:
			method( service , TYPE_BOUQUET , *args )
			servicelist = self.readServicesFromBouquet(service,"C")
			for ref in servicelist:
				sRef = str(ref[0])
				method( sRef , TYPE_BOUQUETSERVICE , *args )
		else:
			ref = ServiceReference(service)
			sRef = str(ref)
			method( sRef , TYPE_SERVICE , *args )
	
	def setServiceLevel(self, service, type, level):
		self.serviceLevel[service] = level

	def isServicePlayable(self, ref, callback):
		if not config.ParentalControl.configured.value or not config.ParentalControl.servicepinactive.value:
			return True
		#Check if configuration has already been read or if the significant values have changed.
		#If true: read the configuration 
		if self.configInitialized == False or self.storeServicePin != config.ParentalControl.storeservicepin.value or self.storeServicePinCancel != config.ParentalControl.storeservicepincancel.value:
			self.getConfigValues()
		service = ref.toCompareString()
		splitted = service.split(':')
		if splitted[0] == '1' and len(splitted[-1]) and splitted[-1][0] == '/': # check if this is a dvb playback
			serviceHandler = eServiceCenter.getInstance()
			info = serviceHandler.info(ref)
			recrefstr = info and info.getInfoString(ref, iServiceInformation.sServiceref) # get original servicereference of recording...
			if recrefstr:
				recref = eServiceReference(recrefstr)
				service = recref.toCompareString()
		if (config.ParentalControl.type.value == LIST_WHITELIST and not self.whitelist.has_key(service)) or (config.ParentalControl.type.value == LIST_BLACKLIST and self.blacklist.has_key(service)):
			#Check if the session pin is cached and return the cached value, if it is.
			if self.sessionPinCached == True:
				#As we can cache successful pin- entries as well as canceled pin- entries,
				#We give back the last action 
				return self.sessionPinCachedValue
			self.callback = callback
			#Someone started to implement different levels of protection. Seems they were never completed
			#I did not throw out this code, although it is of no use at the moment
			levelNeeded = 0
			if self.serviceLevel.has_key(service):
				levelNeeded = self.serviceLevel[service]
			pinList = self.getPinList()[:levelNeeded + 1]
			Notifications.AddNotificationWithCallback(boundFunction(self.servicePinEntered, ref), PinInput, triesEntry = config.ParentalControl.retries.servicepin, pinList = pinList, service = ServiceReference(ref).getServiceName(), title = _("this service is protected by a parental control pin"), windowTitle = _("Parental control"), domain = "ParentalControl")
			return False
		else:
			return True
		
	def protectService(self, service):
		if config.ParentalControl.type.value == LIST_WHITELIST:
			if self.whitelist.has_key(service):
				self.serviceMethodWrapper(service, self.removeServiceFromList, self.whitelist)
				#self.deleteWhitelistService(service)
		else: # blacklist
			if not self.blacklist.has_key(service):
				self.serviceMethodWrapper(service, self.addServiceToList, self.blacklist)
				#self.addBlacklistService(service)
		#print "whitelist:", self.whitelist
		#print "blacklist:", self.blacklist

	def unProtectService(self, service):
		#print "unprotect"
		#print "config.ParentalControl.type.value:", config.ParentalControl.type.value
		if config.ParentalControl.type.value == LIST_WHITELIST:
			if not self.whitelist.has_key(service):
				self.serviceMethodWrapper(service, self.addServiceToList, self.whitelist)
				#self.addWhitelistService(service)
		else: # blacklist
			if self.blacklist.has_key(service):
				self.serviceMethodWrapper(service, self.removeServiceFromList, self.blacklist)
				#self.deleteBlacklistService(service)
		#print "whitelist:", self.whitelist
		#print "blacklist:", self.blacklist

	def getProtectionLevel(self, service):
		if (config.ParentalControl.type.value == LIST_WHITELIST and not self.whitelist.has_key(service)) or (config.ParentalControl.type.value == LIST_BLACKLIST and self.blacklist.has_key(service)):
			if self.serviceLevel.has_key(service):
				return self.serviceLevel[service]
			else:
				return 0
		else:
			return -1

	def getProtectionType(self, service):
		#New method used in ParentalControlList: This method does not only return
		#if a service is protected or not, it also returns, why (whitelist or blacklist, service or bouquet)
		sImage = ""
		if (config.ParentalControl.type.value == LIST_WHITELIST):
			if self.whitelist.has_key(service):
				if TYPE_SERVICE in self.whitelist[service]:
					sImage = IMG_WHITESERVICE
				else:
					sImage = IMG_WHITEBOUQUET
		elif (config.ParentalControl.type.value == LIST_BLACKLIST):
			if self.blacklist.has_key(service):
				if TYPE_SERVICE in self.blacklist[service]:
					sImage = IMG_BLACKSERVICE
				else:
					sImage = IMG_BLACKBOUQUET
		bLocked = self.getProtectionLevel(service) != -1
		return (bLocked,sImage)
	
	def getConfigValues(self):	
		#Read all values from configuration 
		self.checkPinInterval = False
		self.checkPinIntervalCancel = False
		self.checkSessionPin = False
		self.checkSessionPinCancel = False
		
		self.sessionPinCached = False
		self.pinIntervalSeconds = 0
		self.pinIntervalSecondsCancel = 0

		self.storeServicePin = config.ParentalControl.storeservicepin.value
		self.storeServicePinCancel = config.ParentalControl.storeservicepincancel.value
		
		if self.storeServicePin == "never":
			pass
		elif self.storeServicePin == "standby":
			self.checkSessionPin = True
		else:
			self.checkPinInterval = True
			iMinutes = float(self.storeServicePin)
			iSeconds = iMinutes*60
			self.pinIntervalSeconds = int(iSeconds)
	
		if self.storeServicePinCancel == "never":
			pass
		elif self.storeServicePinCancel == "standby":
			self.checkSessionPinCancel = True
		else:
			self.checkPinIntervalCancel = True
			iMinutes = float(self.storeServicePinCancel)
			iSeconds = iMinutes*60
			self.pinIntervalSecondsCancel = int(iSeconds)
	
		self.configInitialized = True
		# Reset PIN cache on standby: Use StandbyCounter- Config- Callback
		config.misc.standbyCounter.addNotifier(self.standbyCounterCallback, initial_call = False)

	def standbyCounterCallback(self, configElement):
		self.resetSessionPin()
		
	def resetSessionPin(self):
		#Reset the session pin, stop the timer
		self.sessionPinCached = False
		self.sessionPinTimer.stop()

	def getCurrentTimeStamp(self):
		return time.time()

	def getPinList(self):
		return [ x.value for x in config.ParentalControl.servicepin ]
		
	def servicePinEntered(self, service, result):

		if result is not None and result:
			#This is the new function of caching the service pin
			#save last session and time of last entered pin...
			if self.checkSessionPin == True:
				self.sessionPinCached = True
				self.sessionPinCachedValue = True
			if self.checkPinInterval == True:
				self.sessionPinCached = True
				self.sessionPinCachedValue = True
				self.sessionPinTimer.start(self.pinIntervalSeconds*1000,1)
			self.callback(ref = service)
		else:
			#This is the new function of caching cancelling of service pin
			if result is not None:
				Notifications.AddNotification(MessageBox,  _("The pin code you entered is wrong."), MessageBox.TYPE_ERROR, domain = "ParentalControl")
			else:
				if self.checkSessionPinCancel == True:
					self.sessionPinCached = True
					self.sessionPinCachedValue = False
				if self.checkPinIntervalCancel == True:
					self.sessionPinCached = True
					self.sessionPinCachedValue = False
					self.sessionPinTimer.start(self.pinIntervalSecondsCancel*1000,1) 
			
	def saveListToFile(self,sWhichList):
		#Replaces saveWhiteList and saveBlackList: 
		#I don't like to have two functions with identical code...
		if sWhichList == LIST_BLACKLIST:
			vList = self.blacklist
		else:
			vList = self.whitelist
		file = open(resolveFilename(SCOPE_CONFIG, sWhichList), 'w')
		for sService,sType in vList.iteritems():
			#Only Services that are selected directly and Bouqets are saved. 
			#Services that are added by a bouquet are not saved. 
			#This is the reason for the change in self.whitelist and self.blacklist
			if TYPE_SERVICE in sType or TYPE_BOUQUET in sType:
				file.write(str(sService) + "\n")
		file.flush()
		fsync(file.fileno())
		file.close()

	def openListFromFile(self,sWhichList):
		#Replaces openWhiteList and openBlackList: 
		#I don't like to have two functions with identical code...
		if sWhichList == LIST_BLACKLIST:
			self.blacklist = {}
			vList = self.blacklist
		else:
			self.whitelist = {}
			vList = self.whitelist
		try:
			file = open(resolveFilename(SCOPE_CONFIG, sWhichList ), 'r')
			lines = file.readlines()
			for x in lines:
				sPlain = x.strip()
				self.serviceMethodWrapper(sPlain, self.addServiceToList, vList)
			file.close()
		except IOError:
			pass
	
	def addServiceToList(self, service, type, vList):
		#Replaces addWhitelistService and addBlacklistService
		#The lists are not only lists of service references any more. 
		#They are named lists with the service as key and an array of types as value:
		
		if vList.has_key(service):
			if not type in vList[service]:
				vList[service].append(type)
		else:
			vList[service] = [type]
	
	def removeServiceFromList(self, service, type, vList):
		#Replaces deleteWhitelistService and deleteBlacklistService
		if vList.has_key(service):
			if type in vList[service]:
				vList[service].remove(type)
			if not vList[service]:
				del vList[service]
		if self.serviceLevel.has_key(service):
			self.serviceLevel.remove(service)
		
	def readServicesFromBouquet(self,sBouquetSelection,formatstring):
		#This method gives back a list of services for a given bouquet
		from enigma import eServiceCenter, eServiceReference
		from Screens.ChannelSelection import service_types_tv
		serviceHandler = eServiceCenter.getInstance()
		refstr = sBouquetSelection
		root = eServiceReference(refstr)
		list = serviceHandler.list(root)
		if list is not None:
			services = list.getContent("CN", True) #(servicecomparestring, name)
			return services
		
	def save(self):
		# we need to open the files in case we havent's read them yet
		self.saveListToFile(LIST_BLACKLIST)
		self.saveListToFile(LIST_WHITELIST)
		
	def open(self):
		self.openListFromFile(LIST_BLACKLIST)
		self.openListFromFile(LIST_WHITELIST)

