from parallels.core import messages
from parallels.core import MigrationError
from parallels.core.utils.common import format_multiline_list
from parallels.core.utils.common.ip import is_ipv4, is_ipv6


class IPMapping(object):
	"""Class representing IP mapping"""

	def __init__(self):
		self._ip_mapping = {}

	def set(self, source_ip, target_ip):
		"""Set mapping of source IP address to target IP address

		:type source_ip: basestring
		:type target_ip: basestring
		:rtype: None
		"""
		self._ip_mapping[source_ip] = target_ip

	def get(self, source_ip):
		"""Get target IP address for specified source IP address.
		If there is no such source IP in the mapping - return None.

		:type source_ip: basestring
		:rtype: basestring | None
		"""
		return self._ip_mapping.get(source_ip)


class IPMappingReader(object):
	"""Read IP mapping from a file"""
	def __init__(self, target_ips):
		"""
		:type target_ips: set[basestring]
		"""
		self._target_ips = target_ips

	def read(self, file_obj):
		"""Read IP mapping from a file
		:param file_obj: file-like object
		:rtype: parallels.core.ip_mapping.IPMapping
		"""
		errors = []
		mapping = IPMapping()

		for line_number, line in enumerate(file_obj, start=1):
			line = line.strip()

			if line == '':
				# skip empty lines
				continue

			line_parts = line.split()
			if len(line_parts) != 2:
				errors.append(
					messages.IP_MAPPING_INVALID_ARGUMENTS_COUNT % line_number
				)
				continue

			source_ip, target_ip = line_parts
			source_ip = source_ip.strip()
			target_ip = target_ip.strip()

			if not is_ipv4(source_ip) and not is_ipv6(source_ip):
				errors.append(
					messages.LINE_S_INVALID_SOURCE_IP_ADDRESS % (
						line_number, source_ip
					)
				)
				continue

			if not is_ipv4(target_ip) and not is_ipv6(target_ip):
				errors.append(
					messages.LINE_S_INVALID_TARGET_IP_ADDRESS % (
						line_number, target_ip
					)
				)
				continue

			if is_ipv4(source_ip) and is_ipv6(target_ip) or is_ipv6(source_ip) and is_ipv4(target_ip):
				errors.append(
					messages.LINE_S_IP_ADDRESSES_DIFFERENT_TYPES % (
						line_number
					)
				)
				continue

			if mapping.get(source_ip) is not None:
				errors.append(
					messages.LINE_S_SOURCE_IP_ADDRESS_S % (
						line_number, source_ip
					)
				)
				continue

			if target_ip not in self._target_ips:
				errors.append(
					messages.LINE_S_TARGET_IP_ADDRESS_S % (
						line_number, target_ip
					)
				)
				continue

			mapping.set(source_ip, target_ip)

		if len(errors) > 0:
			raise MigrationError(messages.FAILED_TO_READ_IP_MAPPING % format_multiline_list(errors))

		return mapping
