# -*- coding: utf-8 -*-

import xbmc,xbmcgui,xbmcaddon,xbmcvfs,xbmcgui
import time,datetime,calendar
import urlparse,urllib

import numbers
import json
import subprocess
import webbrowser
import sys,os,copy
import stat
import uuid
import hashlib
import shutil
import imp
import pkgutil
import re
import platform
import zipfile
import threading

from resources.lib.modules import control
from resources.lib.modules import pytz


###################################################################
#---Translation
###################################################################
class Translation(object):

	@classmethod
	def string(self, id, utf8 = True, system = False):
		if isinstance(id, (int, long)):
			if system: result = xbmc.getLocalizedString(id)
			else: result = xbmcaddon.Addon(System.DescentAddon).getLocalizedString(id)
		else:
			try: result = str(id)
			except: result = id
		if utf8:
			try:
				if not '•' in result: result = Converter.unicode(string = result, umlaut = True).encode('utf-8')
			except:
				result = Converter.unicode(string = result, umlaut = True).encode('utf-8')
		return result


###################################################################
#---Format
###################################################################
class Format(object):
	ColorCustomize = False
	ColorNone = None
	ColorPrimary = 'FFA0C12C'
	ColorPrimary = 'FFFF00000'
	ColorSecondary = 'FF3C7DBF'
	ColorOrion = 'FF637385'
	ColorMain = 'FF2396FF'
	ColorAlternative = 'FF004F98'
	ColorSpecial = 'FF6C3483'
	ColorUltra = 'FF00A177'
	ColorExcellent = 'FF1E8449'
	ColorGood = 'FF668D2E'
	ColorMedium = 'FFB7950B'
	ColorPoor = 'FFBA4A00'
	ColorBad = 'FF922B21'
	ColorWhite = 'FFFFFFFF'
	ColorBlack = 'FF000000'
	ColorDisabled = 'FF888888'

	Gradients = {}

	FontNewline = '[CR]'
	FontSeparator = ' • '
	FontPassword = '••••••••••'
	FontDivider = ' - '
	FontSplitInterval = 50

	@classmethod
	def settingsColorUpdate(self, type):
		setting = 'interface.color.' + type
		color = Dialog.input(title = 35235, type = Dialog.InputAlphabetic, default = self.settingsColor(Settings.getString(setting)))
		if self.colorIsHex(color):
			while len(color) < 8: color = 'F' + color
			if len(color) > 8: color = color[:8]
			Settings.set(setting, self.fontColor(color, color))
		else:
			control.notification(title = 35235, message = 35236, icon = 'ERROR')

	@classmethod
	def settingsColor(self, color):
		try: return re.search('\\[.*\\](.*)\\[.*\\]', color, re.IGNORECASE).group(1)
		except: return ''

	@classmethod
	def colorIsHex(self, color):
		return re.match('[0-9a-fA-F]*', color)

	@classmethod
	def colorToRgb(self, hex):
		return [int(hex[i:i+2], 16) for i in range(2,8,2)]

	@classmethod
	def colorToHex(self, rgb):
		rgb = [int(i) for i in rgb]
		return 'FF' + ''.join(['0{0:x}'.format(i) if i < 16 else '{0:x}'.format(i) for i in rgb])

	@classmethod
	def colorGradient(self, startHex, endHex, count = 10):
		key = '%s_%s_%s' % (str(startHex), str(endHex), str(count))
		if not key in Format.Gradients:
			start = self.colorToRgb(startHex)
			end = self.colorToRgb(endHex)
			colors = [start]
			for i in range(1, count):
				vector = [int(start[j] + (float(i) / (count-1)) * (end[j] - start[j])) for j in range(3)]
				colors.append(vector)
			Format.Gradients[key] = [self.colorToHex(i) for i in colors]
		return Format.Gradients[key]

	@classmethod
	def colorGradientIncrease(self, count = 10):
		return self.colorGradient(Format.ColorBad, Format.ColorExcellent, count)

	@classmethod
	def colorGradientDecrease(self, count = 10):
		return self.colorGradient(Format.ColorExcellent, Format.ColorBad, count)

	@classmethod
	def colorChange(self, color, change = 10):
		if color:
			color = self.colorToRgb(color)
			color = [i + change for i in color]
			color = [min(255, max(0, i)) for i in color]
			return self.colorToHex(color)
		else:
			return None

	@classmethod
	def colorLighter(self, color, change = 10):
		return self.colorChange(color, change)

	@classmethod
	def colorDarker(self, color, change = 10):
		return self.colorChange(color, -change)

	@classmethod
	def __translate(self, label, utf8 = True):
		return Translation.string(label, utf8 = utf8)


	@classmethod
	def font(self, label, color = None, bold = None, italic = None, light = None, uppercase = None, lowercase = None, capitalcase = None, newline = None, separator = None, translate = True):
		if label == None: return label
		if translate: label = self.__translate(label)
		if label:
			if color:
				label = self.fontColor(label, color, translate = False)
			if bold:
				label = self.fontBold(label, translate = False)
			if italic:
				label = self.fontItalic(label, translate = False)
			if light:
				label = self.fontLight(label, translate = False)
			if uppercase:
				label = self.fontUppercase(label, translate = False)
			elif lowercase:
				label = self.fontLowercase(label, translate = False)
			elif capitalcase:
				label = self.fontCapitalcase(label, translate = False)
			if newline:
				label += self.fontNewline(translate = False)
			if separator:
				label += self.fontSeparator(translate = False)
			return label
		else:
			return ''

	@classmethod
	def fontColor(self, label, color, translate = True):
		if color == None: return label
		if len(color) == 6: color = 'FF' + color
		if translate: label = self.__translate(label)
		return '[COLOR ' + color + ']' + label + '[/COLOR]'

	@classmethod
	def fontBold(self, label, translate = True):
		if translate: label = self.__translate(label)
		return '[B]' + label + '[/B]'

	@classmethod
	def fontItalic(self, label, translate = True):
		if translate: label = self.__translate(label)
		return '[I]' + label + '[/I]'

	@classmethod
	def fontLight(self, label, translate = True):
		if translate: label = self.__translate(label)
		return '[LIGHT]' + label + '[/LIGHT]'

	@classmethod
	def fontUppercase(self, label, translate = True):
		if translate: label = self.__translate(label)
		return '[UPPERCASE]' + label + '[/UPPERCASE]'

	@classmethod
	def fontLowercase(self, label, translate = True):
		if translate: label = self.__translate(label)
		return '[LOWERCASE]' + label + '[/LOWERCASE]'

	@classmethod
	def fontCapitalcase(self, label, translate = True):
		if translate: label = self.__translate(label)
		return '[CAPITALIZE]' + label + '[/CAPITALIZE]'

	@classmethod
	def fontNewline(self):
		return Format.FontNewline

	@classmethod
	def fontSeparator(self, color = ColorDisabled):
		return self.fontColor(Format.FontSeparator, color = color, translate = False)

	@classmethod
	def fontDivider(self):
		return Format.FontDivider

	@classmethod
	def fontSplit(self, label, interval = None, type = None):
		if not interval: interval = Format.FontSplitInterval
		if not type: type = Format.FontNewline
		return re.sub('(.{' + str(interval) + '})', '\\1' + type, label, 0, re.DOTALL)

	@classmethod
	def color(self, label, color):
		return self.fontColor(label, color)

	@classmethod
	def bold(self, label):
		return self.fontBold(label)

	@classmethod
	def italic(self, label):
		return self.fontItalic(label)

	@classmethod
	def light(self, label):
		return self.fontLight(label)

	@classmethod
	def uppercase(self, label):
		return self.fontUppercase(label)

	@classmethod
	def lowercase(self, label):
		return self.fontLowercase(label)

	@classmethod
	def capitalcase(self, label):
		return self.fontCapitalcase(label)

	@classmethod
	def newline(self):
		return self.fontNewline()

	@classmethod
	def separator(self):
		return self.fontSeparator()

	@classmethod
	def divider(self):
		return self.fontDivider()

	@classmethod
	def split(self, label, interval = None, type = None):
		return self.fontSplit(label = label, interval = interval, type = type)



