# -*- coding: utf-8 -*-

#########################################################################
# pyeole.service._<METHOD> - manage <METHOD> services
# Copyright © 2013 Pôle de Compétence EOLE <eole@ac-dijon.fr>
#
# License CeCILL:
#  * in french: http://www.cecill.info/licences/Licence_CeCILL_V2-fr.html
#  * in english http://www.cecill.info/licences/Licence_CeCILL_V2-en.html
#########################################################################

"""EOLE <METHOD> service management

THIS IS A SKELETON.

This internal module is only for use by :mod:`pyeole.service`.

"""

import re

import logging

# Base exception
from pyeole.service import ServiceError
# Configuration
from pyeole.service import ConfigureError
from pyeole.service import DisabledError
from pyeole.service import UnknownServiceError
# Action
from pyeole.service import StartError
from pyeole.service import StopError

from pyeole.service import _exec_cmd
from pyeole.service import _SERVICE_CMD

log = logging.getLogger(__name__)
log.addHandler(logging.NullHandler())


#####
##### Per action workers
#####


def _check_service(action, service, ctx):
    """Verify if a <METHOD> service exists and is in coherent state.

    :param service: service informations
    :type service: `dict`
    :param ctx: container context
    :type ctx: `dict`
    :raise UnknownServiceError: if service is unknown
    :raise DisabledError: if service is disabled

    """
    name = service[u'name']
    log.debug(u'Check <METHOD> service {0} in {1}'.format(name, ctx[u'name']))

    raise NotImplementedError(u'<METHOD> is just a template')


def _configure_service(service, ctx):
    """Configure a <METHOD> service.

    Use :data:`service[u'activate']` to figure out what to do.

    :param service: service informations
    :type service: `dict`
    :param ctx: container context
    :type ctx: `dict`

    """
    log.debug(u'Configure <METHOD> service {0} in {1}'.format(service[u'name'],
                                                              ctx[u'name']))
    if service.get(u'activate', False):
        _enable_service(service, ctx)
    else:
        _disable_service(service, ctx)


def _apply_service(service, ctx):
    """Configure and start/stop a <METHOD> service.

    Use :data:`service[u'activate']` to figure out what to do.

    :param service: service informations
    :type service: `dict`
    :param ctx: container context
    :type ctx: `dict`

    """
    log.debug(u'Apply <METHOD> service {0} in {1}'.format(service[u'name'],
                                                          ctx[u'name']))
    if service.get(u'activate', False):
        _start_service(service, ctx)
    else:
        _stop_service(service, ctx)


def _enable_service(service, ctx):
    """Enable a <METHOD> service.

    :param service: service informations
    :type service: `dict`
    :param ctx: container context
    :type ctx: `dict`

    """
    name = service[u'name']
    log.info(u'Enable <METHOD> service {0} in {1}'.format(name, ctx[u'name']))

    raise NotImplementedError(u'<METHOD> is just a template')


def _disable_service(service, ctx):
    """Disable a <METHOD> service.

    :param service: service informations
    :type service: `dict`
    :param ctx: container context
    :type ctx: `dict`

    """
    name = service[u'name']
    log.info(u'Disable <METHOD> service {0} in {1}'.format(name, ctx[u'name']))

    raise NotImplementedError(u'<METHOD> is just a template')


def _status_service(service, ctx):
    """Check status of a <METHOD> service.

    :param service: service informations
    :type service: `dict`
    :param ctx: container context
    :type ctx: `dict`

    """
    msg = u'Get status of <METHOD> service {0} in {1}'
    log.debug(msg.format(service[u'name'], ctx[u'name']))
    try:
        _check_service(u'status', service, ctx)
        _do_<METHOD>(u'status', service, ctx)
        log.info(u'Service {0} in {1} is running'.format(service[u'name'],
                                                         ctx[u'name']))
    except UnknownServiceError:
        msg = u'Service {0} in {1} is not installed'
        log.error(msg.format(service['name'], ctx[u'name']))
    except ConfigureError, err:
        msg = u'Service {0} in {1} is miss-configured: {2}'
        log.error(msg.format(service[u'name'], ctx[u'name'], err))
    except DisabledError:
        msg = u'Service {0} in {1} is disabled'
        log.info(msg.format(service[u'name'], ctx[u'name']))
    except ServiceError:
        msg = u'Service {0} in {1} is not running'
        log.info(msg.format(service[u'name'], ctx[u'name']))


