import socket
import re
import struct
from parallels.core.utils.common import unique_list
from parallels.core import messages


def is_ip(ip_address):
    """Check if specified string is an IPv4 or IPv6 address

    :type ip_address: str | unicode
    :rtype: bool
    """
    return is_ipv4(ip_address) or is_ipv6(ip_address)


def is_ipv4(ip_address):
    """Check if specified string is an IPv4 address

    :type ip_address: str | unicode
    :rtype: bool
    """

    # We cannot use socket.inet_pton because it is not available on Windows
    # and may be some Unix OSes
    # https://docs.python.org/2/library/socket.html#socket.inet_pton
    # Availability: Unix (maybe not all platforms).

    # Simple solution taken from:
    # http://stackoverflow.com/questions/319279/how-to-validate-ip-address-in-python
    # This solution is:
    # - quite simple and
    # - does not require any external libraries
    # - works on any platform

    pattern = re.compile(r"""
        ^
        (?:
            # Dotted variants:
            (?:
                # Decimal 1-255 (no leading 0's)
                [3-9]\d?|2(?:5[0-5]|[0-4]?\d)?|1\d{0,2}
            |
                0x0*[0-9a-f]{1,2}  # Hexadecimal 0x0 - 0xFF (possible leading 0's)
            |
                0+[1-3]?[0-7]{0,2} # Octal 0 - 0377 (possible leading 0's)
            )
            (?:                  # Repeat 0-3 times, separated by a dot
                \.
                (?:
                    [3-9]\d?|2(?:5[0-5]|[0-4]?\d)?|1\d{0,2}
                |
                    0x0*[0-9a-f]{1,2}
                |
                    0+[1-3]?[0-7]{0,2}
                )
            ){0,3}
        |
            0x0*[0-9a-f]{1,8}    # Hexadecimal notation, 0x0 - 0xffffffff
        |
            0+[0-3]?[0-7]{0,10}  # Octal notation, 0 - 037777777777
        |
            # Decimal notation, 1-4294967295:
            429496729[0-5]|42949672[0-8]\d|4294967[01]\d\d|429496[0-6]\d{3}|
            42949[0-5]\d{4}|4294[0-8]\d{5}|429[0-3]\d{6}|42[0-8]\d{7}|
            4[01]\d{8}|[1-3]\d{0,9}|[4-9]\d{0,8}
        )
        $
    """, re.VERBOSE | re.IGNORECASE)

    return pattern.match(ip_address) is not None


def is_ipv6(ip_address):
    """Check if specified string is an IPv6 address

    :type ip_address: str | unicode
    :rtype: bool
    """

    # See comment inside is_ipv4 function - the same is applicable here.

    pattern = re.compile(r"""
        ^
        \s*                      # Leading whitespace
        (?!.*::.*::)             # Only a single whildcard allowed
        (?:(?!:)|:(?=:))         # Colon iff it would be part of a wildcard
        (?:                      # Repeat 6 times:
            [0-9a-f]{0,4}        #   A group of at most four hexadecimal digits
            (?:(?<=::)|(?<!::):) #   Colon unless preceded by wildcard
        ){6}                     #
        (?:                      # Either
            [0-9a-f]{0,4}        #   Another group
            (?:(?<=::)|(?<!::):) #   Colon unless preceded by wildcard
            [0-9a-f]{0,4}        #   Last group
            (?: (?<=::)          #   Colon iff preceded by exacly one colon
                |  (?<!:)           #
                |  (?<=:) (?<!::) : #
            )                   # OR
            |                       #   A v4 address with NO leading zeros
            (?:25[0-4]|2[0-4]\d|1\d\d|[1-9]?\d)
            (?: \.
                (?:25[0-4]|2[0-4]\d|1\d\d|[1-9]?\d)
            ){3}
        )
        \s*                         # Trailing whitespace
        $
    """, re.VERBOSE | re.IGNORECASE | re.DOTALL)

    return pattern.match(ip_address) is not None


def resolve(address):
    """Resolve hostname into IP address.
    
    Return the first IP address, or None if we were not able to resolve.
    If address is already a valid IP address - return it as is.

    :type address: str | unicode
    :rtype: None | str | unicode
    """
    addresses = resolve_all(address)
    if len(addresses) == 0:
        return None
    else:
        return addresses[0]


def resolve_all(address):
    """Resolve hostname into list of IP addresses.
    
    If address is already a valid IP address - return
    list that contains only that address.
    Otherwise resolve and return list of IP addresses 
    (IPv6 and IPv4 all in one list). 
    If we were not able to resolve, empty list is returned.

    :type address: str | unicode
    :rtype: list[str | unicode]
    """
    if is_ipv4(address) or is_ipv6(address):
        return [address]
    else:
        try:
            ip_addresses = unique_list([
                ainfo[4][0] for ainfo in socket.getaddrinfo(address, None)]
            )
            return ip_addresses
        except socket.gaierror:
            return []


def resolve_all_safe(address):
    """Resolve hostname into list of IP addresses. Return empty list if any error occurred.

    This function works in the same way as resolve_all, but for example if specified hostname is invalid,
    this function will simply return empty list. Use it when you don't know and even not interested if
    specified address is valid or not.

    :type address: str | unicode
    :rtype: list[str | unicode]
    """
    if is_ipv4(address) or is_ipv6(address):
        return [address]
    else:
        try:
            ip_addresses = unique_list([
                ainfo[4][0] for ainfo in socket.getaddrinfo(address, None)]
            )
            return ip_addresses
        except:
            # All possible exception are just ignored
            return []


def collapse_ipv4_mask(mask):
    """Convert string network mask to integer.

    :type mask: str | unicode
    :rtype: int
    :raises: ValueError
    """

    buf = socket.inet_aton(mask)
    n = struct.unpack('!I', buf)[0]
    all_bits = 0xffffffff
    for i in xrange(33):
        if ((all_bits << (32-i)) & all_bits) == n:
            return i
    raise ValueError(messages.INVALID_IPV4_MASK % mask)


def ip_distance(ip1, ip2):
    """Calculate distance between 2 IP addresses

    By distance we mean how these IP addresses are similar.
    For example, IP addresses '192.168.1.15' and '192.168.2.17' have distance of [0, 0, 1, 2],
    and IPs '10.58.1.1' and '192.168.2.17' have distance of [180, 110, 1, 16]. As [180, 110, 1, 16] is
    greater than [0, 0, 1, 2], then '192.168.1.15' is closer to '192.168.2.17'.

    :type ip1: str | unicode
    :type ip2: str | unicode
    :rtype: list[int]
    """
    def to_int(ip):
        return [int(s) for s in ip.split('.')]

    distance = []
    for i1, i2 in zip(to_int(ip1), to_int(ip2)):
        distance.append(abs(i1 - i2))

    return distance