###################################################################
#---Settings
###################################################################
class Settings(object):

	Database = 'settings'
	Lock = threading.Lock()

	SettingsId = 10140

	ParameterDefault = 'default'
	ParameterValue = 'value'
	ParameterVisible = 'visible'

	CategoryCount = 11

	CategoryGeneral = 0
	CategoryInterface = 1
	CategoryScraping = 2
	CategoryProviders = 3
	CategoryAccounts = 4
	CategoryStreaming = 5
	CategoryManual = 6
	CategoryAutomation = 7
	CategoryDownloads = 8
	CategorySubtitles = 9
	CategoryLibrary = 10
	CategoryLightpack = 11

	CacheInitialized = False
	CacheEnabled = False
	CacheMainData = None
	CacheMainValues = None
	CacheUserData = None
	CacheUserValues = None

	PathDefault = 'Default'
	Paths = {
		'downloads.manual.path.combined' 		: 'special://userdata/addon_data/plugin.video.descent/Downloads/Manual/',
		'downloads.manual.path.movies'			: 'special://userdata/addon_data/plugin.video.descent/Downloads/Manual/Movies/',
		'downloads.manual.path.shows'			: 'special://userdata/addon_data/plugin.video.descent/Downloads/Manual/Shows/',
		'downloads.manual.path.documentaries'	: 'special://userdata/addon_data/plugin.video.descent/Downloads/Manual/Documentaries/',
		'downloads.manual.path.shorts'			: 'special://userdata/addon_data/plugin.video.descent/Downloads/Manual/Shorts/',
		'downloads.manual.path.other'			: 'special://userdata/addon_data/plugin.video.descent/Downloads/Manual/Other/',

		'downloads.cache.path.combined'			: 'special://userdata/addon_data/plugin.video.descent/Downloads/Cache/',
		'downloads.cache.path.movies'			: 'special://userdata/addon_data/plugin.video.descent/Downloads/Cache/Movies/',
		'downloads.cache.path.shows'			: 'special://userdata/addon_data/plugin.video.descent/Downloads/Cache/Shows/',
		'downloads.cache.path.documentaries'	: 'special://userdata/addon_data/plugin.video.descent/Downloads/Cache/Documentaries/',
		'downloads.cache.path.shorts'			: 'special://userdata/addon_data/plugin.video.descent/Downloads/Cache/Shorts/',
		'downloads.cache.path.other'			: 'special://userdata/addon_data/plugin.video.descent/Downloads/Cache/Other/',

		'library.locations.combined'			: 'special://userdata/addon_data/plugin.video.descent/Library/',
		'library.locations.movies'				: 'special://userdata/addon_data/plugin.video.descent/Library/Movies/',
		'library.locations.shows'				: 'special://userdata/addon_data/plugin.video.descent/Library/Shows/',
		'library.locations.documentaries'		: 'special://userdata/addon_data/plugin.video.descent/Library/Documentaries/',
		'library.locations.shorts'				: 'special://userdata/addon_data/plugin.video.descent/Library/Shorts/',
	}

	@classmethod
	def _database(self):
		from resources.lib.indexers import database
		return database.Database.instance(Settings.Database, default = File.joinPath(System.path(), 'resources'))

	@classmethod
	def path(self, id):
		path = self.get(id)
		if path == Settings.PathDefault or path.strip() == '' or not path:
			path = Settings.Paths[id]
		return path

	@classmethod
	def pathAddon(self):
		return File.joinPath(System.path(), 'resources', 'settings.xml')

	@classmethod
	def pathProfile(self):
		return File.joinPath(System.profile(), 'settings.xml')

	@classmethod
	def clear(self):
		File.delete(File.joinPath(System.profile(), 'settings.xml'))
		File.delete(File.joinPath(System.profile(), 'settings.db'))

	@classmethod
	def cache(self):
		if not Settings.CacheInitialized:
			global SettingsAddon
			Settings.CacheInitialized = True
			Settings.CacheEnabled = Converter.boolean(SettingsAddon.getSetting('general.settings.cache'))
			Settings.CacheMainValues = {}
			Settings.CacheUserValues = {}

	@classmethod
	def cacheClear(self):
		global SettingsAddon
		SettingsAddon = xbmcaddon.Addon(System.DescentAddon)

		Settings.CacheInitialized = False
		Settings.CacheEnabled = False
		Settings.CacheMainData = None
		Settings.CacheMainValues = None
		Settings.CacheUserData = None
		Settings.CacheUserValues = None

	@classmethod
	def cacheEnabled(self):
		self.cache()
		return Settings.CacheEnabled

	@classmethod
	def cacheGet(self, id, raw, database = False):
		self.cache()
		if raw:
			if Settings.CacheMainData == None:
				Settings.CacheMainData = File.readNow(self.pathAddon())
			data = Settings.CacheMainData
			values = Settings.CacheMainValues
			parameter = Settings.ParameterDefault
		else:
			if Settings.CacheUserData == None:
				Settings.CacheUserData = File.readNow(self.pathProfile())
			data = Settings.CacheUserData
			values = Settings.CacheUserValues
			parameter = Settings.ParameterValue

		if id in values: 
			return values[id]
		elif database:
			result = self._getDatabase(id = id)
			values[id] = result
			return result
		else:
			result = self.raw(id = id, parameter = parameter, data = data)
			if result == None: 
				global SettingsAddon
				result = SettingsAddon.getSetting(id)
			values[id] = result
			return result


	@classmethod
	def cacheSet(self, id, value):
		self.cache()
		Settings.CacheUserValues[id] = value


	@classmethod
	def data(self):
		data = None
		path = File.joinPath(System.path(), 'resources', 'settings.xml')
		with open(path, 'r') as file:
			data = file.read()
		return data


	@classmethod
	def set(self, id, value, cached = False):
		if isinstance(value, (dict, list, tuple)):
			from resources.lib.indexers import database
			database = self._database()
			database._insert('INSERT OR IGNORE INTO %s (id) VALUES(?);' % Settings.Database, parameters = (id,))
			database._update('UPDATE %s SET data = ? WHERE id = ?;' % Settings.Database, parameters = (Converter.jsonTo(value), id))
			if cached or self.cacheEnabled(): self.cacheSet(id = id, value = value)
		else:
			if value is True or value is False: 
				value = Converter.boolean(value, string = True)
			else:
				value = str(value)
			global SettingsAddon
			Settings.Lock.acquire()
			SettingsAddon.setSetting(id = id, value = value)
			Settings.Lock.release()
			if cached or self.cacheEnabled(): self.cacheSet(id = id, value = value)


	@classmethod
	def raw(self, id, parameter = ParameterDefault, data = None):
		try:
			if data == None: data = self.data()
			indexStart = data.find('id="%s"' % id)
			if indexStart < 0: return None
			indexStart += 4
			indexStart = data.find('"', indexStart)
			if indexStart < 0: return None
			indexEnd = data.find('/>', indexStart)
			if indexEnd < 0: indexEnd = data.find('/setting>', indexStart) 
			if indexEnd < 0: return None
			data = data[indexStart : indexEnd]
			indexStart = data.find(parameter + '="')
			if indexStart >= 0:
				indexStart = data.find('"', indexStart) + 1
				indexEnd = data.find('"', indexStart)
			elif parameter == Settings.ParameterValue and System.versionKodiNew():
				indexStart = data.find('>') + 1
				indexEnd = data.find('<', indexStart)
			else:
				return None
			return data[indexStart : indexEnd]
		except:
			return None


	@classmethod
	def _getDatabase(self, id):
		try:
			from resources.lib.indexers import database
			return Converter.jsonFrom(self._database()._selectValue('SELECT data FROM %s WHERE id = "%s";' % (Settings.Database, id)))
		except: return None


	@classmethod
	def get(self, id, raw = False, cached = True, database = False):
		if cached and self.cacheEnabled():
			return self.cacheGet(id = id, raw = raw, database = database)
		elif raw:
			return self.raw(id)
		elif database:
			return self._getDatabase(id)
		else:
			global SettingsAddon
			return SettingsAddon.getSetting(id)


	@classmethod
	def getString(self, id, raw = False, cached = True):
		return self.get(id = id, raw = raw, cached = cached)


	@classmethod
	def getBoolean(self, id, raw = False, cached = True):
		return Converter.boolean(self.get(id = id, raw = raw, cached = cached))


	@classmethod
	def getBool(self, id, raw = False, cached = True):
		return self.getBoolean(id = id, raw = raw, cached = cached)


	@classmethod
	def getNumber(self, id, raw = False, cached = True):
		return self.getDecimal(id = id, raw = raw, cached = cached)


	@classmethod
	def getDecimal(self, id, raw = False, cached = True):
		value = self.get(id = id, raw = raw, cached = cached)
		try: return float(value)
		except: return 0


	@classmethod
	def getFloat(self, id, raw = False, cached = True):
		return self.getDecimal(id = id, raw = raw, cached = cached)


	@classmethod
	def getInteger(self, id, raw = False, cached = True):
		value = self.get(id = id, raw = raw, cached = cached)
		try: return int(value)
		except: return 0


	@classmethod
	def getInt(self, id, raw = False, cached = True):
		return self.getInteger(id = id, raw = raw, cached = cached)


	@classmethod
	def getList(self, id, raw = False, cached = True):
		result = self.get(id = id, raw = raw, cached = cached, database = True)
		return [] if result == None or result == '' else result


	@classmethod
	def getObject(self, id, raw = False, cached = True):
		result = self.get(id = id, raw = raw, cached = cached, database = True)
		return None if result == None or result == '' else result



