import os
import sys
import time
import logging
from re import compile
from socket import gethostbyaddr

try:
    import twisted.internet
    from twisted.application.service import MultiService
    import zope.app.appsetup.interfaces
    import zope.app.twisted.main

    import twisted.web2.wsgi
    import twisted.web2.server
    import twisted.web2.log
    
    try:
        from twisted.web2.http import HTTPFactory
    except ImportError:
        from twisted.web2.channel.http import HTTPFactory
    
    from zope.component import provideUtility
    from zope.app.twisted.server import ServerType, SSLServerType
    from zope.app.twisted.interfaces import IServerType
    from ZPublisher.WSGIPublisher import publish_module
    
    _use_twisted = True
except ImportError:
    _use_twisted = False

# top-level key handlers


# XXX  I suspect there are still problems here.  In most cases, if the
# value is not set from the config file, or if the value is the
# default, the corresponding envvar is not unset, which is needed to
# ensure that the environment variables and the configuration values
# reflect a coherant configuration.

def _setenv(name, value):
    if isinstance(value, str):
        os.environ[name] = value
    else:
        os.environ[name] = `value`

def debug_mode(value):
    value and _setenv('Z_DEBUG_MODE', '1')
    import Globals # to set value
    Globals.DevelopmentMode = boolean(value)
    return value

def locale(value):
    import locale
    locale.setlocale(locale.LC_ALL, value)
    return value

def datetime_format(value):
    value and _setenv('DATETIME_FORMAT', value)
    return value

def automatically_quote_dtml_request_data(value):
    not value and _setenv('ZOPE_DTML_REQUEST_AUTOQUOTE', '0')
    return value

def maximum_number_of_session_objects(value):
    default = 1000
    value not in (None, default) and _setenv('ZSESSION_OBJECT_LIMIT', value)
    return value

def session_add_notify_script_path(value):
    value is not None and _setenv('ZSESSION_ADD_NOTIFY', value)
    return value

def session_delete_notify_script_path(value):
    value is not None and _setenv('ZSESSION_DEL_NOTIFY', value)
    return value

def session_timeout_minutes(value):
    default = 20
    value not in (None, default) and _setenv('ZSESSION_TIMEOUT_MINS', value)
    return value

def suppress_all_access_rules(value):
    value and _setenv('SUPPRESS_ACCESSRULE', value)
    return value

def suppress_all_site_roots(value):
    value and _setenv('SUPPRESS_SITEROOT', value)
    return value

def database_quota_size(value):
    value and _setenv('ZOPE_DATABASE_QUOTA', value)
    return value

def read_only_database(value):
    # This handler exists only for BBB:  using the 'read-only-database'
    # directive in a config file is deprecated.
    if value is not None:
        import warnings
        warnings.warn("The 'read-only-database' configuration option is "
                    "deprecated; please use the correct optoin inside the "
                    "section defining the appropriate storage.",
                    DeprecationWarning, stacklevel=2)
    return value

def zeo_client_name(value):
    value and _setenv('ZEO_CLIENT', value)
    return value

def structured_text_header_level(value):
    value is not None and _setenv('STX_DEFAULT_LEVEL', value)
    return value

def rest_input_encoding(value):
    value and _setenv('REST_INPUT_ENCODING' , value)
    return value

def rest_output_encoding(value):
    value and _setenv('REST_OUTPUT_ENCODING' , value)
    return value

def rest_header_level(value):
    value and _setenv('REST_DEFAULT_LEVEL' , value)
    return value

def rest_language_code(value):
    value and _setenv('REST_LANGUAGE_CODE' , value)
    return value

def large_file_threshold(value):
    import ZServer
    ZServer.LARGE_FILE_THRESHOLD = value

def publisher_profile_file(value):
    value is not None and _setenv('PROFILE_PUBLISHER', value)
    from ZPublisher.Publish import install_profiling
    install_profiling(value)
    return value

def http_realm(value):
    value is not None and _setenv('Z_REALM', value)
    return value

