# This file is part of CloudControl.
#
# CloudControl is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# CloudControl is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with CloudControl.  If not, see <http://www.gnu.org/licenses/>.


import socket
import logging
import logging.config
from StringIO import StringIO
from itertools import ifilterfalse
from ConfigParser import SafeConfigParser, NoOptionError, NoSectionError

from cloudcontrol.common.client.exc import ConfigError


logger = logging.getLogger(__name__)


class _ConfigProxy(object):
    """Simple ConfigParser proxy that provide default values for get* methods.

    """
    def __init__(self, config_parser):
        self.config = config_parser

    @staticmethod
    def config_error(msg):
        logger.error(msg)
        raise ConfigError(msg)

    def __getattr__(self, name):
        if name.startswith('get'):
            def getter(section, option, *default):
                assert not default or len(default) == 1
                try:
                    return getattr(self.config, name)(section, option)
                except NoSectionError:
                    self.config_error('Section "%s" is not present in config'
                                      ' file' % section)
                except NoOptionError:
                    if default:
                        return default[0]

                    self.config_error(
                        'Attribute "%s" not specified in config'
                        ' file (section "%s")' % (option, section))
                except ValueError:
                    self.config_error(
                        'Configuration attribute "%s" value is invalid'
                        ' (section "%s")' % (option, section))

            return getter

        # else
        return getattr(self.config, name)


class NodeConfigParser(object):
    """ConfigParser for ccnode config file."""
    def __init__(self, file_path):
        config = _ConfigProxy(SafeConfigParser())
        config.read(file_path)

        # ccserver settings
        self.server_host = config.get('node', 'address')
        self.server_port = config.getint('node', 'port', 1984)
        self.server_user = config.get('node', 'login')
        self.server_passwd = config.get('node', 'password')

        # node settings
        try:
            self.logging_level = config.getint('node', 'verbosity', 0)
        except ConfigError:
            try:
                self.logging_level = dict(
                    debug=3,
                    info=2,
                    warning=1,
                    error=0,
                )[config.get('node', 'verbosity')]
            except KeyError:
                _ConfigProxy.config_error(
                    'Configuration attribute "verbosity"'
                    ' is invalid (section "node")')

        self.debug = config.getboolean('node', 'debug', False)
        self.logging_output = 'console' if self.debug else 'syslog'

        # path settings
        self.jobs_store_path = config.get('node', 'jobs_store_path',
                                          '/var/lib/cc-node/jobs')
        # plugins persistance
        self.plugins_store_path = config.get('node', 'plugins_store_path',
                                             '/var/lib/cc-node/plugins')

        # Libvirt URI to export to cc-server (vir_uri tag)
        default_libvirt_uri = 'qemu+tcp://%s/system' % socket.gethostbyname(socket.gethostname())
        self.libvirt_uri = config.get('node', 'libvirt_uri', default_libvirt_uri)

        # Path to define script
        default_define_script = 'hkvm-define'
        self.define_script = config.get('node', 'define_script', default_define_script)
        default_rescue_script = 'hkvm-rescue'
        self.rescue_script = config.get('node', 'rescue_script', default_rescue_script)
        default_install_script = 'hkvm-install'
        self.install_script = config.get('node', 'install_script', default_install_script)
        default_mode_script = 'hkvm-mode'
        self.mode_script = config.get('node', 'mode_script', default_mode_script)
        default_vlan_script = 'hkvm-vlan'
        self.vlan_script = config.get('node', 'vlan_script', default_vlan_script)
        default_attach_script = 'hkvm-attach'
        self.attach_script = config.get('node', 'attach_script', default_attach_script)
        default_detach_script = 'hkvm-detach'
        self.detach_script = config.get('node', 'detach_script', default_detach_script)
        default_boot_order_script = 'hkvm-boot-order'
        self.boot_order_script = config.get('node', 'boot_order_script', default_boot_order_script)

        # RPC handler ACLs
        acl_section_name = 'node_handler'
        if config.has_section(acl_section_name):
            self.forbidden_handlers = set(ifilterfalse(
                lambda o: config.getboolean(acl_section_name, o, True),
                config.options(acl_section_name),
            ))
        else:
            self.forbidden_handlers = set()
        # backward compatibility
        command_execution_handlers = set(('shutdown', 'execute'))
        if config.getboolean('node', 'command_execution', None) == False:
            self.forbidden_handlers |= command_execution_handlers
        else:
            self.forbidden_handlers -= command_execution_handlers

        # deprecated options
        for o in ('detect_hypervisor', 'force_xen'):
            if config.get('node', o, None) is not None:
                logger.warning('%s config option is not supported anymore', o)


def configure_logging(level, output):
    level = {
        0: 'ERROR', 1: 'WARNING',
        2: 'INFO', 3: 'DEBUG',
    }[level]
    output = dict(
        console="""
[handler_output]
class=StreamHandler
formatter=simpleFormatter
args=(sys.stderr,)

[formatter_simpleFormatter]
format=cc-node - %(asctime)s - %(name)s - %(levelname)s - %(message)s
""",
        syslog="""
[handler_output]
class=handlers.SysLogHandler
formatter=simpleFormatter
args=('/dev/log', handlers.SysLogHandler.LOG_DAEMON)

[formatter_simpleFormatter]
class=cloudcontrol.common.helpers.formatter.EncodingFormatter
format=cc-node - %(name)s - %(levelname)s - %(message)s
""",
    )[output]

    # create config parser for logging configuration
    logging.config.fileConfig(StringIO("""
[loggers]
keys=root,ccnode,cccommon,sjrpc

[handlers]
keys=output

[formatters]
keys=simpleFormatter

[logger_root]
level=ERROR
handlers=output

[logger_ccnode]
level=%(level)s
handlers=
qualname=cloudcontrol.node

[logger_cccommon]
level=%(level)s
handlers=
qualname=cloudcontrol.common

[logger_sjrpc]
level=ERROR
handlers=
qualname=sjrpc

%(output)s
    """ % dict(level=level, output=output)))