# -*- coding: utf-8 -*-
# Moovida - Home multimedia server
# Copyright (C) 2006-2009 Fluendo Embedded S.L. (www.fluendo.com).
# All rights reserved.
#
# This file is available under one of two license agreements.
#
# This file is licensed under the GPL version 3.
# See "LICENSE.GPL" in the root of this distribution including a special
# exception to use Moovida with Fluendo's plugins.
#
# The GPL part of Moovida is also available under a commercial licensing
# agreement from Fluendo.
# See "LICENSE.Moovida" in the root directory of this distribution package
# for details on that license.
#
# Authors: Philippe Normand <philippe@fluendo.com>
#          Benjamin Kampmann <benjamin@fluendo.com>

"""
Miscellaneaous utilities that don't need their own module because they are
reasonnably small.
"""

import platform
import os, re
import subprocess

from twisted.trial.unittest import SkipTest

import pkg_resources

"""
The name of the environment variable that has to be set to 'True' to run also
functional tests
"""
FUNCTIONAL_TESTS_STR = 'RUN_FUNCTIONAL_TESTS'

# Use the same line separator on all the platforms (yes, including Windows)
LINESEP = '\n'


def env_var_expand(var_name):
    """
    Expand the given environment variable content. If it contains
    other references to environment variables, they are expanded too.

    Supported platforms are win32 and linux.
    
    Example of use::

       >>> env_var_expand('$HOME')
       >>> '/home/phil'

    @raises ValueError: if current system's platform is not windows or linux
    @param var_name:    environment variable
    @type var_name:     string
    @rtype:             string
    """
    platform_type = platform.system().lower()
    if platform_type == 'windows':
        re_env = re.compile(r'%(\w+)%')
        def expander(mo):
            return os.environ.get(mo.group()[1:-1], 'UNKNOWN')
        
    elif platform_type == 'linux':
        re_env = re.compile(r'\$(\w+)')
        
        def expander(mo):
            xpanded = os.environ.get(mo.group()[1:], 'UNKNOWN')
            if xpanded.find('$') > -1:
                xpanded = re_env.sub(expander, xpanded)
            return xpanded
        
    else:
        raise ValueError("Unsupported platform")
    
    expanded = re_env.sub(expander, var_name)
    return expanded

def env_var_explode_list(var_name, default=''):
    """
    Explode a list of values stored in an environment variable as a
    single string. On win32 the item separator is ';' and on other
    platforms it is ':'.

    Example of use::

       >>> env_var_explode_list('$PATH')
       >>> ['/usr/bin','/bin']
    
    @param var_name:    environment variable
    @type var_name:     string
    @keyword default:   value to use if environment variable not found
    @type default:      string
    @rtype:             list of strings
    """
    value = os.environ.get(var_name, default)
    platform_type = platform.system().lower()
    if platform_type == 'windows':
        separator = ';'
    else:
        separator = ':'
    exploded = value.split(separator)
    if exploded == ['']:
        exploded = []
    return exploded
    

def un_camelify(camel_string):
    """
    Convert CamelCase styled strings to lower_cased style.

    @param camel_string: CamelStyled string to convert
    @type camel_string:  string
    @rtype:              string
    """
    if len(camel_string) < 2:
        un_cameled = camel_string.lower()
    else:
        camel_string = camel_string.replace(' ', '')
        un_cameled = camel_string[0].lower()
        for letter in camel_string[1:]:
            if letter.isupper():
                un_cameled += '_%s' % letter.lower()
            else:
                un_cameled += letter
    return un_cameled

# cache the result of the first is_hildon_desktop_running call
_hildon_desktop_running = None

def is_hildon_desktop_running():
    global _hildon_desktop_running

    try:
        import dbus
    except ImportError:
        _hildon_desktop_running = False
        return False

    if _hildon_desktop_running is not None:
        # return the cached value
        return _hildon_desktop_running
    try: 
        bus = dbus.SystemBus()
        driver = bus.get_object(dbus.BUS_DAEMON_NAME,
            dbus.BUS_DAEMON_PATH, dbus.BUS_DAEMON_IFACE)
        iface = dbus.Interface(driver, dbus.BUS_DAEMON_IFACE)
        _hildon_desktop_running = \
            bool(iface.NameHasOwner('org.moblin.SystemDaemon'))
    except dbus.DBusException:
        # it fails so we assume it is not hildon
        _hildon_desktop_running = False
        return False

    return _hildon_desktop_running

