# Written by John Hoffman
# see LICENSE.txt for license information

from bisect import bisect, insort

try:
    True
except:
    True = 1
    False = 0
    bool = lambda x: not not x


def to_long_ipv4(ip):
    ip = ip.split('.')
    if len(ip) != 4:
        raise ValueError, "bad address"
    b = 0L
    for n in ip:
        b *= 256
        b += int(n)
    return b


def to_long_ipv6(ip):
    if ip == '':
        raise ValueError, "bad address"
    if ip == '::':      # boundary handling
        ip = ''
    elif ip[:2] == '::':
        ip = ip[1:]
    elif ip[0] == ':':
        raise ValueError, "bad address"
    elif ip[-2:] == '::':
        ip = ip[:-1]
    elif ip[-1] == ':':
        raise ValueError, "bad address"

    b = []
    doublecolon = False
    for n in ip.split(':'):
        if n == '':     # double-colon
            if doublecolon:
                raise ValueError, "bad address"
            doublecolon = True
            b.append(None)
            continue
        if n.find('.') >= 0: # IPv4
            n = n.split('.')
            if len(n) != 4:
                raise ValueError, "bad address"
            for i in n:
                b.append(int(i))
            continue
        n = ('0'*(4-len(n))) + n
        b.append(int(n[:2],16))
        b.append(int(n[2:],16))
    bb = 0L
    for n in b:
        if n is None:
            for i in xrange(17-len(b)):
                bb *= 256
            continue
        bb *= 256
        bb += n
    return bb

ipv4addrmask = 65535L*256*256*256*256

class IP_List:
    def __init__(self, entrylist=None):
        self.ipv4list = []  # starts of ranges
        self.ipv4dict = {}  # start: end of ranges
        self.ipv6list = []  # "
        self.ipv6dict = {}  # "

        if entrylist:
            l4 = []
            l6 = []
            for b,e in entrylist:
                assert b <= e
                if b.find(':') < 0:        # IPv4
                    b = to_long_ipv4(b)
                    e = to_long_ipv4(e)
                    l4.append((b,e))
                else:
                    b = to_long_ipv6(b)
                    e = to_long_ipv6(e)
                    bb = b % (256*256*256*256)
                    if bb == ipv4addrmask:
                        b -= bb
                        e -= bb
                        l4.append((b,e))
                    else:
                        l6.append((b,e))
            self._import_ipv4(l4)
            self._import_ipv6(l6)

    def __nonzero__(self):
        return bool(self.ipv4list or self.ipv6list)


    def append(self, ip_beg, ip_end = None):
        if ip_end is None:
            ip_end = ip_beg
        else:
            assert ip_beg <= ip_end
        if ip_beg.find(':') < 0:        # IPv4
            ip_beg = to_long_ipv4(ip_beg)
            ip_end = to_long_ipv4(ip_end)
            l = self.ipv4list
            d = self.ipv4dict
        else:
            ip_beg = to_long_ipv6(ip_beg)
            ip_end = to_long_ipv6(ip_end)
            bb = ip_beg % (256*256*256*256)
            if bb == ipv4addrmask:
                ip_beg -= bb
                ip_end -= bb
                l = self.ipv4list
                d = self.ipv4dict
            else:
                l = self.ipv6list
                d = self.ipv6dict

        p = bisect(l,ip_beg)-1
        if p >= 0:
            while p < len(l):
                range_beg = l[p]
                if range_beg > ip_end+1:
                    done = True
                    break
                range_end = d[range_beg]
                if range_end < ip_beg-1:
                    p += 1
                    if p == len(l):
                        done = True
                        break
                    continue
                # if neither of the above conditions is true, the ranges overlap
                ip_beg = min(ip_beg, range_beg)
                ip_end = max(ip_end, range_end)
                del l[p]
                del d[range_beg]
                break

        insort(l,ip_beg)
        d[ip_beg] = ip_end


    def _import_ipv4(self, entrylist):  #entrylist = sorted list of pairs of ipv4s converted to longs
        assert not self.ipv4list
        if not entrylist:
            return
        entrylist.sort()
        l = []
        b1,e1 = entrylist[0]
        for b2,e2 in entrylist:
            if e1+1 >= b2:
                e1 = max(e1,e2)
            else:
                l.append((b1,e1))
                b1 = b2
                e1 = e2
        l.append((b1,e1))
        self.ipv4list = [b for b,e in l]
        for b,e in l:
            self.ipv4dict[b] = e

    def _import_ipv6(self, entrylist):  #entrylist = sorted list of pairs of ipv6s converted to longs
        assert not self.ipv6list
        if not entrylist:
            return
        entrylist.sort()
        l = []
        b1,e1 = entrylist[0]
        for b2,e2 in entrylist:
            if e1+1 >= b2:
                e1 = max(e1,e2)
            else:
                l.append((b1,e1))
                b1 = b2
                e1 = e2
        l.append((b1,e1))
        self.ipv6list = [b for b,e in l]
        for b,e in l:
            self.ipv6dict[b] = e


    def includes(self, ip):
        if not (self.ipv4list or self.ipv6list):
            return False
        if ip.find(':') < 0:        # IPv4
            ip = to_long_ipv4(ip)
            l = self.ipv4list
            d = self.ipv4dict
        else:
            ip = to_long_ipv6(ip)
            bb = ip % (256*256*256*256)
            if bb == ipv4addrmask:
                ip -= bb
                l = self.ipv4list
                d = self.ipv4dict
            else:
                l = self.ipv6list
                d = self.ipv6dict
        for ip_beg in l[bisect(l,ip)-1:]:
            if ip == ip_beg:
                return True
            ip_end = d[ip_beg]
            if ip > ip_beg and ip <= ip_end:
                return True
        return False


    # reads a list from a file in the format 'whatever:whatever:ip-ip'
    # (not IPv6 compatible at all)
    def read_rangelist(self, file):
        l = []
        f = open(file, 'r')
        while True:
            line = f.readline()
            if not line:
                break
            line = line.strip()
            if not line or line[0] == '#':
                continue
            line = line.split(':')[-1]
            try:
                ip1,ip2 = line.split('-')
            except:
                ip1 = line
                ip2 = line
            try:
                ip1 = to_long_ipv4(ip1)
                ip2 = to_long_ipv4(ip2)
                assert ip1 <= ip2
            except:
                print '*** WARNING *** could not parse IP range: '+line
            l.append((ip1,ip2))
        f.close()
        self._import_ipv4(l)


def is_ipv4(ip):
    return ip.find(':') < 0

def is_valid_ip(ip):
    try:
        if is_ipv4(ip):
            a = ip.split('.')
            assert len(a) == 4
            for i in a:
                chr(int(i))
            return True
        to_long_ipv6(ip)
        return True
    except:
        return False