###################################################################
#---Logger
###################################################################
class Logger(object):

	TypeNotice = xbmc.LOGNOTICE
	TypeError = xbmc.LOGERROR
	TypeSevere = xbmc.LOGSEVERE
	TypeFatal = xbmc.LOGFATAL
	TypeDefault = TypeNotice

	@classmethod
	def log(self, message, message2 = None, message3 = None, message4 = None, message5 = None, name = True, parameters = None, level = TypeDefault):
		divider = ' '
		message = str(message)
		if message2: message += divider + str(message2)
		if message3: message += divider + str(message3)
		if message4: message += divider + str(message4)
		if message5: message += divider + str(message5)
		if name:
			nameValue = control.addonName().upper()
			if not name == True:
				nameValue += ' ' + name
			nameValue += ' ' + System.version()
			if parameters:
				nameValue += ' ['
				if isinstance(parameters, basestring):
					nameValue += parameters
				else:
					nameValue += ', '.join([str(parameter) for parameter in parameters])
				nameValue += ']'
			nameValue += ': '
			message = nameValue + message
		xbmc.log(message, level)

	@classmethod
	def error(self, message = None, exception = True):
		if exception:
			type, value, traceback = sys.exc_info()
			filename = traceback.tb_frame.f_code.co_filename
			linenumber = traceback.tb_lineno
			name = traceback.tb_frame.f_code.co_name
			errortype = type.__name__
			errormessage = value.message
			if message:
				message += ' -> '
			else:
				message = ''
			message += str(errortype) + ' -> ' + str(errormessage)
			parameters = [filename, linenumber, name]
		else:
			parameters = None
		self.log(message, name = 'ERROR', parameters = parameters, level = Logger.TypeError)



###################################################################
#---Icon
###################################################################
class Icon(object):

	TypeIcon = 'icon'
	TypeThumb = 'thumb'
	TypePoster = 'poster'
	TypeBanner = 'banner'
	TypeDefault = TypeIcon

	QualitySmall = 'small'
	QualityLarge = 'large'
	QualityDefault = QualityLarge

	SpecialNone = None
	SpecialQuality = 'quality'
	SpecialDonations = 'donations'
	SpecialNotifications = 'notifications'

	ThemeInitialized = False
	ThemePath = None
	ThemeIcon = None
	ThemeThumb = None
	ThemePoster = None
	ThemeBanner = None

	@classmethod
	def _initialize(self, special = SpecialNone):
		if special == False or not special == Icon.ThemeInitialized:
			Icon.ThemeInitialized = special
			if special: theme = special
			else: theme = tools.Settings.getString('interface.theme.icon').lower()

			if not theme in ['default', '-', '']:

				theme = theme.replace(' ', '').lower()
				if 'glass' in theme:
					theme = theme.replace('(', '').replace(')', '')
				else:
					index = theme.find('(')
					if index >= 0: theme = theme[:index]

				addon = tools.System.pathResources() if theme in ['white', Icon.SpecialQuality, Icon.SpecialDonations, Icon.SpecialNotifications] else tools.System.pathIcons()
				Icon.ThemePath = tools.File.joinPath(addon, 'resources', 'media', 'icons', theme)

				quality = tools.Settings.getInteger('interface.theme.icon.quality')
				if quality == 0:
					if Skin.isAeonNox():
						Icon.ThemeIcon = Icon.QualitySmall
						Icon.ThemeThumb = Icon.QualitySmall
						Icon.ThemePoster = Icon.QualityLarge
						Icon.ThemeBanner = Icon.QualityLarge
					else:
						Icon.ThemeIcon = Icon.QualityLarge
						Icon.ThemeThumb = Icon.QualityLarge
						Icon.ThemePoster = Icon.QualityLarge
						Icon.ThemeBanner = Icon.QualityLarge
				elif quality == 1:
					Icon.ThemeIcon = Icon.QualitySmall
					Icon.ThemeThumb = Icon.QualitySmall
					Icon.ThemePoster = Icon.QualitySmall
					Icon.ThemeBanner = Icon.QualitySmall
				elif quality == 2:
					Icon.ThemeIcon = Icon.QualityLarge
					Icon.ThemeThumb = Icon.QualityLarge
					Icon.ThemePoster = Icon.QualityLarge
					Icon.ThemeBanner = Icon.QualityLarge
				else:
					Icon.ThemeIcon = Icon.QualityLarge
					Icon.ThemeThumb = Icon.QualityLarge
					Icon.ThemePoster = Icon.QualityLarge
					Icon.ThemeBanner = Icon.QualityLarge

	@classmethod
	def exists(self, icon, type = TypeDefault, default = None, special = SpecialNone, quality = None):
		return tools.File.exists(self.path(icon = icon, type = type, default = default, special = special, quality = quality))

	@classmethod
	def path(self, icon, type = TypeDefault, default = None, special = SpecialNone, quality = None):
		if icon == None: return None
		self._initialize(special = special)
		if Icon.ThemePath == None:
			return default
		else:
			if quality == None:
				if type == Icon.TypeIcon: type = Icon.ThemeIcon
				elif type == Icon.TypeThumb: type = Icon.ThemeThumb
				elif type == Icon.TypePoster: type = Icon.ThemePoster
				elif type == Icon.TypeBanner: type = Icon.ThemeBanner
				else: type = Icon.ThemeIcon
			else:
				type = quality
			if not icon.endswith('.png'): icon += '.png'
			return tools.File.joinPath(Icon.ThemePath, type, icon)

	@classmethod
	def pathAll(self, icon, default = None, special = SpecialNone):
		return (
			self.pathIcon(icon = icon, default = default, special = special),
			self.pathThumb(icon = icon, default = default, special = special),
			self.pathPoster(icon = icon, default = default, special = special),
			self.pathBanner(icon = icon, default = default, special = special)
		)

	@classmethod
	def pathIcon(self, icon, default = None, special = SpecialNone):
		return self.path(icon = icon, type = Icon.TypeIcon, default = default, special = special)

	@classmethod
	def pathThumb(self, icon, default = None, special = SpecialNone):
		return self.path(icon = icon, type = Icon.TypeThumb, default = default, special = special)

	@classmethod
	def pathPoster(self, icon, default = None, special = SpecialNone):
		return self.path(icon = icon, type = Icon.TypePoster, default = default, special = special)

	@classmethod
	def pathBanner(self, icon, default = None, special = SpecialNone):
		return self.path(icon = icon, type = Icon.TypeBanner, default = default, special = special)

	@classmethod
	def select(self):
		id = tools.Extensions.IdGaiaIcons
		items = ['Default', 'White']
		getMore = Format.fontBold(Translation.string(33739))
		if tools.Extensions.installed(id):
			items.extend(['Black', 'Glass (Light)', 'Glass (Dark)', 'Shadow (Grey)', 'Fossil (Grey)', 'Navy (Blue)', 'Cerulean (Blue)', 'Sky (Blue)', 'Pine (Green)', 'Lime (Green)', 'Ruby (Red)', 'Candy (Red)', 'Tiger (Orange)', 'Pineapple (Yellow)', 'Violet (Purple)', 'Magenta (Pink)', 'Amber (Brown)'])
		else:
			items.extend([getMore])
		choice = Dialog.options(title = 33338, items = items)
		if choice >= 0:
			if items[choice] == getMore:
				choice = Dialog.option(title = 33338, message = 33741, labelConfirm = 33736, labelDeny = 33743)
				if choice:
					tools.Extensions.enable(id = id)
			else:
				tools.Settings.set('interface.theme.icon', items[choice])



