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

#########################################################################
# pyeole.service - manage EOLE services
# Copyright © 2012-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
#########################################################################
import sys, termios

from os.path import join
from os import unlink

from pyeole import ihm
from pyeole.translation.i18n import _
from pyeole.process import creole_system_code, creole_system_out, system_progress_out
from pyeole.service import module
from pyeole.encode import normalize
# Base exception
from pyeole.service.error import ServiceError
# Configuration
from pyeole.service.error import DisabledError
#from pyeole.service.error import ConfigureError
from pyeole.service.error import UnknownServiceError

import logging

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

_ENCODING = 'utf-8'

class CommandController(object):

    def __init__(self, action, display):
        self.action = action
        self.display = display


def is_supported(method):
    def is_sup(*args, **kwargs):
        if not 'cmd_'+method.__name__ in dir(args[0]):
            msg = method.__name__ + ' not supported for ' + args[0].module_name
            log.debug(msg)
        else:
            return method(*args,**kwargs)
    return is_sup


def _in_container(container):
    """Return the string to log for container localisation

    :param container: container context
    :type container: `dict`
    :return: the container part of log message
    :rtype: `str`

    """
    if container.get(u'ip', u'127.0.0.1') == u'127.0.0.1':
        return ''
    else:
        return ' in {0}'.format(container['name'])


