#!/usr/bin/python3
# -*- coding: utf-8; -*-
"""
Script to load default values into Pulse2 database.
"""
#
# (c) 2011-2012 Mandriva, http://www.mandriva.com/
#
# Author(s):
#   Ladislav Cabelka <lcabelka@mandriva.com>
#
# This file is part of Pulse 2, http://pulse2.mandriva.org
#
# Pulse 2 is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# Pulse 2 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Pulse 2.  If not, see <http://www.gnu.org/licenses/>.

import os
import sys
import logging

from xmlrpc.client import Fault, ProtocolError
from socket import error as SocketError
from optparse import OptionParser, OptionGroup
from configparser import ConfigParser

from mmc.client.sync import Proxy
from mmc.site import mmcconfdir
from mmc.core.log import ColoredFormatter

# Name of module
MODULE_NAME = os.path.splitext(os.path.basename(__file__))[0]

# Logger create
log = logging.getLogger(MODULE_NAME)


class ConfigReader :
    """Read and parse config files"""
    def __init__(self):
        base_ini = os.path.join(mmcconfdir,
                                "plugins",
                                "base.ini")
        pkg_server_ini = os.path.join(mmcconfdir,
                                      "pulse2",
                                      "package-server",
                                      "package-server.ini")

        self._base_config = self.get_config(base_ini)
        self._pkg_server_config = self.get_config(pkg_server_ini)


    @classmethod
    def get_config(cls, inifile):
        """
        Get the configuration from config file

        @param inifile: path to config file
        @type inifile: string

        @return: ConfigParser.ConfigParser instance
        """
        log.debug("Load config file %s" % inifile)
        if not os.path.exists(inifile) :
            log.error("Error while reading the config file: Not found.")
            sys.exit(2)

        config = ConfigParser()
        config.readfp(open(inifile))
        if os.path.isfile(inifile + '.local'):
            config.readfp(open(inifile + '.local','r'))

        return config

    @property
    def pkg_server_config (self):
        """
        Get the configuration of package server

        @return: ConfigParser.ConfigParser instance
        """
        return self._pkg_server_config

    @property
    def base_config(self):
        """
        Get the configuration from base.ini

        @return: ConfigParser.ConfigParser instance
        """
        return self._base_config


class MMCProxy :
    """ Provider to connect at mmc-agent """
    def __init__(self):

        config = ConfigReader()

        self.pkg_server_config = config.pkg_server_config
        self.base_config = config.base_config

        self._url = None
        self._proxy = None

        self._build_url()
        self._build_proxy()

    def _build_url(self):
        """ URL building for XML-RPC proxy """

        if not self.pkg_server_config.has_section("mmc_agent") :
            log.error("Error while reading the config file: Section 'mmc_agent' not exists")
            sys.exit(2)

        username = self.pkg_server_config.get("mmc_agent", "username")
        host = self.pkg_server_config.get("mmc_agent", "host")
        password = self.pkg_server_config.get("mmc_agent", "password")
        port = self.pkg_server_config.get("mmc_agent", "port")

        log.debug("Building the connection URL at mmc-agent")
        self._url = 'https://%s:%s@%s:%s' % (username, password, host, port)

    def _get_ldap_password(self):
        """
        Password for LDAP authentification

        @return: string
        """

        if not self.base_config.has_section("ldap") :
            log.error("Error while reading the config file: Section 'ldap'")
            sys.exit(2)

        return self.base_config.get("ldap","password")

    def _build_proxy (self):
        """ Builds the XML-RPC proxy to MMC agent. """
        try :
            self._proxy = Proxy(self._url)

            log.debug("LDAP authentification")

            self._proxy.base.ldapAuth('root', self._get_ldap_password())

            log.debug("Create a mmc-agent proxy")

        except Exception, err :
            log.error("Error while connecting to mmc-agent : %s" % err)
            sys.exit(2)


    @property
    def proxy (self):
        """
        Get the XML-RPC proxy to MMC agent.

        @return: mmc.client.sync.Proxy
        """
        return self._proxy



class RPCClient :
    """
    XML-RPC Handler to execute remote functions.

    To add a new function, use :
    option_check() -> action_resolve()-> rpc_execute()
    """

    def __init__(self, options) :
        """
        @param options: parsed options from command line
        @type options: options container of OptionParser
        """
        self.proxy = None

        self.options = options
        self.options_check()
        self._set_proxy()
        self.action_resolve()

    def _set_proxy(self):
        """ Set the proxy to connect at MMC agent """

        mmc_agent = MMCProxy()
        self.proxy = mmc_agent.proxy

    @classmethod
    def rpc_execute(cls, fnc, *args, **kwargs) :
        """
        Remote execution handler

        @param fnc: RPC function to call
        @type fnc: function type

        @param args: Arguments of called function
        @type args: *args type (list)

        @param kwargs: Arguments of called function
        @type kwargs: **kwargs type (dict)
        """
        log.debug("Execute remote function")

        try :
            ret_msg = fnc(*args, **kwargs)
        except Fault, err :
            log.error(err)
            sys.exit(2)

        except SocketError, err :
            err_code, err_msg = err.args
            log.error("%s: %s" % (err_code, err_msg))
            log.error("Service 'mmc-agent' isn't running ?")
            sys.exit(2)

        except ProtocolError, err :
            log.error(err)
            sys.exit(2)

        return ret_msg

    def options_check(self) :
        """ Option validation and test of options coexistence."""
        raise NotImplementedError

    def action_resolve(self) :
        """ Resolve to execute a remote function """
        raise NotImplementedError