###################################################################
#---Dialog
###################################################################
class Dialog(object):

	IconPlain = 'logo'
	IconInformation = 'information'
	IconWarning = 'warning'
	IconError = 'error'
	IconSuccess = 'success'

	IconNativeLogo = 'nativelogo'
	IconNativeInformation = 'nativeinformation'
	IconNativeWarning = 'nativewarning'
	IconNativeError = 'nativeerror'

	InputAlphabetic = xbmcgui.INPUT_ALPHANUM 
	InputNumeric = xbmcgui.INPUT_NUMERIC 
	InputDate = xbmcgui.INPUT_DATE 
	InputTime = xbmcgui.INPUT_TIME 
	InputIp = xbmcgui.INPUT_IPADDRESS 
	InputPassword = xbmcgui.INPUT_PASSWORD 

	BrowseFile = 1
	BrowseImage = 2
	BrowseDirectoryRead = 0
	BrowseDirectoryWrite = 3
	BrowseDefault = BrowseFile

	PrefixColor = Format.ColorPrimary
	PrefixBack = '« '
	PrefixNext = '» '

	IdDialogText = 10147
	IdDialogProgress = 10101
	IdDialogOk = 12002
	IdDialogNotification = 10107

	@classmethod
	def prefix(self, text, prefix, color = PrefixColor, bold = True):
		return Format.font(prefix, color = color, bold = bold, translate = False) + Translation.string(text)

	@classmethod
	def prefixBack(self, text, color = PrefixColor, bold = None):
		return self.prefix(text = text, prefix = Dialog.PrefixBack, color = color, bold = bold)

	@classmethod
	def prefixNext(self, text, color = PrefixColor, bold = None):
		return self.prefix(text = text, prefix = Dialog.PrefixNext, color = color, bold = bold)

	@classmethod
	def prefixContains(self, text):
		try: return Dialog.PrefixBack in text or Dialog.PrefixNext in text
		except: return False

	@classmethod
	def close(self, id, sleep = None):
		xbmc.executebuiltin('Dialog.Close(%s,true)' % str(id))
		if sleep: time.sleep(sleep / 1000.0)

	@classmethod
	def closeOk(self, sleep = None):
		self.close(id = self.IdDialogOk, sleep = sleep)

	@classmethod
	def closeNotification(self, sleep = None):
		self.close(id = self.IdDialogNotification, sleep = sleep)

	@classmethod
	def closeAll(self, sleep = None):
		xbmc.executebuiltin('Dialog.Close(all,true)')
		if sleep: time.sleep(sleep / 1000.0)

	@classmethod
	def closeAllProgress(self, sleep = None):
		xbmc.executebuiltin('Dialog.Close(progressdialog,true)')
		xbmc.executebuiltin('Dialog.Close(extendedprogressdialog,true)')
		if sleep: time.sleep(sleep / 1000.0)

	@classmethod
	def closeAllNative(self, sleep = None):
		xbmc.executebuiltin('Dialog.Close(virtualkeyboard,true)')
		xbmc.executebuiltin('Dialog.Close(yesnodialog,true)')
		xbmc.executebuiltin('Dialog.Close(progressdialog,true)')
		xbmc.executebuiltin('Dialog.Close(extendedprogressdialog,true)')
		xbmc.executebuiltin('Dialog.Close(sliderdialog,true)')
		xbmc.executebuiltin('Dialog.Close(okdialog,true)')
		xbmc.executebuiltin('Dialog.Close(selectdialog,true)')
		if sleep: time.sleep(sleep / 1000.0)

	@classmethod
	def aborted(self):
		return xbmc.abortRequested

	@classmethod
	def windowId(self):
		return xbmcgui.getCurrentWindowId()

	@classmethod
	def windowVisible(self, id):
		return self.windowId() == id

	@classmethod
	def dialogId(self):
		return xbmcgui.getCurrentWindowDialogId()

	@classmethod
	def dialogVisible(self, id):
		return self.dialogId() == id

	@classmethod
	def dialogProgressVisible(self):
		return self.dialogVisible(Dialog.IdDialogProgress)

	@classmethod
	def confirm(self, message, title = None):
		return xbmcgui.Dialog().ok(self.title(title), self.__translate(message))

	@classmethod
	def select(self, items, multiple = False, selection = None, title = None):
		return self.options(items = items, multiple = multiple, selection = selection, title = title)

	@classmethod
	def option(self, message, labelConfirm = None, labelDeny = None, title = None):
		if not labelConfirm == None:
			labelConfirm = self.__translate(labelConfirm)
		if not labelDeny == None:
			labelDeny = self.__translate(labelDeny)
		return xbmcgui.Dialog().yesno(self.title(title), self.__translate(message), yeslabel = labelConfirm, nolabel = labelDeny)

	@classmethod
	def options(self, items, multiple = False, selection = None, title = None):
		if multiple:
			try: return xbmcgui.Dialog().multiselect(self.title(title), items, preselect = selection)
			except: return xbmcgui.Dialog().multiselect(self.title(title), items)
		else:
			try: return xbmcgui.Dialog().select(self.title(title), items, preselect = selection)
			except: return xbmcgui.Dialog().select(self.title(title), items)


	@classmethod
	def context(self, items = None, labels = None):
		if items:
			labels = [i[0] for i in items]
			choice = xbmcgui.Dialog().contextmenu(labels)
			if choice >= 0: return items[choice][1]()
			else: return False
		else:
			return xbmcgui.Dialog().contextmenu(labels)

	@classmethod
	def progress(self, message = None, background = False, title = None):
		if background:
			dialog = xbmcgui.DialogProgressBG()
		else:
			dialog = xbmcgui.DialogProgress()
		if not message:
			message = ''
		else:
			message = self.__translate(message)
		title = self.title(title)
		dialog.create(title, message)
		if background:
			dialog.update(0, title, message)
		else:
			dialog.update(0, message)
		return dialog

	@classmethod
	def input(self, type = InputAlphabetic, verify = False, confirm = False, hidden = False, default = None, title = None):
		default = '' if default == None else default
		if verify:
			option = xbmcgui.PASSWORD_VERIFY
			if isinstance(verify, basestring):
				default = verify
		elif confirm:
			option = 0
		elif hidden:
			option = xbmcgui.ALPHANUM_HIDE_INPUT
		else:
			option = None
		if option == None: result = xbmcgui.Dialog().input(self.title(title), str(default), type = type)
		else: result = xbmcgui.Dialog().input(self.title(title), str(default), type = type, option = option)

		if verify:
			return not result == ''
		else:
			return result

	@classmethod
	def inputPassword(self, verify = False, confirm = False, title = None):
		return self.input(title = title, type = Dialog.InputPassword, verify = verify, confirm = confirm)

	@classmethod
	def browse(self, type = BrowseDefault, default = None, multiple = False, mask = [], title = None):
		if default == None: default = File.joinPath(System.pathHome(), '') 
		if mask == None: mask = []
		elif isinstance(mask, basestring): mask = [mask]
		for i in range(len(mask)):
			mask[i] = mask[i].lower()
			if not mask[i].startswith('.'):
				mask[i] = '.' + mask[i]
		mask = '|'.join(mask)
		return xbmcgui.Dialog().browse(type, self.title(title), 'files', mask, True, False, default, multiple)

	@classmethod
	def page(self, message, title = None):
		xbmc.executebuiltin('ActivateWindow(%d)' % Dialog.IdDialogText)
		time.sleep(0.5)
		window = xbmcgui.Window(Dialog.IdDialogText)
		retry = 50
		while retry > 0:
			try:
				time.sleep(0.01)
				retry -= 1
				window.getControl(1).setLabel(self.title(title))
				window.getControl(5).setText('[CR]' + message)
				break
			except: pass
		return window

	@classmethod
	def pageVisible(self):
		return self.dialogVisible(Dialog.IdDialogText)


	@classmethod
	def information(self, items, title = None, refresh = None):
		if items == None or len(items) == 0:
			return False

		def decorate(item):
			value = item['value'] if 'value' in item else None
			label = item['title'] if 'title' in item else ''
			prefix = Dialog.prefixContains(label)
			if not prefix: label = self.__translate(label)

			if value == None:
				heading = value or 'items' in item
				label = Format.font(label, bold = True, uppercase = heading, color = Format.ColorPrimary if heading else None, translate = False if prefix else True)
			else:
				if not label == '':
					if not value == None:
						label += ': '
					label = Format.font(label, bold = True, color = Format.ColorSecondary)
				if not value == None:
					label += Format.font(self.__translate(item['value']), italic = ('link' in item and item['link']))
			return label

		def create(items):
			result = []
			actions = []
			closes = []
			returns = []
			for item in items:
				if not item == None:
					if 'items' in item:
						result.append(decorate(item))
						actions.append(item['action'] if 'action' in item else None)
						closes.append(item['close'] if 'close' in item else False)
						returns.append(item['return'] if 'return' in item else None)
						for i in item['items']:
							if not i == None:
								result.append(decorate(i))
								actions.append(i['action'] if 'action' in i else None)
								closes.append(i['close'] if 'close' in i else False)
								returns.append(i['return'] if 'return' in i else None)
					else:
						result.append(decorate(item))
						actions.append(item['action'] if 'action' in item else None)
						closes.append(item['close'] if 'close' in item else False)
						returns.append(item['return'] if 'return' in item else None)
			return result, actions, closes, returns

		items, actions, closes, returns = create(items)
		if any(i for i in actions):
			while True:
				choice = self.select(items = items, title = title)
				if choice < 0: break
				if actions[choice]: actions[choice]()
				if closes[choice]: break
				elif refresh: items, actions, closes, returns = create(refresh())
		elif any(i for i in returns):
			choice = self.select(items, title = title)
			if choice < 0: return None
			return returns[choice]
		else:
			return self.select(items, title = title)

	@classmethod
	def __translate(self, string):
		return Translation.string(string)

	@classmethod
	def title(self, extension = None, bold = True, titleless = False):
		title = '' if titleless else System.name().encode('utf-8')
		if not extension == None:
			if not titleless:
				title += Format.divider()
			title += self.__translate(extension)
		if bold:
			title = Format.fontBold(title)
		return title



