from collections import namedtuple
from ConfigParser import NoOptionError
import urlparse
from parallels.core import MigrationError
from parallels.core.registry import Registry
from parallels.core.utils.common import format_list
from parallels.core.utils.entity import Entity
from parallels.core import messages

Auth = namedtuple('Auth', ('username', 'password'))


class HttpConnectionSettings(Entity):
	def __init__(self, url, auth):
		self._url = url
		self._auth = auth

	@property
	def url(self):
		return self._url

	@property
	def auth(self):
		return self._auth

	@property
	def port(self):
		return urlparse.urlparse(self.url).port


class ConfigSection(object):
	"""Dictionary-like interface to configuration section.

	Also allows to refer part of keys starting with given prefix.
	"""
	def __init__(self, config, section_name, prefix=''):
		self.config = config
		self.section_name = section_name
		self.prefix = prefix

	def __getitem__(self, name):
		return self.config.get(self.section_name, self.prefix+name)

	def get(self, name, default=None):
		return self.get_or_exec(name, lambda: default)

	def getboolean(self, name, default=None):
		return self.getboolean_or_exec(name, lambda: default)

	def get_or_exec(self, name, get_default):
		try:
			return self.config.get(self.section_name, self.prefix+str(name))
		except NoOptionError:
			return get_default()

	def getboolean_or_exec(self, name, get_default):
		try:
			return self.config.getboolean(self.section_name, self.prefix+str(name))
		except NoOptionError:
			return get_default()

	def keys(self):
		return [key for key in self.config.options(self.section_name) if key.startswith(self.prefix)]

	def items(self):
		return [(k, v) for (k, v) in self.config.items(self.section_name) if k.startswith(self.prefix)]

	def __contains__(self, name):
		return self.config.has_option(self.section_name, self.prefix+name)

	def prefixed(self, prefix):
		return ConfigSection(self.config, self.section_name, self.prefix+prefix)

	def with_defaults(self, defaults):
		return ConfigSectionWithDefaults(self, defaults)


class ConfigSectionWithDefaults(object):
	"""Configuration section with default values.
	"""
	def __init__(self, section, defaults):
		self.section = section
		self.defaults = defaults

	def __getitem__(self, name):
		return self.section.get_or_exec(name, lambda: self.defaults[name])

	def get(self, name, default=None):
		return self.get_or_exec(name, lambda: default)

	def get_or_exec(self, name, get_default):
		try:
			return self.section.get_or_exec(name, lambda: self.defaults[name])
		except ValueError:
			return get_default()

	def keys(self):
		return set(self.section.keys()) | set(self.defaults.keys())

	def __contains__(self, name):
		return name in self.section or name in self.defaults

	def with_defaults(self, defaults):
		return ConfigSectionWithDefaults(self, defaults)


def config_get(config, section, option, default=None):
	if option in config.options(section):
		return config.get(section, option)
	else:
		return default


def get_option(section, option, default):
	context = Registry.get_instance().get_context()
	if context is not None and context.config is not None and option in context.config.options(section):
		return context.config.get(section, option)
	else:
		return default


def read_auth(section):
	return Auth(section.get('username'), section.get('password'))


def read_url(section):
	if 'url' in section:
		url = section['url']
		
	else:
		host = section['host']
		protocol = section['protocol']
		port = int(section['port'])
		path = section['path']
		
		url = urlparse.urlunparse((protocol, '%s:%d' % (host, port), path, '', '', ''))
		
	return url


def read_http_connection_settings(section):
	return HttpConnectionSettings(url=read_url(section), auth=read_auth(section))


def get_sections_list(config, section, option):
	"""Parse list of sections in specified option of configuration file and check that each section actually exists

	Consider we have list of sections in option
	=============================================
	[GLOBAL]
	source-servers: source1, source2, source3
	=============================================

	If you call this function (for with arguments (config, 'GLOBAL', 'source-servers')), it will:
	1) Return list of sections: ['source1', 'source2', 'source3']
	2) Check that each of these sections exists in configuration file.
	If section does not exist - exception will be raised. For example, if there is no section like:
	=============================================
	[source3]
	options
	=============================================
	exception will be raised.

	:rtype: list[str]
	"""
	if option in config.options(section):
		list_str = config.get(section, option)
		list_str = list_str.strip()
		if list_str == '':
			return []
		sections = [s.strip() for s in list_str.split(',')]

		for section in sections:
			if not config.has_section(section):
				raise MigrationError(
					messages.SECTION_DOES_NOT_EXIST.format(section=section, option=option)
				)
		return sections
	else:
		return []


class ConfigSelectionOption(object):
	"""Description of configuration file option that may have one value from allowed set"""

	@property
	def name(self):
		"""Return name of the option

		:rtype: str
		"""
		raise NotImplementedError()

	@property
	def allowed_values(self):
		"""Return list of allowed values for this option

		:rtype: list[str]
		"""
		raise NotImplementedError()

	@property
	def case_insensitive(self):
		"""Return true if option is case insensitive, False otherwise

		:rtype: bool
		"""
		return True

	@property
	def default(self):
		"""Return default value for this option, if it was not specified in configuration file

		:rtype: str
		"""
		raise NotImplementedError()


def read_selection_option(section, selection_option):
	"""Read selection option (option that may have one value from allowed set) from configuration file

	:type section: parallels.core.utils.config_utils.ConfigSection
	:type selection_option: parallels.core.utils.config_utils.ConfigSelectionOption
	:rtype: str
	"""
	def lowercase_if_case_insensitive(v):
		if selection_option.case_insensitive:
			return v.lower()
		else:
			return v

	value = lowercase_if_case_insensitive(section.get(selection_option.name, selection_option.default))
	allowed_values = [
		lowercase_if_case_insensitive(allowed_value) for allowed_value in selection_option.allowed_values
	]
	if value not in allowed_values:
		raise MigrationError(
			messages.INVALID_OPTION_VALUE.format(
				option_name=selection_option.name,
				specified_value=section.get(selection_option.name),
				allowed_values=format_list(allowed_values)
			)
		)

	return value


def global_section(config):
	return ConfigSection(config, 'GLOBAL')