# -*- 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.

"""
Functions used to extend setuptools and add elisa specific metadata
when distributing plugins.
"""

__maintainer__ = 'Philippe Normand <philippe@fluendo.com>'

import os.path
import sys
import types

from elisa.core.utils import i18n, misc

import setuptools
from setuptools import Distribution as SetuptoolsDistribution
from setuptools.command.bdist_egg import bdist_egg
from setuptools.command import test
from setuptools.command import sdist
from setuptools.command import build_py

from distutils.errors import DistutilsSetupError, DistutilsOptionError
from distutils.core import Command
from distutils.command import clean

from twisted.scripts import trial
import pkg_resources

PACKAGE_DATA = {'': ['*.conf', '*.png', '*.mo', '*.po', '*.xml', '*.lirc']}



class Distribution(SetuptoolsDistribution):

    def find_config_files(self):
        files = SetuptoolsDistribution.find_config_files(self)

        # look for setup.cfg in the directory containing the setup.py
        # (not current directory as in default's distutils
        # Distribution)
        setup_py = sys.argv[0]
        project_dir = os.path.dirname(setup_py)
        setup_cfg = os.path.join(project_dir, 'setup.cfg')
        if os.path.exists(setup_cfg):
            files.append(setup_cfg)
        return files

class SDist(sdist.sdist):

    def make_distribution(self):
        files = self.filelist.files[:]
        for filename in files:
            dummy, ext = os.path.splitext(filename)
            ext = ext[1:].lower()
            if ext == 'mo':
                self.filelist.files.remove(filename)
                self.announce("Removed mo file: %r" % filename)
        sdist.sdist.make_distribution(self)

    def make_release_tree(self, base_dir, files):
        if self.distribution.package_dir:
            dirs = self.distribution.package_dir.values()
            egg_name = self.distribution.get_name().replace('-','_')
            stripped_filelist = []

            for dirname in dirs:
                for filename in files:
                    to_include = os.path.abspath(filename).startswith(dirname)
                    is_egg_info = filename.find(egg_name) > -1
                    if to_include or is_egg_info:
                        if filename not in stripped_filelist:
                            stripped_filelist.append(filename)

            files = stripped_filelist
        
        sdist.sdist.make_release_tree(self, base_dir, files)

        script_name = os.path.basename(sys.argv[0])
        self.copy_file(sys.argv[0], os.path.join(base_dir, script_name))

        # if we're building a bundle we need to manually copy some files
        dist_name = self.distribution.get_name()
        if self.distribution.package_dir and 'elisa-plugins' in dist_name:
            
            elisa_init = os.path.join(self.distribution.package_dir['elisa'],
                                      '__init__.py')
            dest_dir = os.path.join(base_dir, 'elisa-plugins')
            self.copy_file(elisa_init, os.path.join(dest_dir, 'elisa',
                                                    '__init__.py'))
            self.copy_file(elisa_init, os.path.join(dest_dir, 'elisa',
                                                    'plugins',
                                                    '__init__.py'))


class LocaleBuildPy(build_py.build_py):
    """
    create .mo files from .po locales if needed
    """
 
    def run(self):
        pobuild = build_po(self.distribution)
        pobuild.finalize_options()
        pobuild.run()
        build_py.build_py.run(self)
        
class TrialTest(test.test):
    """
    Twisted Trial setuptools command
    """

    user_options = test.test.user_options + [
        ('coverage','c', "Report coverage data"),
    ]

    def initialize_options(self):
        test.test.initialize_options(self)
        self.coverage = None

    def finalize_options(self):
        if self.test_suite is None:
            if self.test_module is None:
                self.test_suite = self.distribution.test_suite
            else:
                self.test_suite = self.test_module
        elif self.test_module:
            raise DistutilsOptionError(
                "You may specify a module or a suite, but not both"
            )

        self.test_args = self.test_suite

    def run_tests(self):
        config = trial.Options()
        config.parseOptions()

        args = self.test_args
        if type(args) == str:
            args = [args,]

        config['tests'] = args

        if self.coverage:
            config.opt_coverage()

        trial._initialDebugSetup(config)
        trialRunner = trial._makeRunner(config)
        suite = trial._getSuite(config)

        # run the tests
        if config['until-failure']:
            test_result = trialRunner.runUntilFailure(suite)
        else:
            test_result = trialRunner.run(suite)

        # write coverage data
        if config.tracer:
            sys.settrace(None)
            results = config.tracer.results()
            results.write_results(show_missing=1, summary=False,
                                  coverdir=config.coverdir)