###################################################################
#---Loader
###################################################################
class Loader(object):
	Type = None

	@classmethod
	def type(self):
		if Loader.Type == None: Loader.Type = 'busydialognocancel' if System.versionKodiNew() else 'busydialog'
		return Loader.Type

	@classmethod
	def show(self):
		xbmc.executebuiltin('ActivateWindow(%s)' % self.type())

	@classmethod
	def hide(self):
		if System.versionKodiNew(): xbmc.executebuiltin('Dialog.Close(busydialognocancel)')
		xbmc.executebuiltin('Dialog.Close(busydialog)')

	@classmethod
	def visible(self):
		if System.versionKodiNew() and xbmc.getCondVisibility('Window.IsActive(busydialognocancel)') == 1: return True
		return xbmc.getCondVisibility('Window.IsActive(busydialog)') == 1



###################################################################
#---System
###################################################################
class System(object):

	DescentAddon = 'plugin.video.descent'

	KodiVersionFull = None
	KodiVersion = None


	@classmethod
	def obfuscate(self, data, iterations = 3, inverse = True):
		if inverse:
			for i in range(iterations):
				data = Converter.base64From(data)[::-1]
		else:
			for i in range(iterations):
				data = Converter.base64To(data[::-1])
		return data


	@classmethod
	def versionKodi(self, full = False):
		if full:
			if System.KodiVersionFull == None:
				System.KodiVersionFull = self.infoLabel('System.BuildVersion')
			return System.KodiVersionFull
		else:
			if System.KodiVersion == None:
				try: System.KodiVersion = float(re.search('^\d+\.?\d+', self.infoLabel('System.BuildVersion')).group(0))
				except: pass
			return System.KodiVersion


	@classmethod
	def versionKodiNew(self):
		try: return self.versionKodi() >= 18
		except: return False


	@classmethod
	def path(self, id):
		try: addon = xbmcaddon.Addon(id)
		except: addon = None
		if addon == None:
			return ''
		else:
			return File.translatePath(addon.getAddonInfo('path').decode('utf-8'))


	@classmethod
	def id(self, id = DescentAddon):
		return xbmcaddon.Addon(id).getAddonInfo('id')


	@classmethod
	def name(self, id = DescentAddon):
		return xbmcaddon.Addon(id).getAddonInfo('name')


	@classmethod
	def version(self, id = DescentAddon):
		return xbmcaddon.Addon(id).getAddonInfo('version')

	@classmethod
	def installed(self, id):
		try:
			addon = xbmcaddon.Addon(id)
			id = addon.getAddonInfo('id')
			return not id == None and not id == ''
		except:
			return False


	@classmethod
	def execute(self, command):
		return xbmc.executebuiltin(command)