def max_listen_sockets(value):
    import ZServer
    ZServer.CONNECTION_LIMIT = value

def cgi_maxlen(value):
    import cgi
    cgi.maxlen = value

def http_header_max_length(value):
    return value

def enable_ms_author_via(value):
    import webdav
    webdav.enable_ms_author_via = value

def enable_ms_public_header(value):
    import webdav
    webdav.enable_ms_public_header = value

def catalog_getObject_raises(value):

    if value is not None:

        import warnings
        warnings.warn(
        "'catalog-getObject-raises' option will be removed in Zope 2.10:\n",
        DeprecationWarning)

        from Products.ZCatalog import CatalogBrains
        CatalogBrains.GETOBJECT_RAISES = bool(value)

    return value

# server handlers

def root_handler(config):
    """ Mutate the configuration with defaults and perform
    fixups of values that require knowledge about configuration
    values outside of their context.
    """

    # Set environment variables
    for k,v in config.environment.items():
        os.environ[k] = v

    # Add directories to the pythonpath
    instancelib = os.path.join(config.instancehome, 'lib', 'python')
    if instancelib not in config.path:
        if os.path.isdir(instancelib):
            config.path.append(instancelib)
    path = config.path[:]
    path.reverse()
    for dir in path:
        sys.path.insert(0, dir)

    # Add any product directories not already in Products.__path__.
    # Directories are added in the order they are mentioned

    instanceprod = os.path.join(config.instancehome, 'Products')
    if instanceprod not in config.products:
        if os.path.isdir(instanceprod):
            config.products.append(instanceprod)

    import Products
    L = []
    for d in config.products + Products.__path__:
        if d not in L:
            L.append(d)
    Products.__path__[:] = L

    # Augment the set of MIME types:
    if config.mime_types:
        from zope.contenttype import add_files
        add_files(config.mime_types)

    # if no servers are defined, create default http server and ftp server
    if not config.servers:
        config.servers = []

    # prepare servers:
    for factory in config.servers:
        factory.prepare(config.ip_address or '',
                        config.dns_resolver,
                        "Zope2",
                        config.cgi_environment,
                        config.port_base)

    if not config.twisted_servers:
        config.twisted_servers = []
    else:
        # Set number of threads (reuse zserver_threads variable)
        twisted.internet.reactor.suggestThreadPoolSize(config.zserver_threads)

        # Create a root service
        rootService = MultiService()

        for server in config.twisted_servers:
            service = server.create(None)
            service.setServiceParent(rootService)

        rootService.startService()
        twisted.internet.reactor.addSystemEventTrigger(
            'before', 'shutdown', rootService.stopService)

    # set up trusted proxies
    if config.trusted_proxies:
        import ZPublisher.HTTPRequest
        # DM 2004-11-24: added host name mapping (such that examples in
        # conf file really have a chance to work
        mapped = []
        for name in config.trusted_proxies: mapped.extend(_name2Ips(name))
        ZPublisher.HTTPRequest.trusted_proxies = tuple(mapped)


def handleConfig(config, multihandler):
    handlers = {}
    for name, value in globals().items():
        if not name.startswith('_'):
            handlers[name] = value
    return multihandler(handlers)

# DM 2004-11-24: added
def _name2Ips(host, isIp_=compile(r'(\d+\.){3}').match):
    '''map a name *host* to the sequence of its ip addresses;
    use *host* itself (as sequence) if it already is an ip address.
    Thus, if only a specific interface on a host is trusted,
    identify it by its ip (and not the host name).
    '''
    if isIp_(host): return [host]
    return gethostbyaddr(host)[2]


# Twisted support:

def createHTTPFactory(ignored):
    resource = twisted.web2.wsgi.WSGIResource(publish_module)
    resource = twisted.web2.log.LogWrapperResource(resource)

    return HTTPFactory(twisted.web2.server.Site(resource))

if _use_twisted:
    http = ServerType(createHTTPFactory, 8080)
    provideUtility(http, IServerType, 'Zope2-HTTP')