class Clean(clean.clean):
    def run(self):
        clean.clean.run(self)

        # Removing .pyc, .pyo and .cover files
        for root, dirs, files in os.walk('.', topdown=False):
            for name in files:
                if name.endswith('.pyc') or name.endswith('.pyo') or \
                       name.endswith('.cover'):
                    os.remove(os.path.join(root, name))


CMD_CLASS = {'test': TrialTest, #'build_py': LocaleBuildPy,
             'clean': Clean, 'sdist': SDist}


def write_dict(cmd, basename, filename, force=False):
    """
    DOCME
    """
    argname = os.path.splitext(basename)[0]
    arg_value = getattr(cmd.distribution, argname, None)
    text_value = ""
    if arg_value is not None:
        for key, value in arg_value.iteritems():
            
            text_value += "%s = %s\n" % (key, value)
    cmd.write_or_delete_file(argname, filename, text_value, force)


def write_mappings(cmd, basename, filename, force=False):
    """
    Setuptools compliant serialiser for mappings.
    """
    argname = os.path.splitext(basename)[0]
    arg_value = getattr(cmd.distribution, argname, None)
    text_value = misc.text_from_mappings(arg_value)
    cmd.write_or_delete_file(argname, filename, text_value, force)


def _all(iterable):
    try:
        return all(iterable)
    except NameError:
        # The all builtin is new in Python 2.5
        # (see http://www.python.org/doc/2.5.2/lib/built-in-funcs.html#l2h-9)
        for element in iterable:
            if not element:
                return False
        return True


def assert_list_of_string_tuples_or_none(dist, attr, value):
    """Verify that value is a list of tuples of string"""
    if type(value) is types.NoneType:
        return
    if type(value) is list:
        for item in value:
            valid = type(item) is tuple and \
                    _all([type(e) is str for e in item])
            if not valid:
                raise DistutilsSetupError("%r must be a list of tuples of " \
                                          "strings or None. Got %r" \
                                          % (attr, value))


def assert_dict_or_none(dist, attr, value):
    """Verify that value is a dictionary"""
    if type(value) not in (dict, types.NoneType):
        raise DistutilsSetupError("%r must be a dictionary or None value. "\
                                  "Got %r" % (attr,value))

def find_packages_internal(path='.'):
    path = os.path.abspath(path)

    if os.path.isdir(os.path.join(path, 'elisa')):
        return setuptools.find_packages(path)

    dirname = os.path.split(path)[1]
    package = 'elisa.plugins.%s' % dirname
    packages = [package]

    packages += ['%s.%s' % (package, p) 
            for p in setuptools.find_packages(path)]
   
    return packages

def find_packages(path='.'):
    packages = find_packages_internal(path)

    if 'elisa' in packages:
        return packages, {}
    
    path = os.path.abspath(path)
    dirname = os.path.split(path)[1]
    package = 'elisa.plugins.%s' % dirname

    packages.extend(['elisa', 'elisa.plugins'])
    init_dir = pkg_resources.resource_filename('elisa.core',
            'tests/data/namespace_init')
    package_dir = {package: path, 'elisa': init_dir, 'elisa.plugins': init_dir}

    return packages, package_dir


class bdist_elisa(bdist_egg):
    """
    A customized bdist_egg command that creates .elisa.egg files. They
    are the same as bare eggs for now. Only filename differs.
    """
    
    def finalize_options(self):
        bdist_egg.finalize_options(self)
        
        egg_file = self.egg_output

        basename, ext = os.path.splitext(self.egg_output)
        self.egg_output = "%s.elisa.egg" % basename

class build_po(Command):
    """
    Compile po files to mo files and ship mo files as package_data of
    the distribution.
    """

    description = "compile Moovida translation file(s)"
    user_options = []

    def finalize_options(self):
        pass

    def initialize_options(self):
        pass

    def run(self):
        setup_path = os.path.dirname(sys.argv[0])
        po_path = os.path.join(setup_path, 'i18n')
        if not os.path.exists(po_path):
            plugin_name = self.distribution.get_name().split('-').pop()
            plugin_path = os.path.join('elisa', 'plugins', plugin_name)
            po_path = os.path.join(plugin_path, 'i18n')
        if not os.path.exists(po_path):
            return
        mo_files = i18n.compile_po_files(self.distribution.get_name(),
                                         po_path, po_path,
                                         announce_func=self.announce)
        self.distribution.package_data.update(mo_files)
