# -*- coding: utf-8 -*-
"""
This module/script is for updating port_for._ranges with recent information
from IANA and Wikipedia.
"""
from __future__ import absolute_import
import sys
import os
import re
import datetime
import urllib2
from xml.etree import ElementTree

name = os.path.abspath(os.path.normpath(os.path.join(os.path.dirname(__file__), '..')))
sys.path.insert(0, name)

from port_for.utils import to_ranges, ranges_to_set

IANA_DOWNLOAD_URL = 'https://www.iana.org/assignments/service-names-port-numbers/service-names-port-numbers.xml'
IANA_NS = 'http://www.iana.org/assignments'
WIKIPEDIA_PAGE = 'http://en.wikipedia.org/wiki/List_of_TCP_and_UDP_port_numbers'


def _write_unassigned_ranges(out_filename):
    """
    Downloads ports data from IANA & Wikipedia and converts
    it to a python module. This function is used to generate _ranges.py.
    """
    with open(out_filename, 'wt') as f:
        f.write('# auto-generated by port_for._download_ranges (%s)\n' % datetime.date.today())
        f.write('UNASSIGNED_RANGES = [\n')
        for range in to_ranges(sorted(list(_unassigned_ports()))):
            f.write("    (%d, %d),\n" % range)
        f.write(']\n')


def _unassigned_ports():
    """ Returns a set of all unassigned ports (according to IANA and Wikipedia) """
    free_ports = ranges_to_set(_parse_ranges(_iana_unassigned_port_ranges()))
    known_ports = ranges_to_set(_wikipedia_known_port_ranges())
    return free_ports.difference(known_ports)


def _wikipedia_known_port_ranges():
    """
    Returns used port ranges according to Wikipedia page.
    This page contains unofficial well-known ports.
    """
    req = urllib2.Request(WIKIPEDIA_PAGE, headers={'User-Agent' : "Magic Browser"})
    page = urllib2.urlopen(req).read().decode('utf8')

    # just find all numbers in table cells
    ports = re.findall('<td>((\d+)(\W(\d+))?)</td>', page, re.U)
    return ((int(p[1]), int(p[3] if p[3] else p[1])) for p in ports)


def _iana_unassigned_port_ranges():
    """
    Returns unassigned port ranges according to IANA.
    """
    page = urllib2.urlopen(IANA_DOWNLOAD_URL).read()
    xml = ElementTree.fromstring(page)
    records = xml.findall('{%s}record' % IANA_NS)
    for record in records:
        description = record.find('{%s}description' % IANA_NS).text
        if description == 'Unassigned':
            numbers = record.find('{%s}number' % IANA_NS).text
            yield numbers


def _parse_ranges(ranges):
    """ Converts a list of string ranges to a list of [low, high] tuples. """
    for txt in ranges:
        if '-' in txt:
            low, high = txt.split('-')
        else:
            low, high = txt, txt
        yield int(low), int(high)


if __name__ == '__main__':
    _write_unassigned_ranges('_ranges.py')
