import os, sys, types
from distutils import core
from distutils import fancy_getopt
from distutils.core import DEBUG
from distutils.errors import *
from distutils.util import convert_path
from distutils.version import StrictVersion
import Dist

# For .pkg data files
import Structures

core.USAGE = """\
Usage:
  %(script)s [global_opts] cmd1 [cmd1_opts] [cmd2 [cmd2_opts] ...]
  %(script)s --help [cmd1 cmd2 ...]
  %(script)s cmd --help
  %(script)s --help-commands
  %(script)s --help-packages
"""

# the wrap_text function will collapse 2+ newlines into 1+ newlines
INSTALL_SUCCESS = """\
%(name)s version %(version)s has been successfully installed!

Please note the installation locations:


    Example repository config files:

    %(install_sysconf)s


    Machine-specific variable data space:

    %(install_localstate)s


    Local message catalogs:

    %(install_l10n)s


    Demos, default XML catalogs, schemas, profiling code, load

    tests, and various standard files for installation in new

    4Suite repositories:

    %(install_data)s


    Python library code, including Python C extensions:

    %(install_lib)s%(sep)s%(package_root)s


    Test suites:

    %(install_tests)s


    Executable scripts (add this to your executable path):

    %(install_scripts)s


    Documentation in various formats, if installed:

    %(install_docs)s
"""


class PackageManager(Dist.Dist):

    global_options = Dist.Dist.global_options + [
        ('package=', 'p', "limit command(s) to a given package"),
        ]

    display_options = [
        ('help-packages', None, 'list all available packages'),
        ] + Dist.Dist.display_options

    display_option_names = ['help_packages'] + Dist.Dist.display_option_names

    def __init__(self, attrs):
        self.package = None
        self.package_root = None
        self.package_info = {}
        Dist.Dist.__init__(self, attrs)
        return

    def finalize_options(self):
        self.distributions = {}

        if self.package:
            if not self.package_info.has_key(self.package):
                raise DistutilsArgError, \
                      "package '%s' is unknown, use --help-packages " \
                      "to get a complete listing" % self.package
            packages = self.resolve_dependencies(self.package)
        else:
            packages = self.package_info.keys()

        self.sub_packages = self.sort_dependencies(packages)

        # Initialize the list-type data variables before merging
        if not self.packages:
            self.packages = []
        if not self.py_modules:
            self.py_modules = []
        if not self.libraries:
            self.libraries = []
        if not self.headers:
            self.headers = []
        if not self.ext_modules:
            self.ext_modules = []
        if not self.scripts:
            self.scripts = []
        if not self.data_files:
            self.data_files = []

        # Build a composite of requested package and dependencies
        for name in self.sub_packages:
            self.merge_package(self.package_info[name])

        self.developer_mode = (os.path.isdir('CVS') or os.path.isdir('RCS')
                               or os.path.exists('CVS.sandboxinfo'))
        if self.developer_mode:
            print "developer mode enabled (CVS directory found)"

        Dist.Dist.finalize_options(self)
        return

    def get_package_distribution(self, package):
        if self.distributions.has_key(package):
            return self.distributions[package]

        attrs = {'script_name' : self.script_name,
                 'script_args' : [],
                 }

        metadata = vars(self.metadata)
        attrs.update(metadata)

        pkginfo = self.package_info[package]
        attrs.update(pkginfo)

        dist = self.distributions[package] = Dist.Dist(attrs)
        return dist

    def handle_display_options(self, option_order):
        from distutils.core import gen_usage
        if self.help_packages:
            self.print_packages()
            print
            print gen_usage(self.script_name)
            return 1
        return Dist.Dist.handle_display_options(self, option_order)

    def print_packages(self):
        packages = self.package_info.keys()
        packages.sort()

        max_length = 0
        for pkg in packages:
            if len(pkg) > max_length:
                max_length = len(pkg)

        print "Available packages:"
        for pkg in packages:
            try:
                description = self.package_info[pkg]['description']
            except KeyError:
                description = '(no description available)'

            print "  %-*s  %s" % (max_length, pkg, description)
        return

    def run_commands(self):
        if DEBUG: print "PackageManager.run_commands():"

        Dist.Dist.run_commands(self)

        if self.have_run.get('install') and not self.developer_mode:
            install = self.get_command_obj('install')
            subst = {'sep': os.sep}
            subst.update(vars(self))
            subst.update(vars(self.metadata))
            subst.update(vars(install))

            message = INSTALL_SUCCESS % subst
            print '-'*78
            print wrap_text(message, 78)
        return

    def merge_package(self, package_info, overwrite=0):
        # Any attribute that is not already defined is invalid!
        metadict = self.metadata.__dict__
        dict = self.__dict__
        for key, val in package_info.items():
            if metadict.has_key(key):
                if overwrite:
                    metadict[key] = val
            elif dict.has_key(key):
                if type(val) is types.TupleType:
                    val = list(val)
                if overwrite or dict[key] is None:
                    dict[key] = val
                elif type(dict[key]) is type(val) is types.ListType:
                    # Merge two lists
                    dict[key].extend(val)
                else:
                    raise DistutilsSetupError, \
                          "incompatiable types for option '%s'" % key
            else:
                raise DistutilsSetupError, \
                      "invalid distribution option '%s'" % key
        return

    def sort_dependencies(self, packages):
        if DEBUG: print "PackageManager.sort_dependencies():"

        # Make sure that the dependencies exist in package_info
        for pkg in packages:
            try:
                pkginfo = self.package_info[pkg]
                for dep in pkginfo.get('dependencies', []):
                    depinfo = self.package_info[dep]
            except KeyError, key:
                raise DistutilsFileError('missing dependency: %s' % key)

        # Remove any duplicates
        set = {}
        for name in packages:
            set[name] = 1
        packages = set.keys()

        # Now we can sort 'em
        sorted = []
        while packages:
            changed = 0
            if DEBUG: print "  begin sort:"
            for pkg in packages[:]:
                if DEBUG: print "    trying", pkg
                pkginfo = self.package_info[pkg]
                dependencies = pkginfo.get('dependencies', [])
                for dep in dependencies:
                    if dep not in sorted:
                        break
                else:
                    if DEBUG: print "    sorted", pkg
                    sorted.append(pkg)
                    packages.remove(pkg)
                    changed = 1
            if not changed:
                raise DistutilsFileError("circular dependency: %s" % packages)

        return sorted

    def resolve_dependencies(self, package):
        if DEBUG: print "PackageManager.resolve_dependencies():"
        packages = [package]
        pkg_info = self.package_info[package]
        for name in pkg_info.get('dependencies', []):
            if self.package_info.has_key(name):
                packages.extend(self.resolve_dependencies(name))
        return packages


def wrap_text (text, width):
    if text is None: return ''
    if len(text) <= width: return text

    chunks = text.split('\n\n')
    result = []
    for chunk in chunks:
        if chunk.startswith('\n'):
            # An odd number of newlines, the empty string will become
            # a newline when this list is joined.
            result.append('')
            chunk = chunk[1:]
        lines = fancy_getopt.wrap_text(chunk, width)
        result.append('\n'.join(lines))

    return '\n'.join(result)
