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: str | unicode
        :type target_ip: str | unicode
        :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: str | unicode
        :rtype: str | unicode | 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[str | unicode]
        """
        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