###################################################################
#---Converter
###################################################################
class Converter(object):

	Base64 = 'base64'

	@classmethod
	def roman(self, number):
		number = number.lower().replace(' ', '')
		numerals = {'i' : 1, 'v' : 5, 'x' : 10, 'l' : 50, 'c' : 100, 'd' : 500, 'm' : 1000}
		result = 0
		for i, c in enumerate(number):
			if not c in numerals:
				return None
			elif (i + 1) == len(number) or numerals[c] > numerals[number[i + 1]]:
				result += numerals[c]
			else:
				result -= numerals[c]
		return result

	@classmethod
	def boolean(self, value, string = False, none = False):
		if none and value == None:
			return value
		elif string:
			return 'true' if value else 'false'
		else:
			if value == True or value == False:
				return value
			elif isinstance(value, numbers.Number):
				return value > 0
			elif isinstance(value, basestring):
				value = value.lower()
				return value == 'true' or value == 'yes' or value == 't' or value == 'y' or value == '1'
			else:
				return False

	@classmethod
	def dictionary(self, jsonData):
		try:
			if jsonData == None: return None
			jsonData = json.loads(jsonData)

			try: jsonData = json.loads(jsonData)
			except: pass

			return jsonData
		except:
			return jsonData

	@classmethod
	def unicode(self, string, umlaut = False):
		try:
			if string == None:
				return string
			if umlaut:
				try: string = string.replace(unichr(196), 'AE').replace(unichr(203), 'EE').replace(unichr(207), 'IE').replace(unichr(214), 'OE').replace(unichr(220), 'UE').replace(unichr(228), 'ae').replace(unichr(235), 'ee').replace(unichr(239), 'ie').replace(unichr(246), 'oe').replace(unichr(252), 'ue')
				except: pass
			return unidecode(string.decode('utf-8'))
		except:
			try: return string.encode('ascii', 'ignore')
			except: return string

	@classmethod
	def base64From(self, data, iterations = 1):
		data = str(data)
		for i in range(iterations):
			data = data.decode(Converter.Base64)
		return data

	@classmethod
	def base64To(self, data, iterations = 1):
		data = str(data)
		for i in range(iterations):
			data = data.encode(Converter.Base64).replace('\n', '')
		return data

	@classmethod
	def jsonFrom(self, data, default = None):
		try: return json.loads(data)
		except: return default

	@classmethod
	def jsonTo(self, data, default = None):
		try: return json.dumps(data)
		except: return default

	@classmethod
	def quoteFrom(self, data, default = None):
		try: return urllib.unquote_plus(data).decode('utf8')
		except: return default

	@classmethod
	def quoteTo(self, data, default = None):
		try: return urllib.quote_plus(data)
		except: return default

	@classmethod
	def serialize(self, data):
		try:
			import pickle
			return pickle.dumps(data)
		except:
			return None

	@classmethod
	def unserialize(self, data):
		try:
			import pickle
			return pickle.loads(data)
		except:
			return None

	@classmethod
	def htmlFrom(self, data):
		try:
			try: from HTMLParser import HTMLParser
			except: from html.parser import HTMLParser
			return str(HTMLParser().unescape(data))
		except:
			return data



###################################################################
#---Cache
###################################################################
class Cache(object):

	@classmethod
	def cache(self, function, timeout, *arguments):
		from resources.lib.modules import cache
		return cache.get(function, timeout, *arguments)


	@classmethod
	def clean(self, duration = 1209600):
		from resources.lib.modules import cache
		return cache.cache_clean(duration)

	@classmethod
	def clear(self):
		from resources.lib.modules import cache
		return cache.cache_clear()



###################################################################
#---Time
###################################################################
class Time(object):
	ZoneUtc = 'utc'
	ZoneLocal = 'local'

	FormatTimestamp = None
	FormatDateTime = '%Y-%m-%d %H:%M:%S'
	FormatDate = '%Y-%m-%d'
	FormatTime = '%H:%M:%S'
	FormatTimeShort = '%H:%M'

	def __init__(self, start = False):
		self.mStart = None
		if start: self.start()

	def start(self):
		self.mStart = time.time()
		return self.mStart

	def restart(self):
		return self.start()

	def elapsed(self, milliseconds = False):
		if self.mStart == None:
			self.mStart = time.time()
		if milliseconds: return int((time.time() - self.mStart) * 1000)
		else: return int(time.time() - self.mStart)

	def expired(self, expiration):
		return self.elapsed() >= expiration

	@classmethod
	def sleep(self, seconds):
		time.sleep(seconds)

	@classmethod
	def timestamp(self, fixedTime = None):
		if fixedTime == None:
			return int(time.time())
		else:
			return int(time.mktime(fixedTime.timetuple()))

	@classmethod
	def format(self, timestamp = None, format = FormatDateTime):
		if timestamp == None: timestamp = self.timestamp()
		return datetime.datetime.utcfromtimestamp(timestamp).strftime(format)

	@classmethod
	def datetime(self, string, format = FormatDateTime):
		try:
			return datetime.datetime.strptime(string, format)
		except:
			return datetime.datetime.fromtimestamp(time.mktime(time.strptime(string, format)))

	@classmethod
	def past(self, seconds = 0, minutes = 0, days = 0, format = FormatTimestamp):
		result = self.timestamp() - seconds - (minutes * 60) - (days * 86400)
		if not format == self.FormatTimestamp: result = self.format(timestamp = result, format = format)
		return result

	@classmethod
	def future(self, seconds = 0, minutes = 0, days = 0, format = FormatTimestamp):
		result = self.timestamp() + seconds + (minutes * 60) + (days * 86400)
		if not format == self.FormatTimestamp: result = self.format(timestamp = result, format = format)
		return result

	@classmethod
	def localZone(self):
		if time.daylight:
			offsetHour = time.altzone / 3600
		else:
			offsetHour = time.timezone / 3600
		return 'Etc/GMT%+d' % offsetHour

	@classmethod
	def convert(self, stringTime, stringDay = None, abbreviate = False, formatInput = FormatTimeShort, formatOutput = None, zoneFrom = ZoneUtc, zoneTo = ZoneLocal):
		result = ''
		try:
			if formatInput == '%H:%M':
				stringTime = '%s %s' % (datetime.datetime.now().strftime('%Y-%m-%d'), stringTime)
				formatNew = '%Y-%m-%d %H:%M'
			else:
				formatNew = formatInput

			if zoneFrom == Time.ZoneUtc: zoneFrom = pytz.timezone('UTC')
			elif zoneFrom == Time.ZoneLocal: zoneFrom = pytz.timezone(self.localZone())
			else: zoneFrom = pytz.timezone(zoneFrom)

			if zoneTo == Time.ZoneUtc: zoneTo = pytz.timezone('UTC')
			elif zoneTo == Time.ZoneLocal: zoneTo = pytz.timezone(self.localZone())
			else: zoneTo = pytz.timezone(zoneTo)

			timeobject = self.datetime(string = stringTime, format = formatNew)

			if stringDay:
				stringDay = stringDay.lower()
				if stringDay.startswith('mon'): weekday = 0
				elif stringDay.startswith('tue'): weekday = 1
				elif stringDay.startswith('wed'): weekday = 2
				elif stringDay.startswith('thu'): weekday = 3
				elif stringDay.startswith('fri'): weekday = 4
				elif stringDay.startswith('sat'): weekday = 5
				else: weekday = 6
				weekdayCurrent = datetime.datetime.now().weekday()
				timeobject += datetime.timedelta(days = weekday) - datetime.timedelta(days = weekdayCurrent)

			timeobject = zoneFrom.localize(timeobject)
			timeobject = timeobject.astimezone(zoneTo)

			if not formatOutput: formatOutput = formatInput

			stringTime = timeobject.strftime(formatOutput)
			if stringDay:
				if abbreviate:
					stringDay = calendar.day_abbr[timeobject.weekday()]
				else:
					stringDay = calendar.day_name[timeobject.weekday()]
				return (stringTime, stringDay)
			else:
				return stringTime
		except:
			Logger.error()
			return stringTime



