import sys, os
from types import ClassType, ListType
from distutils import command
from distutils.cmd import Command
from distutils.core import Distribution, gen_usage, DEBUG
from distutils.errors import *
from distutils.fancy_getopt import wrap_text

# Our new Distribution class
class Dist(Distribution):

    # A mapping of all available commands
    command_mapping = {}

    # Add builtin distutils' commands
    for cmd in command.__all__:
        command_mapping[cmd] = None

    command_mapping.update({
        'config' : 'Config',

        'build' : 'Build',
        'build_py' : 'BuildPy',
        'build_ext' : 'BuildExt',
        #'build_clib' : '',
        'build_scripts' : 'BuildScripts',
        'build_tests' : 'BuildTests',
        'build_docs' : 'BuildDocs',
        'build_l10n' : 'BuildL10n',
        'build_idl' : 'BuildIdl',

        #'clean' : '',

        'install' : 'Install',
        'install_lib' : 'InstallLib',
        #'install_headers' : '',
        'install_scripts' : 'InstallScripts',
        'install_data' : 'InstallData',
        'install_sysconf' : 'InstallSysconf',
        'install_localstate' : 'InstallLocalState',
        'install_tests' : 'InstallTests',
        'install_docs' : 'InstallDocs',
        'install_l10n' : 'InstallL10n',

        'sdist' : 'SDist',

        'bdist' : 'BDist',
        #'bdist_dumb' : '',
        'bdist_rpm' : 'BDistRpm',
        'bdist_inno' : 'BDistInno',
        'bdist_wininst' : 'BDistWininst',
        'bdist_install' : 'BDistInstall',

        'generate' : 'Generate',
        'generate_bgen' : 'GenerateBisonGen',
        'generate_l10n' : 'GenerateL10n',
        })

    ordered_commands = ['config',
                        'build',
                        'clean',
                        'install',
                        'sdist',
                        'bdist',
                        'generate',
                        ]

    display_options = [
        ('help-compilers', None, 'list available compilers'),
        ] + Distribution.display_options

    display_option_names = [
        'help_compilers',
        ] + Distribution.display_option_names

    def __init__(self, attrs):
        # Add our placeholders for arguments from setup()
        self.dependencies = []
        self.l10n = []
        self.doc_files = []
        self.bgen_files = []
        self.tests = []
        self.idl_files = []
        self.sysconf_files = []
        self.localstate_files = []

        # The module where configuration variables are written.
        self.config_module = None

        self.manifest_templates = []
        self.validate_templates = []

        Distribution.__init__(self, attrs)
        if sys.version < '2.3':
            self.verbose = -1
        return

    def finalize_options(self):
        Distribution.finalize_options(self)

        # Python 2.1 uses licence, whereas 2.2 uses license
        if self.license:
            self.metadata.license = self.license
            self.metadata.licence = self.license
        return

    # -- Command-line parsing methods ----------------------------------

    def parse_command_line(self):
        rv = Distribution.parse_command_line(self)
        if self.verbose == -1:
            # neither --quiet or --verbose specified
            self.verbose = 0
            self._dist_verbose = self.developer_mode
        else:
            self._dist_verbose = self.verbose
        return rv

    def print_commands(self):
        max_length = 0
        for cmd in self.ordered_commands:
            if len(cmd) > max_length:
                max_length = len(cmd)

        self.print_command_list(self.ordered_commands,
                                "Available commands",
                                max_length)
        return

    def print_option_list(self, options, header, max_length):
        # Generate lines of help text.
        line_width = 78
        opt_width = max_length + 2 + 2 + 2  # room for indent + dashes + gutter
        text_width = line_width - opt_width
        big_indent = ' ' * opt_width

        print header

        for option in options:
            long, short, help = option[:3]
            if long[-1] == '=':
                long = long[0:-1]

            # Case 1: no short option at all
            if short is None:
                opt_names = long

            # Case 2: we have a short option, so we have to include it
            # just after the long option
            else:
                opt_names = "%s (-%s)" % (long, short)

            text = wrap_text(help, text_width)
            if text:
                print "  --%-*s  %s" % (max_length, opt_names, text[0])
                for line in text[1:]:
                    print big_indent + line
            else:
                print "  --%-*s" % (max_length, opt_names)
        return

    def _show_help (self, parser, global_options=1, display_options=1,
                    commands=[]):

        # Determine maximum length of option names
        options = self.global_options + self.display_options
        for command in self.commands:
            klass = self.get_command_class(command)
            options = options + klass.user_options
            if hasattr(klass, 'help_options'):
                options = options + klass.help_options

        max_length = 0
        for option in options:
            long = option[0]
            short = option[1]
            l = len(long)
            if long[-1] == '=':
                l = l - 1
            if short is not None:
                l = l + 5                   # " (-x)" where short == 'x'
            if l > max_length:
                max_length = l

        # Now print the option tables
        if global_options:
            self.print_option_list(self.global_options, "Global options:",
                                   max_length)
            print

        if display_options:
            self.print_option_list(self.display_options,
                                   "Display options (ignores commands):",
                                   max_length)
            print

        for command in self.commands:
            klass = self.get_command_class(command)
            command_name = getattr(klass, 'command_name', klass.__name__)
            header = "Options for %r command:" % command_name
            if hasattr(klass, 'help_options'):
                self.print_option_list(klass.user_options + klass.help_options,
                                       header, max_length)
            else:
                self.print_option_list(klass.user_options, header, max_length)
            print

        print gen_usage(self.script_name)
        return

    def handle_display_options(self, option_order):
        if self.help_compilers:
            from distutils.ccompiler import show_compilers
            show_compilers()
            return 1
        return Distribution.handle_display_options(self, option_order)

    def get_command_list(self):
        commands = []
        for cmd in self.ordered_commands:
            commands.append(group)
            klass = self.get_command_class(group)
            for sub_command in klass.sub_commands:
                commands.append(sub_command[0])

        rv = []
        for cmd in commands:
            klass = self.cmdclass.get(cmd)
            if not klass:
                klass = self.get_command_class(cmd)
            try:
                description = klass.description
            except AttributeError:
                description = "(no description available)"
            rv.append((cmd, description))
        return rv

    # -- Command class/object methods ----------------------------------

    def get_command_class (self, command):
        # Try user defined classes first (and already loaded classes)
        klass = self.cmdclass.get(command)
        if klass:
            return klass

        if not self.command_mapping.has_key(command):
            raise DistutilsModuleError("invalid command '%s'" % command)

        base_name = self.command_mapping[command]
        if not base_name:
            # Fallback to builtin commands for distutils
            return Distribution.get_command_class(self, command)

        module_name = 'Ft.Lib.DistExt.' + base_name
        klass_name = base_name

        try:
            __import__ (module_name)
            module = sys.modules[module_name]
        except ImportError:
            if DEBUG: raise
            raise DistutilsModuleError, \
                  "invalid command '%s' (no module named '%s')" % \
                  (command, module_name)

        try:
            klass = getattr(module, klass_name)
        except AttributeError:
            if DEBUG: raise
            raise DistutilsModuleError, \
                  "invalid command '%s' (no class '%s' in module '%s')" \
                  % (command, klass_name, module_name)

        self.cmdclass[command] = klass
        return klass

    # -- Methods that operate on the Distribution ----------------------

    def announce(self, message, level=1):
        if sys.version < '2.3':
            if self._dist_verbose:
                print message
        else:
            Distribution.announce(self, message, level)
        return

    # -- Methods that operate on its Commands --------------------------

    def run_command (self, command):
        """Do whatever it takes to run a command (including nothing at all,
        if the command has already been run).  Specifically: if we have
        already created and run the command named by 'command', return
        silently without doing anything.  If the command named by 'command'
        doesn't even have a command object yet, create one.  Then invoke
        'run()' on that command object (or an existing one).
        """
        # Already been here, done that? then return silently.
        if self.have_run.get(command):
            return

        self.announce("running %s" % command)
        cmd_obj = self.get_command_obj(command)
        cmd_obj.ensure_finalized()
        cmd_obj.run()
        self.have_run[command] = 1
        return

    # -- Distribution query methods ------------------------------------

    def has_idl(self):
        # Used for both build and install
        return self.idl_files and len(self.idl_files) > 0

    def has_l10n(self):
        # Used for both build and generate
        return len(self.l10n) > 0

    def has_sysconf(self):
        # Used for install
        return len(self.sysconf_files) > 0

    def has_localstate(self):
        # Used for install
        return len(self.localstate_files) > 0

    def has_docs(self):
        # Used for both build and install
        docs = self.doc_files
        if not docs:
            # Our scripts have a built-in documentation generator
            docs = self.scripts
        return docs and len(docs) > 0

    def has_tests(self):
        # Used for both build and test
        return self.tests and len(self.tests) > 0

    def has_bgen(self):
        # Used for both sdist and generate
        return self.bgen_files and len(self.bgen_files) > 0

    def has_scripts(self):
        # Used for sdist
        return self.scripts and len(self.scripts) > 0