def get_distro():
    if platform.system() == 'Windows':
        platform_name = platform.platform()
        # example os_name = Windows, os_version = Vista
        os_name, os_version = platform_name.split('-')[:2]
        distro = ' '.join([os_name, os_version])
    else:
        try:
            output = subprocess.Popen(["lsb_release", "-a"],
                                      stderr=subprocess.STDOUT,
                                      stdout=subprocess.PIPE).communicate()[0]
        except OSError, error:
            # lsb_release program not found, let's use a crappy method.
            known_distros = {"Debian" : "/etc/debian_version",
                             #"RedHat" : "/etc/redhat-release",
                             "SUSE" : "/etc/SUSE-release",
                             "Fedora" : "/etc/fedora-release",
                             "Gentoo" : "/etc/gentoo-release",
                             "Slackware" : "/etc/slackware-version",
                             "Mandrake" : "/etc/mandrake-release",
                             "YellowDog" : "/etc/yellowdog-release",
                             "SUN JDS" : "/etc/sun-release",
                             "UnitedLinux" : "/etc/UnitedLinux-release"
                             }
            distro = 'Unknown'
            for name, location in known_distros.iteritems():
                if os.path.exists(location):
                    distro = name
                    break
        else:
            distro_infos = {}
            lines = output.splitlines()
            for line in lines:
                try:
                    key, value = line.split(':')
                except ValueError:
                    continue
                # strip \t
                value = value[1:]
                distro_infos[key] = value

            distro = distro_infos['Distributor ID']
            # TODO: maybe make use of Release information (Intrepid, Hardy, 2009, etc)

    return distro

def get_os_name():
    return platform.system().lower()

def linux_pidof(program_name):
    """
    Get the Linux process id of the given program name. Because
    multiple processes can exist, we return a list of the pids.

    @rtype: C{list} of C{int}
    @returns: the list of running pids of given program name
    """
    output = subprocess.Popen(["pidof", program_name],
                              stderr=subprocess.STDOUT,
                              stdout=subprocess.PIPE).communicate()[0]
    return [int(pid) for pid in output.split()]

def get_user_desktop_name():
    return "yourmom"

def pkg_resources_copy_dir(resource_spec, resource_dir, dest_dir):
    """
    Copy a directory recursively using pkg_resources.
    """
    assert pkg_resources.resource_isdir(resource_spec, resource_dir)

    # add '' at the end so that working_dir ends with a slash
    working_dir = os.path.join(dest_dir, '')

    pkg_resources.ensure_directory(working_dir)

    for rel_entry in pkg_resources.resource_listdir(resource_spec, resource_dir):
        # rel_entry is relative to resource_dir
        entry = os.path.join(resource_dir, rel_entry)

        if pkg_resources.resource_isdir(resource_spec, entry):
            dest_entry = os.path.join(dest_dir, rel_entry)
            pkg_resources_copy_dir(resource_spec, entry, dest_entry)
            continue

        working_file = os.path.join(working_dir, rel_entry)
        resource_stream = pkg_resources.resource_stream(resource_spec, entry)
        
        out = file(working_file, 'w')
        out.write(resource_stream.read())
        out.close()
        resource_stream.close()

def run_functional_tests_check():
    """
    Check whether functional tests should be run or not. This is done by
    checking whether the L{FUNCTIONAL_TESTS_STR} environment variable is set to
    'True'. If it is not explictly set to True, this method raises a
    L{twisted.trial.unittest.SkipTest} (saying that the functional test is not
    run).

    This method allows functional tests to have an easy way to check if they
    should be run. This simple example checks in the setup (but it could also
    be done inside the method itself)::

        from elisa.core.utils.misc import run_functional_tests_check
        [...]
        class MyFunctionalTestCase(TestCase):

            def setUp(self):
                run_functional_tests_check()
                ...
    """
    msg = "Functional Test skipped. If you want to run it set the environment"\
            " variable '%s' to True" % FUNCTIONAL_TESTS_STR

    if os.environ.get(FUNCTIONAL_TESTS_STR, 'False').lower() != 'true':
        raise SkipTest(msg)

def chainDeferredIgnoringResult(first, chained):
    def cb(result):
        chained.callback(result)
        return result

    def eb(failure):
        chained.errback(failure)
        return failure

    first.addCallbacks(cb, eb)


def mappings_from_text(text):
    """
    Unserialise mappings from a string L{text}. Mappings are lists of 2-uplets.

    Example::

        key1 = value1
        key2 = value2a
        key2 = value2b

    is deserialised into::

        [("key1", "value1"), ("key2", "value2a"), ("key2", "value2b")]
    """
    mappings = []
    if text != None:
        for line in text.splitlines():
            m = tuple([elt.strip() for elt in line.split('=')])
            if len(m) != 2: 
                raise ValueError
            mappings.append(m)

    return mappings

def text_from_mappings(mappings):
    """
    Takes a L{mappings} and return a serialised form. Mappings are list of
    2-uplets.

    Example::

        [("key1", "value1"), ("key2", "value2a"), ("key2", "value2b")]

    is serialised into::

        key1 = value1
        key2 = value2a
        key2 = value2b
    """
    text = ""
    if mappings != None:
        for key, value in mappings:
            text += "%s = %s%s" % (key, value, LINESEP)

    return text

def read_mappings(iter_lines):
    """
    Setuptools compliant deserialiser for mappings.
    """
    text = LINESEP.join(iter_lines)
    return mappings_from_text(text)