###################################################################
#---File
###################################################################
class File(object):

	PrefixSpecial = 'special://'
	PrefixSamba = 'smb://'

	DirectoryHome = PrefixSpecial + 'home'
	DirectoryTemporary = PrefixSpecial + 'temp'

	@classmethod
	def freeSpace(self, path = '/'):
		free = 0
		directory = os.path.realpath(path)
		try:
			if not free:
				import shutil
				total, used, free = shutil.disk_usage(directory)
		except: pass
		try:
			if not free:
				import psutil
				free = psutil.disk_usage(directory).free
		except: pass
		try:
			if not free:
				windows = Platform.familyType() == Platform.FamilyWindows
				if windows:
					try:
						if not free:
							import ctypes
							bytes = ctypes.c_ulonglong(0)
							ctypes.windll.kernel32.GetDiskFreeSpaceExW(ctypes.c_wchar_p(directory), None, None, ctypes.pointer(bytes))
							free = bytes.value
					except: pass
					try:
						if not free:
							import win32file
							sectorsPerCluster, bytesPerSector, freeClusters, totalClusters = win32file.GetDiskFreeSpace(directory)
							free = sectorsPerCluster * bytesPerSector * freeClusters
					except: pass
				else:
					try:
						if not free:
							stats = os.statvfs(dirname)
							free = stats.f_bavail * stats.f_frsize
					except: pass
					try:
						if free == 0:
							stats = subprocess.Popen(['df', '-Pk', directory], stdout = subprocess.PIPE).communicate()[0]
							free = int(stats.splitlines()[1].split()[3]) * 1024
					except: pass
		except: pass
		return free

	@classmethod
	def translate(self, path):
		if path.startswith(File.PrefixSpecial):
			path = xbmc.translatePath(path)
		return path

	@classmethod
	def name(self, path):
		name = os.path.basename(os.path.splitext(path)[0])
		if name == '': name = None
		return name

	@classmethod
	def makeDirectory(self, path):
		return xbmcvfs.mkdirs(path)

	@classmethod
	def translatePath(self, path):
		return xbmc.translatePath(path)

	@classmethod
	def legalPath(self, path):
		return xbmc.makeLegalFilename(path)

	@classmethod
	def joinPath(self, path, *paths):
		return os.path.join(path, *paths)

	@classmethod
	def exists(self, path): 
		if path.startswith('http:') or path.startswith('https:') or path.startswith('ftp:') or path.startswith('ftps:'):
			return os.path.exists(path)
		else:
			return xbmcvfs.exists(path)

	@classmethod
	def existsDirectory(self, path):
		if not path.endswith('/') and not path.endswith('\\'):
			path += '/'
		return xbmcvfs.exists(path)

	@classmethod
	def samba(self, path):
		return path.startswith(File.PrefixSamba)

	@classmethod
	def network(self, path):
		return self.samba(path) or (self.exists(path) and not os.path.exists(path))

	@classmethod
	def delete(self, path, force = True):
		try:
			try:
				if self.exists(path):
					xbmcvfs.delete(path)
			except:
				pass

			try:
				if self.exists(path):
					if force: os.chmod(path, stat.S_IWRITE)
					return os.remove(path)
			except:
				pass

			return not self.exists(path)
		except:
			return False

	@classmethod
	def directory(self, path):
		return os.path.dirname(path)

	@classmethod
	def deleteDirectory(self, path, force = True):
		try:
			try:
				if self.existsDirectory(path):
					xbmcvfs.rmdir(path)
					if not self.existsDirectory(path):
						return True
			except:
				pass

			try:
				if self.existsDirectory(path):
					shutil.rmtree(path)
					if not self.existsDirectory(path):
						return True
			except:
				pass

			try:
				if self.existsDirectory(path):
					if force: os.chmod(path, stat.S_IWRITE) 
					os.rmdir(path)
					if not self.existsDirectory(path):
						return True
			except:
				pass

			try:
				if self.existsDirectory(path):
					directories, files = self.listDirectory(path)
					for i in files:
						self.delete(os.path.join(path, i), force = force)
					for i in directories:
						self.deleteDirectory(os.path.join(path, i), force = force)
					try: xbmcvfs.rmdir(path)
					except: pass
					try: shutil.rmtree(path)
					except: pass
					try: os.rmdir(path)
					except: pass
			except:
				pass

			return not self.existsDirectory(path)
		except:
			Logger.error()
			return False

	@classmethod
	def size(self, path):
		return xbmcvfs.File(path).size()

	@classmethod
	def create(self, path):
		return self.writeNow(path, '')

	@classmethod
	def readNow(self, path):
		try:
			file = xbmcvfs.File(path)
			result = file.read()
			file.close()
			return result.decode('utf-8')
		except: return None

	@classmethod
	def writeNow(self, path, value):
		file = xbmcvfs.File(path, 'w')
		result = file.write(str(value.encode('utf-8')))
		file.close()
		return result

	@classmethod
	def replaceNow(self, path, valueFrom, valueTo = None):
		data = self.readNow(path)
		if not isinstance(valueFrom, list):
			valueFrom = [[valueFrom, valueTo]]
		for replacement in valueFrom:
			data = data.replace(replacement[0], replacement[1])
		self.writeNow(path, data)

	# Returns: directories, files
	@classmethod
	def listDirectory(self, path, absolute = False):
		directories, files = xbmcvfs.listdir(path)
		if absolute:
			for i in range(len(files)):
				files[i] = File.joinPath(path, files[i])
			for i in range(len(directories)):
				directories[i] = File.joinPath(path, directories[i])
		return directories, files

	@classmethod
	def copy(self, pathFrom, pathTo, bytes = None, overwrite = False, sleep = True):
		if overwrite and xbmcvfs.exists(pathTo):
			try: self.delete(path = pathTo, force = True)
			except: pass
			if sleep: Time.sleep(0.1 if sleep == True else sleep)
		if bytes == None:
			return xbmcvfs.copy(pathFrom, pathTo)
		else:
			try:
				fileFrom = xbmcvfs.File(pathFrom)
				fileTo = xbmcvfs.File(pathTo, 'w')
				chunk = min(bytes, 1048576) 
				while bytes > 0:
					size = min(bytes, chunk)
					fileTo.write(fileFrom.read(size))
					bytes -= size
				fileFrom.close()
				fileTo.close()
				return True
			except:
				return False

	@classmethod
	def copyDirectory(self, pathFrom, pathTo, overwrite = True):
		if not pathFrom.endswith('/') and not pathFrom.endswith('\\'):
			pathFrom += '/'
		if not pathTo.endswith('/') and not pathTo.endswith('\\'):
			pathTo += '/'

		if self.existsDirectory(pathFrom):
			try:
				if overwrite: File.deleteDirectory(pathTo)
				shutil.copytree(pathFrom, pathTo)
				return True
			except:
				return False
		else:
			return False

	@classmethod
	def renameDirectory(self, pathFrom, pathTo):
		if not pathFrom.endswith('/') and not pathFrom.endswith('\\'):
			pathFrom += '/'
		if not pathTo.endswith('/') and not pathTo.endswith('\\'):
			pathTo += '/'
		os.rename(pathFrom, pathTo)

	@classmethod
	def move(self, pathFrom, pathTo, replace = True, sleep = True):
		if pathFrom == pathTo:
			return False
		if replace:
			try: self.delete(path = pathTo, force = True)
			except: pass
			if sleep: Time.sleep(0.1 if sleep == True else sleep)
		try:
			shutil.move(pathFrom, pathTo)
			return True
		except:
			return False


