"""Utility module containing unrelated functions like initialization.
"""

# Copyright (c) 2004 Infrae. All rights reserved.
# See also LICENSE.txt

from mod_python import apache, util
import sys

import time
from datetime import datetime
from calendar import timegm

import urlparse
import urllib
import urllib2

import rr_config

###

stderr = sys.stderr

_notice_level = 3

###

def ap_debug ( request, msg ):
    if _notice_level > 2:
        msg = 'RR-DEBUG(%s): %s' % (request.request_id, msg)
        request.log_error(msg, apache.APLOG_NOTICE)
    return
#

def ap_notice ( request, msg ):
    if _notice_level > 1:
        msg = 'RR-NOTICE(%s): %s' % (request.request_id, msg)
        request.log_error(msg, apache.APLOG_NOTICE)
    return
#

def ap_warn ( request, msg ):
    if _notice_level > 0:
        msg = 'RR-WARNING(%s): %s' % (request.request_id, msg)
        request.log_error(msg, apache.APLOG_WARNING)
    return
#

def ap_error ( request, msg ):
    msg = 'RR-ERROR(%s): %s' % (request.request_id, msg)
    request.log_error(msg, apache.APLOG_ERR)
    return
#

def redirect ( request, url, msg=None ):
    """Issue a redirect response to the given url.

    If msg is set, it is passed along as the errormsg parameter.

    This function does not return!
    """
    if msg is not None:
        url += '?errormsg=%s' % urllib.quote(msg)
    util.redirect(request, url)
    pass
#

def get_option ( name, req ):
    """Fetch the given option from req.

    Returns None if the option name ws not found.
    """
    options = req.get_options()
    try:
        val = options[name]
        val = val.strip()
    except KeyError:
        ap_notice(req, '     %s option not set!' % name)
        val = None
        pass
    return val
#

def init_railroad ( request ):
    """Initialize the railroad system.

    Get the PythonOption settings and set the corresponding values in rr_config.
    """
    if rr_config.initialized:
        ap_warn(request, 'railroad initialization already done!')
        return
    # check for repository name
    repo_name = get_option('repository', request)
    if repo_name is not None:
        repo_name = repo_name.strip()
    else:
        ap_error(request, 'repository not set!')
        raise apache.SERVER_RETURN, apache.HTTP_INTERNAL_SERVER_ERROR
    rr_config.repo_name = repo_name
    # check for authorization
    do_auth = get_option('authorize', request)
    if do_auth is not None:
        do_auth = do_auth.lower().strip()
        if do_auth in ('on', 'true', '1', 'yes'):
            do_auth = True
        else:
            do_auth = False
    else:
        do_auth = False
        ap_error(request, '     authorize option not set!')
    rr_config.do_auth = do_auth
    # check for search feature
    do_search = get_option('search', request)
    if do_search is not None:
        do_search = do_search.lower().strip()
        if do_search in ('on', 'true', '1', 'yes'):
            do_search = True
        else:
            do_search = False
    else:
        do_search = False
        ap_error(request, '     search option not set!')
    rr_config.do_search = do_search
    # enable DB if necessary
    if do_auth or do_search:
        ap_notice(request, 'ap_railroad enabling database!')
        dbmodule = get_option('db_module', request)
        if dbmodule is None:
            ap_error('no database module configured!')
            raise apache.SERVER_RETURN, apache.HTTP_INTERNAL_SERVER_ERROR
        rr_config.DB = DB = apache.import_module(dbmodule, log=1)
        dbopts = {}
        for opt in ('dbname', 'dbuser', 'dbpasswd', 'dbhost'):
            try:
                val = get_option(opt, request)
                ap_debug(request, '     %s set: %s' % (opt, val))
            except KeyError:
                ap_debug(request, '     %s option not set!' % opt)
                val = None
                pass
            if val is not None:
                dbopts[opt] = val.strip()
        # create dabase connection
        DB.connect(dbopts)
        # get master user/pw for this repository
        repo_id, root_user, root_pw = DB.get_user_pw_for(repo_name)
        rr_config.repo_master[repo_name] = (repo_id, root_user, root_pw)
        if do_search:
            import rr_props
    else:
        ap_notice(request, 'railroad database disabled!')
    rr_config.initialized = True
    ap_notice(request, 'railroad repository %s initialized!' % repo_name)
    return
