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

# Copyright (C) 2010-2013 by Mike Gabriel <mike.gabriel@das-netzwerkteam.de>
#
# Python X2Go is free software; you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
#
# Python X2Go 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 Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program; if not, write to the
# Free Software Foundation, Inc.,
# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.

"""\
L{X2GoSessionProfiles} class - managing x2goclient session profiles.

L{X2GoSessionProfiles} is a public API class. Use this class in your Python X2Go based 
applications.

"""
__NAME__ = 'x2gosessionprofiles-pylib'

import copy
import types
import re

# Python X2Go modules
from x2go.defaults import X2GO_SESSIONPROFILES_CONFIGFILES as _X2GO_SESSIONPROFILES_CONFIGFILES
from x2go.defaults import X2GO_SESSIONPROFILE_DEFAULTS as _X2GO_SESSIONPROFILE_DEFAULTS
from x2go.defaults import X2GO_DESKTOPSESSIONS as _X2GO_DESKTOPSESSIONS
import x2go.inifiles as inifiles
import x2go.log as log
import x2go.utils as utils
from x2go.x2go_exceptions import X2GoProfileException


class X2GoSessionProfilesFILE(inifiles.X2GoIniFile):

    defaultSessionProfile = _X2GO_SESSIONPROFILE_DEFAULTS
    _non_profile_sections = ('embedded')

    def __init__(self, config_files=_X2GO_SESSIONPROFILES_CONFIGFILES, defaults=None, session_profile_defaults=None, logger=None, loglevel=log.loglevel_DEFAULT):
        """\
        Retrieve X2Go session profiles from a file, typically C{~/.x2goclient/sessions}.

        @param config_files: a list of config file locations, the first file name in this list the user has write access to will be the user configuration file
        @type config_files: C{list}
        @param defaults: not used for this class
        @type defaults: C{dict}
        @param session_profile_defaults: a default session profile
        @type session_profile_defaults: C{dict}
        @param logger: you can pass an L{X2GoLogger} object to the
            L{X2GoSessionProfilesFILE} constructor
        @type logger: L{X2GoLogger} instance
        @param loglevel: if no L{X2GoLogger} object has been supplied a new one will be
            constructed with the given loglevel
        @type loglevel: C{int}

        """
        self.defaultValues = {}
        self._profile_metatypes = {}
        self._cached_profile_ids = []
        self._cached_profile_names = []
        self._profiles_need_profile_id_renewal = []

        if logger is None:
            self.logger = log.X2GoLogger(loglevel=loglevel)
        else:
            self.logger = copy.deepcopy(logger)
        self.logger.tag = __NAME__

        # providing defaults for an X2GoSessionProfiles instance will---in the worst case---override your
        # existing sessions file in your home directory once you write the sessions back to file...
        inifiles.X2GoIniFile.__init__(self, config_files, defaults=defaults, logger=logger, loglevel=loglevel)

        if utils._checkSessionProfileDefaults(session_profile_defaults):
            self.defaultSessionProfile = session_profile_defaults

        self.session_profiles = [ p for p in self.iniConfig.sections() if p not in self._non_profile_sections ]
        for session_profile in self.session_profiles:
            for key, default_value in self.defaultSessionProfile.iteritems():
                if not self.iniConfig.has_option(session_profile, key):
                    self._storeValue(session_profile, key, default_value)
            self.get_profile_metatype(session_profile)

    def __call__(self, profile_id_or_name):
        """\
        Retrieve the session profile configuration for a given session profile ID (or name)

        @param profile_id_or_name: profile ID or profile name
        @type profile_id_or_name: C{str}

        @return: the profile ID's / name's profile configuration
        @rtype: C{dict}

        """
        _profile_id = self.check_profile_id_or_name(self, profile_id_or_name)
        return self.get_profile_config(profile_id=_profile_id)

    def get_profile_metatype(self, profile_id_or_name, force=False):
        """\
        Detect a human readable session profile type from the session profile configuration.

        @param profile_id_or_name: profile ID or profile name
        @type profile_id_or_name: C{str}
        @param force: re-detect the meta type, otherwise use a cached result
        @type force: C{bool}

        @return: the profile ID's / name's meta type
        @rtype: C{str}

        """
        _profile_id = self.check_profile_id_or_name(profile_id_or_name)
        if not self._profile_metatypes.has_key(_profile_id) or force:
            _config = self.get_profile_config(_profile_id)
            if _config['host']:
                if _config['rdpserver'] and _config['command'] == 'RDP':
                    _metatype = 'RDP/proxy'
                elif _config['published']:

                    if _config['command'] in _X2GO_DESKTOPSESSIONS.keys():
                        _metatype = '%s + Published Applications' % _config['command']
                    else:
                        _metatype = 'Published Applications'

                elif _config['rootless']:
                    _metatype = 'Single Applications'
                elif _config['command'] in _X2GO_DESKTOPSESSIONS.keys():
                    _metatype = '%s Desktop' % _config['command']
                elif _config['command'] in _X2GO_DESKTOPSESSIONS.values():
                    _metatype = '%s Desktop' % [ s for s in _X2GO_DESKTOPSESSIONS.keys() if _config['command'] == _X2GO_DESKTOPSESSIONS[s] ][0]
                else:
                    _metatype = 'CUSTOM Desktop'
            else:
                if _config['rdpserver'] and _config['command'] == 'RDP':
                    _metatype = 'RDP/direct'
                else:
                    _metatype = 'not supported'
            self._profile_metatypes[_profile_id] = _metatype
        else:
            return self._profile_metatypes[_profile_id]

    def write(self):

        # then update profile IDs for profiles that have a renamed host attribute...
        for profile_id in self._profiles_need_profile_id_renewal:
            _config = self.get_profile_config(profile_id=profile_id)
            self.iniConfig.remove_section(profile_id)
            try: self._cached_profile_ids.remove(profile_id)
            except ValueError: pass
            self.add_profile(profile_id=None, force_add=True, **_config)
        self._profiles_need_profile_id_renewal = []
        self._cached_profile_ids = []
        self._cached_profile_names = []

        # at last write the profile config as is...
        return inifiles.X2GoIniFile.write(self)


    def get_profile_option_type(self, option):
        """\
        Get the data type for a specific session profile option.

        @param option: the option to get the data type for
        @type option: will be detected by this method

        @return: the data type of C{option}
        @rtype: C{type}

        """
        try:
            return type(self.defaultSessionProfile[option])
        except KeyError:
            return types.StringType

    def get_type(self, section, key):
        """\
        Override the parent class's get_type method due to the special layout of this class.

        @param section: INI file section
        @type section: C{str}
        @param key: key in INI file section
        @type key: C{str}

        @return: the data type of C{key} in C{section}
        @rtype: C{type}

        """
        # we have to handle the get_type method separately...
        return self.get_profile_option_type(key)

    def get_profile_config(self, profile_id_or_name=None, parameter=None, profile_id=None):
        """\
        The configuration options for a single session profile.

        @param profile_id_or_name: either profile ID or profile name is accepted
        @type profile_id_or_name: C{str}
        @param parameter: if specified, only the value for the given parameter is returned
        @type parameter: C{str}
        @param profile_id: profile ID (faster than specifying C{profile_id_or_name})
        @type profile_id: C{str}

        @return: the session profile configuration for the given profile ID (or name)
        @rtype: C{dict}

        """
        _profile_id = profile_id or self.check_profile_id_or_name(profile_id_or_name)
        _profile_config = {}
        for option in self.iniConfig.options(_profile_id):
            _profile_config[option] = self.get(_profile_id, option, key_type=self.get_profile_option_type(option))

        if parameter is not None:
            if parameter in _profile_config.keys():

                value = _profile_config[parameter]

                if parameter == 'export':
                    _strvalue = value.replace(',', ';').strip().strip('"').strip().strip(';').strip()
                    value = {}
                    if _strvalue:
                        _export_paths = _strvalue.split(';')
                        for _path in _export_paths:
                            if not re.match('.*:(0|1)$', _path): _path = '%s:1' % _path
                            _auto_export_path = re.match('.*:1$', _path) and True or False
                            _export_path = ':'.join(_path.split(':')[:-1])
                            value[_export_path] = _auto_export_path

                return value

            else:
                raise X2GoProfileException('no such session profile parameter: %s' % parameter)
        return _profile_config

    def default_profile_config(self):
        """\
        Return a default session profile.

        @return: default session profile
        @rtype: C{dict}

        """
        return copy.deepcopy(self.defaultSessionProfile)

    def has_profile(self, profile_id_or_name):
        """\
        Does a session profile of a given profile ID or profile name exist?

        @param profile_id_or_name: profile ID or profile name
        @type profile_id_or_name: C{str}

        @return: C{True} if there is such a session profile, C{False} otherwise
        @rtype: C{bool}

        """
        try:
            self.check_profile_id_or_name(profile_id_or_name)
            return True
        except X2GoProfileException:
            return False

    @property
    def profile_ids(self):
        """\
        Renders a list of all profile IDs found in the session profile configuration file.

        """
        if not self._cached_profile_ids:
            self._cached_profile_ids = [ s for s in self.iniConfig.sections() if s not in self._non_profile_sections ]
        return self._cached_profile_ids

    def has_profile_id(self, profile_id):
        """\
        Does a session profile of a given profile ID exist? (Faster than L{has_profile()}.)

        @param profile_id: profile ID
        @type profile_id: C{str}

        @return: C{True} if there is such a session profile, C{False} otherwise
        @rtype: C{bool}

        """
        return profile_id in self.profile_ids

    @property
    def profile_names(self):
        """\
        Renders a list of all profile names found in the session profile configuration file.

        """
        if not self._cached_profile_names:
            self._cached_profile_names = [ self.to_profile_name(p) for p in self.profile_ids ]
        return  self._cached_profile_names

    def has_profile_name(self, profile_name):
        """\
        Does a session profile of a given profile name exist? (Faster than L{has_profile()}.)

        @param profile_name: profile name
        @type profile_name: C{str}

        @return: C{True} if there is such a session profile, C{False} otherwise
        @rtype: C{bool}

        """
        return profile_name in self.profile_names

    def to_profile_id(self, profile_name):
        """\
        Convert profile name to profile ID.

        @param profile_name: profile name
        @type profile_name: C{str}

        @return: profile ID
        @rtype: C{str}

        """
        _profile_ids = [ p for p in self.profile_ids if self.to_profile_name(p) == profile_name ]
        if len(_profile_ids) == 1:
            return _profile_ids[0]
        elif len(_profile_ids) == 0:
            return None
        else:
            raise X2GoProfileException('The sessions config file contains multiple session profiles with name: %s' % profile_name)

    def to_profile_name(self, profile_id):
        """\
        Convert profile ID to profile name.

        @param profile_id: profile ID
        @type profile_id: C{str}

        @return: profile name
        @rtype: C{str}

        """
        _profile_config = self.get_profile_config(profile_id=profile_id)
        if _profile_config.has_key('name'):
            return _profile_config['name']
        else:
            return ''

    def add_profile(self, profile_id=None, force_add=False, **kwargs):
        """\
        Add a new session profile.

        @param profile_id: a custom profile ID--if left empty a profile ID will be auto-generated
        @type profile_id: C{str}
        @param kwargs: session profile options for this new session profile
        @type kwargs: C{dict}

        @return: the (auto-generated) profile ID of the new session profile
        @rtype: C{str}

        """
        if profile_id is None or profile_id in self.profile_ids:
            profile_id = utils._genSessionProfileId()

        if 'name' not in kwargs.keys():
            raise X2GoProfileException('session profile parameter ,,name\'\' is missing in method parameters')

        if kwargs['name'] in self.profile_names and not force_add:
            raise X2GoProfileException('a profile of name ,,%s\'\' already exists' % kwargs['name'])

        for key, value in kwargs.items():
            self.update_value(None, key, value, profile_id=profile_id)

        for key, value in self.defaultSessionProfile.items():
            if key in kwargs: continue
            self.update_value(None, key, value, profile_id=profile_id)

        self._cached_profile_ids = []
        self._cached_profile_names = []

        return profile_id

    def delete_profile(self, profile_id_or_name):
        """\
        Delete a session profile from the configuration file.

        @param profile_id_or_name: profile ID or profile name
        @type profile_id_or_name: C{str}

        """
        _profile_id = self.check_profile_id_or_name(profile_id_or_name)
        self.iniConfig.remove_section(_profile_id)
        self.write_user_config = True
        self.write()
        self._cached_profile_ids = []
        self._cached_profile_names = []

    def update_value(self, section, key, value, profile_id=None):
        """\
        Update a value in a session profile.

        @param section: the profile ID
        @type section: C{str}
        @param key: the session profile option of the given profile ID
        @type key: C{str}
        @param value: the value to update the session profile option with
        @type value: any type, depends on the session profile option

        """
        try:
            profile_id = profile_id or self.check_profile_id_or_name(section)
        except X2GoProfileException:
            profile_id = section

        if key == 'name':
            profile_name = value
            current_profile_name = self.get_value(profile_id, key)
            if not profile_name:
                raise X2GoProfileException('profile name for profile id %s may not be empty' % profile_id)
            else:
                self._cached_profile_names = []
                if profile_name != current_profile_name and profile_name in self.profile_names:
                    raise X2GoProfileException('a profile of name ,,%s\'\' already exists' % profile_name)

        if key == 'export' and type(value) == types.DictType:
            _strvalue = '"'
            for folder in value.keys():
                _strvalue += "%s:%s;" % (folder, int(value[folder]))
            _strvalue += '"'
            _strvalue = _strvalue.replace('""', '')
            value = _strvalue

        if key == 'host':
            _config = self.get_profile_config(profile_id=profile_id)
            if _config.has_key('host') and _config['host'] != value:
                self._profiles_need_profile_id_renewal.append(profile_id)

        inifiles.X2GoIniFile.update_value(self, profile_id, key, value)

    def check_profile_id_or_name(self, profile_id_or_name):
        """\
        Detect the profile ID from a given string which maybe profile ID or profile name.

        @param profile_id_or_name: profile ID or profile name
        @type profile_id_or_name: C{str}

        @return: profile ID
        @rtype: C{str}

        @raise X2GoProfileException: if no such session profile exists

        """
        _profile_id = None
        if self.has_profile_name(profile_id_or_name):
            # we were given a sesion profile name...
            _profile_id = self.to_profile_id(profile_id_or_name)
        elif self.has_profile_id(profile_id_or_name):
            # we were given a session profile id...
            _profile_id = profile_id_or_name
        else:
            raise X2GoProfileException('No session profile with id or name ,,%s\'\' exists.' % profile_id_or_name)
        return _profile_id

    def to_session_params(self, profile_id_or_name=None, profile_id=None):
        """\
        Convert session profile options to L{X2GoSession} constructor method parameters.

        @param profile_id_or_name: either profile ID or profile name is accepted
        @type profile_id_or_name: C{str}
        @param profile_id: profile ID (fast than specifying C{profile_id_or_name})
        @type profile_id: C{str}

        @return: a dictionary of L{X2GoSession} constructor method parameters
        @rtype: C{dict}

        """
        _profile_id = profile_id or self.check_profile_id_or_name(profile_id_or_name)
        return utils._convert_SessionProfileOptions_2_SessionParams(self.get_profile_config(_profile_id))

    def get_session_param(self, profile_id_or_name, param):
        """\
        Get a single L{X2GoSession} parameter from a specific session profile.

        @param profile_id_or_name: either profile ID or profile name is accepted
        @type profile_id_or_name: C{str}
        @param param: the parameter name in the L{X2GoSession} constructor method
        @type param: C{str}

        @return: the value of the session profile option represented by C{param}
        @rtype: depends on the session profile option requested

        """
        return self.to_session_params(profile_id_or_name)[param]