class LoadDefaults (RPCClient):
    """ Database defaults loader """

    def options_check(self):
        """ Option validation and test of options coexistence."""
        # -- link imaging server options check
        opt = self.options

        if opt.default_menu or opt.link_img_server \
               or opt.unlink_img_server :
            log.debug("Options validated")
            return

        if (opt.loc_id or opt.is_id or opt.loc_name) \
            and not (opt.link_img_server or opt.default_menu) :

            log.error("Incorrect combination of options.")
            log.error("Type '%s --help' for more information." % MODULE_NAME)
            sys.exit(2)

        if not opt.link_img_server or opt.default_menu :
            log.error("Options required.")
            log.error("Type '%s --help' for more information." % MODULE_NAME)
            sys.exit(2)

        log.debug("Options validated")

    def action_resolve(self):
        """ Resolve to execute a remote function """
        # -- link imaging server execute
        if self.options.link_img_server :
            self.linkImagingServerToLocation()
        # -- unlink imaging server execute
        if self.options.unlink_img_server :
            self.unlinkImagingServerToLocation()
        # -- generating default menu
        if self.options.default_menu :
            self.synchroLocation()


    def linkImagingServerToLocation (self):
        """ Wrapper for the remote function """
        is_id = self.options.is_id or "UUID1"
        loc_id = self.options.loc_id or "UUID1"
        loc_name = self.options.loc_name or "Local Imaging Server"

        # Function to call
        fnc = self.proxy.imaging.linkImagingServerToLocation
        args = [is_id, loc_id, loc_name]

        success, msg = self.rpc_execute(fnc, *args)
        if success :
            log.info("Imaging server successfully associated.")
        else :
            log.error(msg)

    def unlinkImagingServerToLocation (self):
        """ Wrapper for the remote function """
        is_id = self.options.is_id or "UUID1"
        loc_id = self.options.loc_id or "UUID1"

        # Function to call
        fnc = self.proxy.imaging.unlinkImagingServerToLocation
        args = [is_id, loc_id]

        success1, success2 = self.rpc_execute(fnc, *args)
        if not success1 :
            log.warn("Failed to unassociate the imaging server to entity")
        if not success2 :
            log.warn("Failed to unassociate the package server to entity")
        if success1 and success2 :
            log.info("Imaging server successfully unassociated.")

    def synchroLocation (self):
        """ Wrapper for the remote function """
        loc_id = self.options.loc_id or "UUID1"

        # Function to call
        fnc = self.proxy.imaging.synchroLocation
        args = [loc_id]

        success = self.rpc_execute(fnc, *args)
        if success :
            log.info("Menu successfully associated.")

def run(argv) :
    """ Run the script """
    # Parse command line options
    usage = "pulse2-load-defaults options"
    parser = OptionParser(usage=usage)

    parser.add_option("-d", "--debug", action="store_true",
                      dest="debug", default=False,
                      help="Print debug messages")
    # -- Imaging group
    group = OptionGroup(parser, "Imaging Options",
                        "Options to link an imaging sever to an entity")
    group.add_option("--link-server",
                      action="store_true",
                      dest="link_img_server", default=False,
                      help="Link imagging server to location")
    group.add_option("--unlink-server",
                      action="store_true",
                      dest="unlink_img_server", default=False,
                      help="Unlink imagging server to location")
    group.add_option("--default-menu",
                      action="store_true",
                      dest="default_menu", default=False,
                      help="Generate Menu")
    group.add_option("--imaging-id", dest="is_id",
                      help="Imaging server UUID")
    group.add_option("--loc-id", dest="loc_id",
                      help="Location UUID")
    group.add_option("--loc-name", dest="loc_name",
                      help="Location name")
    parser.add_option_group(group)

    (options, args) = parser.parse_args(argv)

    if options.debug:
        level = logging.DEBUG
    else:
        level = logging.INFO

    # Init logger
    handler = logging.StreamHandler()
    handler.setFormatter(ColoredFormatter("%(levelname)-18s %(message)s"))
    handler.setLevel(level)
    log.addHandler(handler)
    log.setLevel(level)

    # Load defaults into database
    LoadDefaults(options)

if __name__ == "__main__" :
    run(sys.argv)