#

def call_cms ( url, user, passwd ):
    """Issue a request to the given url (which should be a CMS).

    User and passwd are supplied if needed.

    Only Basic Authentication is supported!
    """
    parts = urlparse.urlsplit(url)
    host = parts[1]
    pwmgr = urllib2.HTTPPasswordMgrWithDefaultRealm()
    auth_handler = urllib2.HTTPBasicAuthHandler(pwmgr)
    if user:
        auth_handler.add_password( None, host, user, passwd )
    opener = urllib2.build_opener( auth_handler, urllib2.HTTPHandler )
    try:
        f = opener.open(url)
        data = f.read()
        f.close()
        return True
    except urllib2.HTTPError, err:
        pass
    except Exception, ex:
        pass
    return False
#

def file_length( f ):
   """Compute length of given file object.
   """
   curr = f.tell()
   f.seek(0,2)
   l = f.tell()
   f.seek(curr)
   return l
#

def register_hook ( hook, fnc ):
    """Register the callable fnc with the hook named hook.

    The hook name doesn't need to exist yet.
    """
    try:
        hl = rr_config.hook_registry[hook]
        hl.append(fnc)
    except KeyError:
        rr_config.hook_registry[hook] = [ fnc ]
        pass
    return
#

def call_hooks ( hook, request, *args, **kwargs ):
    """Call all callables registered with the given hook.

    Will pass on all arguments to the callables.
    """
    # avoid recursion
    if hasattr(request, '_calling_hooks'):
        return
    ap_debug(request, 'Calling hooks for %s' % hook)
    try:
        request._calling_hooks = hook
        try:
            hooks = rr_config.hook_registry[hook]
            for fnc in hooks:
                fnc(request, *args, **kwargs)
        except KeyError:
            pass
    finally:
        del request._calling_hooks
    return
#

_dt_formats = ('%Y-%m-%dT%H:%M:%S%Z',
               '%Y-%m-%dT%H:%M:%SZ',
               '%Y-%m-%dT%H:%M:%S',
               '%Y-%m-%d',
               '%c',
               '%a %b %d %H:%M:%S %Y',
               '%a, %d %b %Y %H:%M:%S')

def parse_timestamp ( ts, xfmts=None ):
    """Try to parse ts using some common formats for timestamps
    and return a datetime object if parsing was successfull.

    xmfts: list of additional formats to try.

    Built-in formats: see _dt_formats
    """
    fl = list(_dt_formats)
    if xfmts:
        fl.extend(list(xfmts))
    if ts.find('.') > 0:
        ts = ts.split('.', 1)[0]
    for fmt in fl:
        try:
            t = time.strptime(ts, fmt)
            t = timegm(t)
            try:
                dt = datetime.utcfromtimestamp(t)
                return dt
            except TypeError:
                raise TypeError, (type(t), 'should be float')
        except ValueError:
            pass
    raise ValueError, repr(ts)
#

def make_utc_timestamp ( now=None, delimiter='T', tz='Z' ):
    """Creat and return a iso8601 timestamp as string.

    If now is not None, it has to be either a datatime instance or a float.
    """
    if now is None:
        now = datetime.utcnow()
    elif isinstance(now, datetime):
        # ok
        pass
    elif type(now) == float:
        now = datetime.utcfromtimestamp(now)
    else:
        raise TypeError
    ts = now.replace(microsecond=0).isoformat(delimiter) + tz
    return ts
#

def encode_dict ( d_in, encoding='utf-8'):
    def encode_one( x ):
        if type(x) == unicode:
            return x.encode(encoding)
        return x
    d_out = {}
    for k, v in d_in.iteritems():
        if type(k) is tuple:
            k = tuple([encode_one(i) for i in k])
        else:
            k = encode_one(k)
        v = encode_one(v)
        d_out[k] = v
    return d_out
#

def decode_dict ( d_in, encoding='utf-8'):
    def decode_one( x ):
        if type(x) != unicode:
            return x.decode(encoding)
        return x
    d_out = {}
    for k, v in d_in.iteritems():
        if type(k) is tuple:
            k = tuple([decode_one(i) for i in k])
        else:
            k = decode_one(k)
        v = decode_one(v)
        d_out[k] = v
    return d_out
#
###