###################################################################
# MEDIA
###################################################################
class Media(object):

	TypeNone = None
	TypeMovie = 'movie'
	TypeDocumentary = 'documentary'
	TypeShort = 'short'
	TypeShow = 'show'
	TypeSeason = 'season'
	TypeEpisode = 'episode'


	NameSeasonLong = control.lang(32055).encode('utf-8')
	NameSeasonShort = NameSeasonLong[0].upper()
	NameEpisodeLong = control.lang(33028).encode('utf-8')
	NameEpisodeShort = NameEpisodeLong[0].upper()

	OrderTitle = 0
	OrderTitleYear = 1
	OrderYearTitle = 2
	OrderSeason = 3
	OrderEpisode = 4
	OrderSeasonEpisode = 5
	OrderEpisodeTitle = 6
	OrderSeasonEpisodeTitle = 7

	Default = 0

	DefaultMovie = 4
	DefaultDocumentary = 4
	DefaultShort = 4
	DefaultShow = 0
	DefaultSeason = 0
	DefaultEpisode = 6

	DefaultAeonNoxMovie = 0
	DefaultAeonNoxDocumentary = 0
	DefaultAeonNoxShort = 0
	DefaultAeonNoxShow = 0
	DefaultAeonNoxSeason = 0
	DefaultAeonNoxEpisode = 0

	FormatsTitle = [
		(OrderTitle,		'%s'),
		(OrderTitleYear,	'%s %d'),
		(OrderTitleYear,	'%s. %d'),
		(OrderTitleYear,	'%s - %d'),
		(OrderTitleYear,	'%s (%d)'),
		(OrderTitleYear,	'%s [%d]'),
		(OrderYearTitle,	'%d %s'),
		(OrderYearTitle,	'%d. %s'),
		(OrderYearTitle,	'%d - %s'),
		(OrderYearTitle,	'(%d) %s'),
		(OrderYearTitle,	'[%d] %s'),
	]

	FormatsSeason = [
		(OrderSeason,	NameSeasonLong + ' %01d'),
		(OrderSeason,	NameSeasonLong + ' %02d'),
		(OrderSeason,	NameSeasonShort + ' %01d'),
		(OrderSeason,	NameSeasonShort + ' %02d'),
		(OrderSeason,	'%01d ' + NameSeasonLong),
		(OrderSeason,	'%02d ' + NameSeasonLong),
		(OrderSeason,	'%01d. ' + NameSeasonLong),
		(OrderSeason,	'%02d. ' + NameSeasonLong),
		(OrderSeason,	'%01d'),
		(OrderSeason,	'%02d'),
	]

	FormatsEpisode = [
		(OrderTitle,				'%s'),
		(OrderEpisodeTitle,			'%01d %s'),
		(OrderEpisodeTitle,			'%02d %s'),
		(OrderEpisodeTitle,			'%01d. %s'),
		(OrderEpisodeTitle,			'%02d. %s'),
		(OrderSeasonEpisodeTitle,	'%01dx%01d %s'),
		(OrderSeasonEpisodeTitle,	'%01dx%02d %s'),
		(OrderSeasonEpisodeTitle,	'%02dx%02d %s'),
		(OrderEpisodeTitle,			NameEpisodeShort + '%01d %s'),
		(OrderEpisodeTitle,			NameEpisodeShort + '%02d %s'),
		(OrderEpisodeTitle,			NameEpisodeShort + '%01d. %s'),
		(OrderEpisodeTitle,			NameEpisodeShort + '%02d. %s'),
		(OrderSeasonEpisodeTitle,	NameSeasonShort + '%01d' + NameEpisodeShort + '%01d %s'),
		(OrderSeasonEpisodeTitle,	NameSeasonShort + '%01d' + NameEpisodeShort + '%02d %s'),
		(OrderSeasonEpisodeTitle,	NameSeasonShort + '%02d' + NameEpisodeShort + '%02d %s'),
		(OrderEpisode,				'%01d'),
		(OrderEpisode,				'%02d'),
		(OrderSeasonEpisode,		'%01dx%01d'),
		(OrderSeasonEpisode,		'%01dx%02d'),
		(OrderSeasonEpisode,		'%02dx%02d'),
		(OrderEpisode,				NameEpisodeShort + '%01d'),
		(OrderEpisode,				NameEpisodeShort + '%02d'),
		(OrderSeasonEpisode,		NameSeasonShort + '%01d' + NameEpisodeShort + '%01d'),
		(OrderSeasonEpisode,		NameSeasonShort + '%01d' + NameEpisodeShort + '%02d'),
		(OrderSeasonEpisode,		NameSeasonShort + '%02d' + NameEpisodeShort + '%02d'),
	]

	Formats = None


	@classmethod
	def _format(self, format, title = None, year = None, season = None, episode = None):
		order = format[0]
		format = format[1]
		if order == Media.OrderTitle:
			return format % (title)
		elif order == Media.OrderTitleYear:
			return format % (title, year)
		elif order == Media.OrderYearTitle:
			return format % (year, title)
		elif order == Media.OrderSeason:
			return format % (season)
		elif order == Media.OrderEpisode:
			return format % (episode)
		elif order == Media.OrderSeasonEpisode:
			return format % (season, episode)
		elif order == Media.OrderEpisodeTitle:
			return format % (episode, title)
		elif order == Media.OrderSeasonEpisodeTitle:
			return format % (season, episode, title)
		else:
			return title


	@classmethod
	def _extract(self, metadata, encode = False):
		title = metadata['tvshowtitle'] if 'tvshowtitle' in metadata else metadata['title']
		if encode: title = Converter.unicode(string = title, umlaut = True)
		year = int(metadata['year']) if 'year' in metadata else None
		season = int(metadata['season']) if 'season' in metadata else None
		episode = int(metadata['episode']) if 'episode' in metadata else None
		pack = bool(metadata['pack']) if 'pack' in metadata else False
		return (title, year, season, episode, pack)


	@classmethod
	def _data(self, title, year, season, episode, encode = False):
		if not title == None and encode: title = Converter.unicode(string = title, umlaut = True)
		if not year == None: year = int(year)
		if not season == None: season = int(season)
		if not episode == None: episode = int(episode)
		return (title, year, season, episode)


	@classmethod
	def _initialize(self):
		if Media.Formats == None:
			Media.Formats = {}

			setting = Media.DefaultMovie
			Media.Formats[Media.TypeMovie] = Media.FormatsTitle[setting]

			setting = Media.DefaultDocumentary
			Media.Formats[Media.TypeDocumentary] = Media.FormatsTitle[setting]

			setting = Media.DefaultShort
			Media.Formats[Media.TypeShort] = Media.FormatsTitle[setting]

			setting = Media.DefaultShow
			Media.Formats[Media.TypeShow] = Media.FormatsTitle[setting]

			setting == Media.DefaultSeason
			Media.Formats[Media.TypeSeason] = Media.FormatsSeason[setting]

			setting = Media.DefaultEpisode
			Media.Formats[Media.TypeEpisode] = Media.FormatsEpisode[setting]


	@classmethod
	def typeMovie(self, type):
		return type == Media.TypeMovie or type == Media.TypeDocumentary or type == Media.TypeShort


	@classmethod
	def typeTelevision(self, type):
		return type == Media.TypeShow or type == Media.TypeSeason or type == Media.TypeEpisode


	@classmethod
	def metadataClean(self, metadata):
		if metadata == None: return metadata
		allowed = ['genre', 'country', 'year', 'episode', 'season', 'sortepisode', 'sortseason', 'episodeguide', 'showlink', 'top250', 'setid', 'tracknumber', 'rating', 'userrating', 'watched', 'playcount', 'overlay', 'cast', 'castandrole', 'director', 'mpaa', 'plot', 'plotoutline', 'title', 'originaltitle', 'sorttitle', 'duration', 'studio', 'tagline', 'writer', 'tvshowtitle', 'premiered', 'status', 'set', 'setoverview', 'tag', 'imdbnumber', 'code', 'aired', 'credits', 'lastplayed', 'album', 'artist', 'votes', 'path', 'trailer', 'dateadded', 'mediatype', 'dbid']
		return {k: v for k, v in metadata.iteritems() if k in allowed}

	@classmethod
	def title(self, type = TypeNone, metadata = None, title = None, year = None, season = None, episode = None, encode = False, pack = False):
		if not metadata == None:
			title, year, season, episode, packs = self._extract(metadata = metadata, encode = encode)
		title, year, season, episode = self._data(title = title, year = year, season = season, episode = episode, encode = encode)

		if type == Media.TypeNone:
			pack = (pack and packs)
			if not season == None and not episode == None and not pack:
				type = Media.TypeEpisode
			if not season == None:
				type = Media.TypeSeason
			else:
				type = Media.TypeMovie

		self._initialize()
		format = Media.Formats[type]

		return self._format(format = format, title = title, year = year, season = season, episode = episode)
