'''Data types used in the configuration file

Copyright 2005-2006 AG Projects
'''

import socket
import re
import struct
from utilities import *


__all__ = ['StringList', 'HexNumber', 'IPAddress', 'Hostname',
           'PortRange', 'NetworkRange', 'NetworkRangeList',
           'ControlSocket', 'NetworkAddress',
           'MandatoryNetworkAddress', 'MediaProxyAddress']

class StringList(list):
    '''A list of strings'''
    def __new__(typ, value):
        if value.lower()=='none':
            return []
        return re.split(r'\s*,\s*', value)

class HexNumber(int):
    '''A decimal or hex number. if it starts with 0x it is a hex number, else decimal'''
    def __new__(typ, value):
        try:
            return int(value)
        except ValueError:
            try:
                return int(value, 16)
            except ValueError:
                raise ValueError("invalid hex/decimal number: %r" % value)

class IPAddress(str):
    '''An IP address'''
    def __new__(typ, value):
        try:
            socket.inet_aton(value)
        except socket.error:
            raise ValueError("invalid IP address: %r" % value)
        return str(value)

class Hostname(str):
    '''A Hostname or an IP address. The keyword `Any' is accepted in place of '0.0.0.0' '''
    def __new__(typ, value):
        if value.lower() == 'any':
            return '0.0.0.0'
        try:
            socket.inet_aton(socket.gethostbyname(value))
        except (socket.error, socket.gaierror):
            raise ValueError("invalid hostname or IP address: %r" % value)
        return str(value)

class PortRange(tuple):
    '''A port range in the form minPort:maxPort which will be expressed as (minPort, maxPort)'''
    def __new__(typ, value):
        m = re.search(r'^(?P<minPort>\d+):(?P<maxPort>\d+)$', value)
        if not m:
            raise ValueError("bad ports: %r: ports must be numbers" % value)
        minPort = int(m.group('minPort'))
        maxPort = int(m.group('maxPort'))
        allowed = xrange(1026, 65536, 2)
        if not (minPort in allowed and maxPort in allowed and minPort < maxPort):
            raise ValueError("bad range: %r: ports must be even and between 1026 and 65536" % value)
        return (minPort, maxPort)

class NetworkRange(tuple):
    '''
    Describes a network address range in the form of: (base_address, network_mask)

    Input should be a string in the form of:
        - network/mask
        - ip_address
        - hostname
    in the latter two cases a mask of 32 is assumed.
    Except the hostname case, where a DNS name is present, in the other cases
    the address should be in quad dotted number notation.
    Mask is number between 0 and 32 (bits used by the network part of address)
    On error ValueError is raised.
    '''
    def __new__(typ, description):
        if not description or description.lower()=='none':
            return (0L, 0xFFFFFFFFL)
        if description.lower()=='any':
            return (0L, 0L) ## This is the any address 0.0.0.0
        match = re.search(r'^(?P<net>.+?)/(?P<bits>\d+)$', description)
        if match:
            net     = match.group('net')
            netbits = int(match.group('bits'))
        else:
            try:
                net = socket.gethostbyname(description) # if not a net/mask it may be a host or ip
            except socket.gaierror:
                raise NameError, "invalid hostname or IP address: '%s'" % description
            netbits = 32
        if netbits < 0 or netbits > 32:
            raise ValueError, "invalid network mask in address: '%s' (should be between 0 and 32)" % description
        try:
            netaddr = socket.inet_aton(net)
        except:
            raise ValueError, "invalid IP address: '%s'" % net
        mask = (0xFFFFFFFFL << 32-netbits) & 0xFFFFFFFFL
        #mask = (-1L << 32-netbits) & 0xFFFFFFFFL
        #mask = ((1L << netbits) - 1) << 32-netbits
        netbase = struct.unpack('!L', netaddr)[0] & mask
        return (netbase, mask)

class NetworkRangeList(list):
    '''A list of NetworkRange objects'''
    def __new__(typ, description):
        if description.lower()=='none':
            return None
        lst = re.split(r'\s*,\s*', description)
        ranges = []
        for x in lst:
            try:
                range = NetworkRange(x)
            except NameError:
                warning("couldn't resolve hostname: `%s' (ignored)" % x)
            except ValueError:
                warning("Invalid network specification: `%s' (ignored)" % x)
            else:
                ranges.append(range)
        return ranges or None

class ControlSocket(str):
    '''A UNIX filesystem socket or None'''
    def __new__(typ, value):
        if value.lower() == 'none':
            return None
        return str(value)

class NetworkAddress(tuple):
    '''A TCP/IP host[:port] network address. 
    Host may be a hostname, an IP address or the keyword `any' meaning `0.0.0.0'.
    If port is missing the mediaproxy's default port (25060) will be used.
    Using the keyword `default' as the value, will use `0.0.0.0:25060' as the address.'''
    _defaultPort = 25060
    _name = 'network address'
    def __new__(typ, value):
        if value.lower() == 'none': return None
        if value.lower() == 'default': return ('0.0.0.0', typ._defaultPort)
        match = re.search(r'^(?P<address>.+?):(?P<port>\d+)$', value)
        if match:
            address = str(match.group('address'))
            port = int(match.group('port'))
        else:
            address = value
            port = typ._defaultPort
        try:
            address = Hostname(address)
        except ValueError:
            raise ValueError("invalid %s: %r" % (typ._name, value))
        return (address, port)

class MandatoryNetworkAddress(NetworkAddress):
    '''This is a NetworkAddress that cannot be None or have an undefined port.'''
    def __new__(typ, value):
        address = NetworkAddress.__new__(typ, value)
        if address is None:
            raise ValueError("invalid %s: %s" % (typ._name, value))
        elif address[1]==0:
            raise ValueError("invalid %s: %s:%s" % (typ._name, address[0], address[1]))
        return address

class MediaProxyAddress(object):
    '''A MediaProxy address. Can be either a ControlSocket or a NetworkAddress.'''
    def __new__(typ, value):
        if re.search(r'^(None|.*/.*)$', value, re.IGNORECASE):
            return ControlSocket(value)
        else:
            address = NetworkAddress(value)
            if type(address) is tuple and (address[0]=='0.0.0.0' or address[1]==0):
                raise ValueError("invalid MediaProxy address: %s:%s" % address)
            return address

