#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
    trytond_sentry

    Tryton application launcher which monkey patches the dispatcher to
    send errors to a sentry server if configured.

    :copyright: © 2012-2014 by Openlabs Technologies & Consulting (P) Limited
    :license: BSD, see LICENSE for more details.
"""

import sys
import os
import optparse
from functools import wraps
DIR = os.path.abspath(os.path.normpath(os.path.join(__file__,
    '..', '..', 'trytond')))
if os.path.isdir(DIR):
    sys.path.insert(0, os.path.dirname(DIR))

import trytond
from trytond.version import VERSION
from trytond.exceptions import UserError, UserWarning, ConcurrencyException, \
        NotLogged
from raven import Client
import pkg_resources


_MODULES_INFO = {}


def get_modules():
    """
    Return the name and version of all python and tryton
    modules in a dictionary object.
    """
    if _MODULES_INFO:
        # Return the value if it was computed and made atleast once
        return _MODULES_INFO

    # Take all the packages in the current working set and
    # their versions
    for module in pkg_resources.WorkingSet():
        _MODULES_INFO[module.key] = module.version

    # Now update the tryton modules alone in a tryton namespace
    # All names are prefixed with an _ so that they appear first
    # when viewed from sentry.
    from trytond.modules import get_module_info, get_module_list
    for module in get_module_list():
        _MODULES_INFO['_trytond.modules.' + module] = \
            get_module_info(module).get('version')
    return _MODULES_INFO


def patch(old_dispatch, client):
    """
    Patch the `old_dispatcher` with an exception handler to send exceptions
    which occur to sentry through `client`

    :param old_dispatch: the function/method to be patched
    :param client: An instance of :class:`raven.Client`.
    """
    @wraps(old_dispatch)
    def wrapper(*args, **kwargs):
        try:
            return old_dispatch(*args, **kwargs)
        except (UserError, UserWarning, ConcurrencyException, NotLogged):
            raise
        except Exception:
            data = {
                'modules': get_modules(),
            }
            # TODO: Add user interface data
            event_id = client.captureException(True, data=data)
            raise UserError(
                "Oops! Something terrible happened\n\n"
                "Your ever loving friends at Openlabs have been notified of "
                "this grave fault!\n",
                "However, if you wish to speak with an Openlabs consultant "
                "about this issue, you may use the following reference:\n\n"
                "%s" % event_id
            )
    return wrapper


def parse_commandline():
    options = {}

    parser = optparse.OptionParser(version=VERSION)

    parser.add_option("-c", "--config", dest="config",
        help="specify config file")
    parser.add_option('--debug', dest='debug_mode', action='store_true',
        help='enable debug mode (start post-mortem debugger if exceptions'
        ' occur)')
    parser.add_option("-v", "--verbose", action="store_true",
        dest="verbose", help="enable verbose mode")

    parser.add_option("-d", "--database", dest="db_name",
        help="specify the database name")
    parser.add_option("-i", "--init", dest="init",
        help="init a module (use \"all\" for all modules)")
    parser.add_option("-u", "--update", dest="update",
        help="update a module (use \"all\" for all modules)")

    parser.add_option("--pidfile", dest="pidfile",
        help="file where the server pid will be stored")
    parser.add_option("--logfile", dest="logfile",
        help="file where the server log will be stored")
    parser.add_option("--disable-cron", dest="cron",
        action="store_false", help="disable cron")
    parser.add_option("-s", "--sentry-dsn",
        help="Sentry DSN", dest="sentry_dsn")

    (opt, _) = parser.parse_args()

    sentry_dsn = opt.sentry_dsn or os.environ.get('SENTRY_DSN')

    if sentry_dsn:
        client = Client(sentry_dsn)

        # Monkey patch the dispatcher
        from trytond.protocols import dispatcher
        dispatcher.dispatch = patch(dispatcher.dispatch, client)

    if opt.config:
        options['configfile'] = opt.config
    else:
        # No config file speficified, it will be guessed
        options['configfile'] = None

    for arg in (
            'verbose',
            'debug_mode',
            'pidfile',
            'logfile',
            'cron'):
        if getattr(opt, arg) is not None:
            options[arg] = getattr(opt, arg)

    db_name = []
    if opt.db_name:
        for i in opt.db_name.split(','):
            db_name.append(i)
    options['db_name'] = db_name

    init = {}
    if opt.init:
        for i in opt.init.split(','):
            if i != 'test':
                init[i] = 1
    options['init'] = init

    update = {}
    if opt.update:
        for i in opt.update.split(','):
            if i != 'test':
                update[i] = 1
    options['update'] = update

    return options


if '--profile' in sys.argv:
    import profile
    import pstats
    import tempfile
    sys.argv.remove('--profile')

    options = parse_commandline()
    statfile = tempfile.mkstemp(".stat", "trytond-")[1]
    profile.run('trytond.server.TrytonServer(options).run()', statfile)
    s = pstats.Stats(statfile)
    s.sort_stats('cumulative').print_stats()
    s.sort_stats('call').print_stats()
    s.sort_stats('time').print_stats()
    s.sort_stats('time')
    s.print_callers()
    s.print_callees()

    os.remove(statfile)
else:
    options = parse_commandline()
    trytond.server.TrytonServer(options).run()
