
'''
Extends Option class with 7 more types:
    - hex/decimal number
    - comma separated list of values
    - host. either a hostname, an ip address or the keyword 'Any' (standing for 0.0.0.0)
    - ip address. this one is easy ;)
    - ports range (min:max between 1026 and 65536, aligned to even values)
    - network address. host[:port]. if port is not specified use 25060. host as above
    - proxy control port. either a network address as above or a file (unix socket path)
      'Any' address is not accepted in this context.

Works with python 2.3 or newer. Also with older pythons if they have the python-optik
module (version 1.4.1 or newer) installed.

'''

from optparse import Option, OptionValueError
from copy import copy

class ExtendedOptions(Option):
    '''Extends the Option class with 5 more types:
       IP address, hex/decimal number, ports range, network address and server address'''

    def checkHexDec(option, opt, value):
        '''Check input if it's a decimal or hex number and convert accordingly'''
        try:
            return int(value)
        except ValueError:
            try:
                return int(value, 16)
            except ValueError:
                raise OptionValueError(
                    "option %s: invalid hex/decimal value: %r" % (opt, value))

    def checkList(option, opt, value):
        '''Takes a list of elements separated by comma and splits them. Not much to check'''
        return value.split(',')

    def checkHost(option, opt, value):
        '''Check if input is an existing hostname or a valid IP address.
        The keyword `Any' is accepted in place of '0.0.0.0' '''
        import socket
        if value.lower() == 'any':
            return '0.0.0.0'
        try:
            socket.inet_aton(socket.gethostbyname(value))
            return value
        except (socket.error, socket.gaierror):
            raise OptionValueError(
                "option %s: invalid host or IP address: %r" % (opt, value))

    def checkIPAddress(option, opt, value):
        '''Check if input is a valid IP address'''
        import socket
        try:
            socket.inet_aton(value)
            return value
        except socket.error:
            raise OptionValueError(
                "option %s: invalid IP address: %r" % (opt, value))

    def checkIPPorts(option, opt, value):
        '''Check if input is a range of ports'''
        import re
        m = re.search(r'(?P<minPort>\d+):(?P<maxPort>\d+)', value)
        if not m:
            raise OptionValueError(
                "option %s: invalid port range: %r" % (opt, value))
        minPort = int(m.group('minPort'))
        maxPort = int(m.group('maxPort'))
        allowed = xrange(1026, 65536, 2)
        if minPort in allowed and maxPort in allowed and minPort < maxPort:
            return (minPort, maxPort)
        raise OptionValueError("option %s: invalid port range: %r: ports must be even and between 1026 and 65536" % (opt, value))

    def checkNetworkAddress(option, opt, value):
        '''Check if input is a TCP/IP host[:port]. 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' for the option will use `0.0.0.0:25060' as the address.'''
        import re, socket
        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 = 25060
        try:
            address = option.checkHost(opt, address)
        except OptionValueError:
            raise OptionValueError(
                "option %s: invalid network address: %r" % (opt, value))
        return (address, port)

    def checkProxyControlPort(option, opt, value):
        '''Check if input is a TCP/IP host[:port], a Unix socket file or 'None' '''
        if value.lower() == 'none':
            return value
        if value.find('/') >= 0:
            return value ## quick shortcut to avoid checking for a network address
        try:
            result = option.checkNetworkAddress(opt, value)
        except OptionValueError:
            ## Assume is a unix file socket
            return value
        if result[0] == '0.0.0.0':
            raise OptionValueError(
                "`Any' address not applicable for the %s option" % opt)
        return result

    TYPES = Option.TYPES + ('hexdec', 'list', 'host', 'ip', 'ports', 'netaddr', 'controlport')
    TYPE_CHECKER = copy(Option.TYPE_CHECKER)
    TYPE_CHECKER['hexdec'] = checkHexDec
    TYPE_CHECKER['list'] = checkList
    TYPE_CHECKER['host'] = checkHost
    TYPE_CHECKER['ip'] = checkIPAddress
    TYPE_CHECKER['ports'] = checkIPPorts
    TYPE_CHECKER['netaddr'] = checkNetworkAddress
    TYPE_CHECKER['controlport'] = checkProxyControlPort