def _start_service(service, ctx):
    """Start a <METHOD> service.

    :param service: service informations
    :type service: `dict`
    :param ctx: container context
    :type ctx: `dict`

    """
    log.info(u'Start <METHOD> service {0} in {1}'.format(service[u'name'],
                                                         ctx[u'name']))
    _do_<METHOD>(u'start', service, ctx)


def _stop_service(service, ctx):
    """Stop a <METHOD> service.

    :param service: service informations
    :type service: `dict`
    :param ctx: container context
    :type ctx: `dict`

    """
    log.info(u'Stop <METHOD> service {0} in {1}'.format(service[u'name'],
                                                        ctx[u'name']))
    _do_<METHOD>(u'stop', service, ctx)


def _restart_service(service, ctx):
    """Restart a <METHOD> service.

    :param service: service informations
    :type service: `dict`
    :param ctx: container context
    :type ctx: `dict`

    """
    log.info(u'Restart <METHOD> service {0} in {1}'.format(service[u'name'],
                                                           ctx[u'name']))
    _do_<METHOD>(u'restart', service, ctx)


def _reload_service(service, ctx):
    """Reload a <METHOD> service.

    :param service: service informations
    :type service: `dict`
    :param ctx: container context
    :type ctx: `dict`

    """
    log.info(u'Reload <METHOD> service {0} in {1}'.format(service[u'name'],
                                                          ctx[u'name']))
    _do_<METHOD>(u'reload', service, ctx)


#####
##### <METHOD> catch action
#####


def _do_<METHOD>(action, service, ctx):
    """Perform action for <METHOD> service.

    <METHOD> does not always use exit code to report status of an
    action.

    :param action: action to perform
    :type action: `str` in [``status``, ``start``, ``restart``,
                  ``stop``, ``reload``]
    :param service: service informations
    :type service: `dict`
    :param ctx: container context
    :type ctx: `dict`
    :raise StartError: if start, restart or reload is not confirmed
    :raise StopError: if stop is not confirmed
    :raise ServiceError: if another :data:`action` is not confirmed

    """
    name = service[u'name']
    cmd = [_SERVICE_CMD, name, action]
    check = [_SERVICE_CMD, name, u'status']
    if action in [u'start', u'restart', u'reload']:
        match_ok = r'^{0} start/running'
        error_string = u'Service {0} in {1} not started: {2}'
        service_exc = StartError
    elif action == u'stop':
        match_ok = r'^{0} stop/waiting'
        error_string = u'Service {0} in {1} not stopped: {2}'
        service_exc = StopError
    elif action == u'status':
        match_ok = r'^{0} stop/waiting'
        error_string = u'Service {0} in {1} is not running: {2}'
        service_exc = ServiceError
    else:
        match_ok = r'.'
        error_string = u'Error: {2}'
        service_exc = ServiceError

    raise NotImplementedError(u'<METHOD> is just a template')

    _exec_cmd(cmd, service, ctx, out=True)
    code, stdout, stderr = _exec_cmd(check, service, ctx, out=True)

    if not (code == 0 or re.match(match_ok.format(name), stdout)):
        # Sometimes, there is no stderr
        output = stderr or stdout
        raise service_exc(error_string.format(name, ctx[u'name'], output))


#####
##### Utilities for <METHOD>
#####


def _build_<METHOD>_filenames(service, ctx):
    """Return the configuration filenames of a <METHOD> service.

    :param service: service informations
    :type service: `dict`
    :param ctx: container context
    :type ctx: `dict`
    :return: configuration filenames
    :rtype: `tuple`

    """
    name = service[u'name']

    raise NotImplementedError(u'<METHOD> is just a template')

    return filenames