class Command(object):

    module_name = None
    # fake_restart: override to True if module uses stop/start instead of restart
    fake_restart = False
    # multiple: override to True if module accept multi services for an action
    multiple = False

    def __init__(self, display, size=6):
        self.display = display
        self.term_cols = ihm.get_current_column()
        self.res_size = size
        try:
            termios.tcgetattr(sys.stdout.fileno())
        except:
            self.allow_color = False
        else:
            self.allow_color = True

    def execute(self, service, ctx):
        pass

    def _getname(self, service):
        if isinstance(service , list):
            name = []
            for srv in service:
                name.append(srv[u'name'])
            name = ' '.join(name)
        else:
            name = service[u'name']
        return name

    @is_supported
    def check_service(self, service, ctx):
        """Verify if a network 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 = self._getname(service)
        log.debug(u'Check {0} service {1}{2}'.format(self.module_name,
                                                         name,
                                                         _in_container(ctx)))
        self.cmd_check_service(service, ctx)

    def configure_service(self, service, ctx):
        """Configure a network 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`

        """
        name = self._getname(service)
        log.debug(u'Configure {0} service {1}{2}'.format(self.module_name,
                                                             name,
                                                             _in_container(ctx)))
        if service.get(u'activate', False):
            self.enable_service(service, ctx)
        else:
            self.disable_service(service, ctx)

    def apply_service(self, service, ctx):
        """Configure and start/stop a network 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`

        """
        name = self._getname(service)
        log.debug(u'Apply {0} service {1}{2}'.format(self.module_name,
                                                         name,
                                                         _in_container(ctx)))
        if service.get(u'activate', False):
            self.start_service(service, ctx)
        else:
            self.stop_service(service, ctx)

    @is_supported
    def enable_service(self, service, ctx):
        """Enable a network service.

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

        """
        if self.module_name != 'Apache':
            name = self._getname(service)
            msg = u'Enable {0} service {1}{2}'.format(self.module_name,
                                                          name,
                                                          _in_container(ctx))
            self.message_print(msg, True)

        self.cmd_enable_service(service, ctx)

    @is_supported
    def disable_service(self, service, ctx):
        """Disable a network service.

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

        """
        if self.module_name != 'Apache':
            name = self._getname(service)
            msg = u'Disable {0} service {1}{2}'.format(self.module_name,
                                                       name,
                                                       _in_container(ctx))
            self.message_print(msg, True)

        self.cmd_disable_service(service, ctx)

    @is_supported
    def status_service(self, service, ctx, display_status=None):
        """Check status of a network service.

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

        """
        msg = u'Get status of {0} service {1}{2}'
        name = self._getname(service)
        log.debug(msg.format(self.module_name, name,
                             _in_container(ctx)))
        try:
            self.check_service(service, ctx)
            self.cmd_status_service(service, ctx)
            msg = u'Service {0}{1} is running'.format(name,
                                                          _in_container(ctx))
            self.message_print(msg, display_status == None)
            if display_status == 'stopped':
                self.message_print_result(1)
            elif display_status == 'started':
                self.message_print_result(0)
        except UnknownServiceError:
            msg = u'Service {0}{1} is not installed'
            name = self._getname(service)
            log.error(msg.format(name, _in_container(ctx)))
        #except ConfigureError, err:
        #    msg = u'Service {0} in {1} is miss-configured: {2}'
        #    log.error(msg.format(service[u'name'], _in_container(ctx), err))
        #except DisabledError:
        #    msg = u'Service {0} in {1} is disabled'.format(service[u'name'],
        #                                                   _in_container(ctx))
        #    self.message_print(msg, True)
        except ServiceError as err:
            name = self._getname(service)
            msg = u'Service {0}{1} is not running'.format(name,
                                                              _in_container(ctx))
            self.message_print(msg, display_status == None)
            if display_status == 'stopped':
                self.message_print_result(0)
            elif display_status == 'started':
                self.message_print_result(1)
            return {'code': 1, 'msg': msg}
        return {'code': 0, 'msg': msg}

    @is_supported
    def start_service(self, service, ctx):
        """Start a network service.

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

        """
        if not isinstance(service, list):
            name = self._getname(service)
            msg = u'Start {0} service {1}{2}'.format(self.module_name,
                                                     name,
                                                     _in_container(ctx))
            self.message_print(msg)
            message = None
        else:
            message = u'Start {0} services{1}'.format(self.module_name, _in_container(ctx))
            msg = message
        try:
            self.cmd_start_service(service, ctx, message)
        except ServiceError as err:
            if not isinstance(service, list):
                self.message_print_result(1)
            self.message_print_error(str(err), True)
            return {'code': 1, 'msg': str(err)}

        if not isinstance(service, list):
            self.message_print_result(0)
        return {'code': 0, 'msg': msg}

    @is_supported
    def stop_service(self, service, ctx):
        """Stop a network service.

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

        """
        if not isinstance(service, list):
            name = self._getname(service)
            msg = u'Stop {0} service {1}{2}'.format(self.module_name,
                                                    name,
                                                    _in_container(ctx))
            self.message_print(msg)
            message = None
        else:
            message = u'Stop {0} services{1}'.format(self.module_name, _in_container(ctx))
            msg = message
        try:
            self.cmd_stop_service(service, ctx, message=message)
        except ServiceError as err:
            if not isinstance(service, list):
                self.message_print_result(1)
            return {'code': 1, 'msg': str(err)}

        if not isinstance(service, list):
            self.message_print_result(0)
        return {'code': 0, 'msg': msg}

    @is_supported
    def restart_service(self, service, ctx):
        """Restart a network service.

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

        """
        if not isinstance(service, list):
            name = self._getname(service)
            msg = u'Restart {0} service {1}{2}'.format(self.module_name,
                                                       name,
                                                       _in_container(ctx))
            self.message_print(msg, newline = self.fake_restart)
            message = None
        else:
            message = u'Restart {0} services{1}'.format(self.module_name, _in_container(ctx))
            msg = message
        try:
            self.cmd_restart_service(service, ctx, message)
        except ServiceError as err:
            if not self.fake_restart:
                self.message_print_result(1)
            return {'code': 1, 'msg': str(err)}
        if not self.fake_restart:
            self.message_print_result(0)
        return {'code': 0, 'msg': msg}

    @is_supported
    def reload_service(self, service, ctx):
        """Reload a network service.

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

        """
        if not isinstance(service, list):
            name = self._getname(service)
            msg = u'Reload {0} service {1}{2}'.format(self.module_name,
                                                      name,
                                                      _in_container(ctx))
            self.message_print(msg)
            message = None
        else:
            message = u'Reload {0} services{1}'.format(self.module_name, _in_container(ctx))
            msg = message
        try:
            self.cmd_reload_service(service, ctx, message)
        except ServiceError as err:
            self.message_print_result(1)
            return {'code': 1, 'msg': str(err)}
        self.message_print_result(0)
        return {'code': 0, 'msg': msg}


    def _make_terminfo(self, container_path):
        """Environment informations are not set in containers when using ssh.

        This method configure terminfo for service line (with ``[ OK ]`` at
        the end).

        :param container_path: path to the root of the container
        :type container_path: `str`
        :rtype: `tuple`

        """
        terminfo_dir = join(u'/', container_path, u'etc/terminfo/')
        ti_file = u'/tmp/eole.ti'
        try:
            cols = self.term_cols
            file_h = open(ti_file, 'w')
            ctt = 'eole,\n\txenl,\n\thpa=\\E[%i%p1%dG,'\
                  '\n\tsetaf=\\E[3%p1%dm,\n\tcols#{0},'\
                  '\n\top=\\E[39;49m,\n'.format(cols)
            file_h.write(ctt)
            file_h.close()
            ticcmd = [u'/usr/bin/tic', u'-o', terminfo_dir, ti_file]
            creole_system_code(ticcmd)
            unlink(ti_file)
            return True, {U'TERM': u'eole'}
        except Exception:
            return False, None

    def message_print(self, msg, newline=False):
        """ Print a message
        """
        if self.display in ["log", "LOG", "L", "both", "BOTH", "B"]:
            log.info(msg)
        if self.display in ["console", "CONSOLE", "C", "both", "BOTH", "B"]:
            if newline:
                print(msg)
            else:
                cols = self.term_cols
                cols = cols - (self.res_size + 2)
                ihm.print_no_cr(msg, cols)

    def message_print_result(self, status):
        result = {}
        for typ_msg in ('color', 'no_color'):
            if typ_msg == 'color':
                #self.allow_color:
                OKGREEN = '\033[92m'
                NOCOLOR = '\033[0m'
                KORED = '\33[31m'
            else:
                OKGREEN = ''
                NOCOLOR = ''
                KORED = ''
            size = self.res_size
            msg = ""
            if status == 0:
                msg = OKGREEN + "OK" + NOCOLOR
                size = len(msg) + 4
            elif status == 1:
                msg = KORED + "KO" + NOCOLOR
                size = len(msg) + 4
            result[typ_msg] = (msg, size)
        if self.display in ["log", "LOG", "L", "both", "BOTH", "B"]:
            log.info(result['no_color'][0])
        if self.display in ["console", "CONSOLE", "C", "both", "BOTH", "B"]:
            if self.allow_color:
                msg, size = result['color']
            else:
                msg, size = result['no_color']
            ihm.print_no_cr('[{:^{sz}}]\n'.format(msg, sz=size), size + 2)

    def message_print_error(self, msg, newline=False):
        self.message_print(msg, newline)

    def _exec_cmd(self, cmd, service, ctx, out=True, progress=False, message=None):
        """Execute an action for a service

        This is a wrapper to :func:`creole_system_out` and
        :func:`creole_system_code` which prepare the environment.

        If :data:`out` is ``True``, use :func:`creole_system_out` to
        return ``stdout`` and ``stderr``.

        Otherwise, set ``stdout`` and ``stderr`` to ``None`` and use
        :func:`creole_system_code`.

        :param action: action to perform
        :type action: `str`
        :param service: service informations
        :type service: `dict`
        :param ctx: container context
        :type ctx: `dict`
        :param out: return standard output and error
        :type out: `bool`
        :return: exit code, standard output and error
        :rtype: `tuple`

        """
        code = 0
        stdout = None
        stderr = None

        if out:
            if progress:
                code, stdout, stderr = system_progress_out(cmd, message=message,
                                                           container=ctx)
            else:
                code, stdout, stderr = creole_system_out(cmd,
                                                         container=ctx)
            if sys.version_info[0] < 3 and not isinstance(stdout, unicode):
                stdout = stdout.decode(_ENCODING)
                stderr = stderr.decode(_ENCODING)
        else:
            code = creole_system_code(cmd, container=ctx)

        return code, stdout, stderr


def command_factory(display):
    commands = {}
    for mod_name in module.get_modules():
        commands[mod_name] = getattr(module, mod_name).Service(display)
    for mod_name in ['service', 'upstart']:
        commands[mod_name] = getattr(module, 'systemd').Service(display)
    return commands


def get_commands(display):
    return command_factory(display)
