##############################################################################
#
# Copyright (c) 2006 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""Install packages as eggs

$Id: egg.py 115900 2010-08-23 20:26:07Z gary $
"""

import UserDict, logging, os, re, zipfile
import zc.buildout
import zc.buildout.easy_install


class Eggs(object):

    include_site_packages = allowed_eggs = None

    def __init__(self, buildout, name, options):
        self.buildout = buildout
        self.name = self.default_eggs = name
        if getattr(options, 'query_bool', None) is None:
            # Someone is not passing us a zc.buildout.buildout.Options
            # object.  Maybe we should have a deprecation warning.
            # Whatever.
            options = _BackwardsSupportOption(options)
        self.options = options
        b_options = buildout['buildout']
        links = options.get('find-links', b_options['find-links'])
        if links:
            links = links.split()
            options['find-links'] = '\n'.join(links)
        else:
            links = ()
        self.links = links

        index = options.get('index', b_options.get('index'))
        if index is not None:
            options['index'] = index
        self.index = index

        allow_hosts = b_options['allow-hosts']
        allow_hosts = tuple([host.strip() for host in allow_hosts.split('\n')
                               if host.strip()!=''])
        self.allow_hosts = allow_hosts

        options['eggs-directory'] = b_options['eggs-directory']
        options['_e'] = options['eggs-directory'] # backward compat.
        options['develop-eggs-directory'] = b_options['develop-eggs-directory']
        options['_d'] = options['develop-eggs-directory'] # backward compat.

        python = options.setdefault('python', b_options['python'])
        options['executable'] = buildout[python]['executable']

    def working_set(self, extra=()):
        """Separate method to just get the working set

        This is intended for reuse by similar recipes.
        """
        options = self.options
        b_options = self.buildout['buildout']

        distributions = [
            r.strip()
            for r in options.get('eggs', self.default_eggs).split('\n')
            if r.strip()]
        orig_distributions = distributions[:]
        distributions.extend(extra)

        if b_options.get('offline') == 'true':
            ws = zc.buildout.easy_install.working_set(
                distributions, options['executable'],
                [options['develop-eggs-directory'],
                 options['eggs-directory']],
                include_site_packages=self.include_site_packages,
                allowed_eggs_from_site_packages=self.allowed_eggs,
                )
        else:
            kw = {}
            if 'unzip' in options:
                kw['always_unzip'] = options.query_bool('unzip', None)
            ws = zc.buildout.easy_install.install(
                distributions, options['eggs-directory'],
                links=self.links,
                index=self.index,
                executable=options['executable'],
                path=[options['develop-eggs-directory']],
                newest=b_options.get('newest') == 'true',
                include_site_packages=self.include_site_packages,
                allowed_eggs_from_site_packages=self.allowed_eggs,
                allow_hosts=self.allow_hosts,
                **kw)

        return orig_distributions, ws

    def install(self):
        reqs, ws = self.working_set()
        return ()

    update = install


class ScriptBase(Eggs):

    def __init__(self, buildout, name, options):
        super(ScriptBase, self).__init__(buildout, name, options)

        b_options = buildout['buildout']

        options['bin-directory'] = b_options['bin-directory']
        options['_b'] = options['bin-directory'] # backward compat.

        self.extra_paths = [
            os.path.join(b_options['directory'], p.strip())
            for p in options.get('extra-paths', '').split('\n')
            if p.strip()
            ]
        if self.extra_paths:
            options['extra-paths'] = '\n'.join(self.extra_paths)


        relative_paths = options.get(
            'relative-paths', b_options.get('relative-paths', 'false'))
        if relative_paths == 'true':
            options['buildout-directory'] = b_options['directory']
            self._relative_paths = options['buildout-directory']
        else:
            self._relative_paths = ''
            assert relative_paths == 'false'

    parse_entry_point = re.compile(
        '([^=]+)=(\w+(?:[.]\w+)*):(\w+(?:[.]\w+)*)$'
        ).match

    def install(self):
        reqs, ws = self.working_set()
        options = self.options

        scripts = options.get('scripts')
        if scripts or scripts is None or options.get('interpreter'):
            if scripts is not None:
                scripts = scripts.split()
                scripts = dict([
                    ('=' in s) and s.split('=', 1) or (s, s)
                    for s in scripts
                    ])

            for s in options.get('entry-points', '').split():
                parsed = self.parse_entry_point(s)
                if not parsed:
                    logging.getLogger(self.name).error(
                        "Cannot parse the entry point %s.", s)
                    raise zc.buildout.UserError("Invalid entry point")
                reqs.append(parsed.groups())

            if options.query_bool('dependent-scripts', 'false'):
                # Generate scripts for all packages in the working set,
                # except setuptools.
                reqs = list(reqs)
                for dist in ws:
                    name = dist.project_name
                    if name != 'setuptools' and name not in reqs:
                        reqs.append(name)
            return self._install(reqs, ws, scripts)
        return ()

    update = install

    def _install(self, reqs, ws, scripts):
        # Subclasses implement this.
        raise NotImplementedError()


class Scripts(ScriptBase):

    def _install(self, reqs, ws, scripts):
        options = self.options
        return zc.buildout.easy_install.scripts(
            reqs, ws, options['executable'],
            options['bin-directory'],
            scripts=scripts,
            extra_paths=self.extra_paths,
            interpreter=options.get('interpreter'),
            initialization=options.get('initialization', ''),
            arguments=options.get('arguments', ''),
            relative_paths=self._relative_paths
            )

Egg = Scripts


class _BackwardsSupportOption(UserDict.UserDict):

    def __init__(self, data):
        self.data = data # We want to show mutations to the underlying dict.

    def query_bool(self, name, default=None):
        """Given a name, return a boolean value for that name.

        ``default``, if given, should be 'true', 'false', or None.
        """
        if default is not None:
            value = self.setdefault(name, default)
        else:
            value = self.get(name)
            if value is None:
                return value
        return _convert_bool(name, value)

    def get_bool(self, name):
        """Given a name, return a boolean value for that name.
        """
        return _convert_bool(name, self[name])


def _convert_bool(name, value):
    if value not in ('true', 'false'):
        raise zc.buildout.UserError(
            'Invalid value for %s option: %s' % (name, value))
    else:
        return value == 'true'
