import os
import sys
import re
import copy
from distutils import util
from distutils.command import install
from distutils.core import DEBUG
from distutils.errors import DistutilsOptionError

from Ft.Lib.DistExt import Util

INSTALL_LOCATIONS = {
    'install_lib' : 'Python modules (including C extensions)',
    'install_headers' : 'C/C++ header files',
    'install_scripts' : 'Executable scripts (for PATH environment variable)',
    'install_data' : 'Examples, demos and other miscellaneous data files',
    'install_sysconf': 'Configuration files',
    'install_localstate' : 'Machine-specific variable data space',
    'install_devel' : 'Developer files (regression tests)',
    'install_text' : 'Text documentation',
    'install_html' : 'HTML documentation',
    'install_l10n' : 'Natural language message catalogs',
    }

# Change the existing "installation schemes" by changing the 'install_data'
# location and adding locations for our additional commands.
INSTALL_SCHEMES = copy.deepcopy(install.INSTALL_SCHEMES)
INSTALL_SCHEMES['unix_prefix'].update({
    'data'       : '$base/share/$dist_name',
    'sysconf'    : '$base/etc/$dist_name',
    'localstate' : '$platbase/var/$dist_name',
    'devel'      : '$platbase/lib/$dist_name',
    'l10n'       : '$base/share/locale',
    'man'        : '$base/man',
    'docs'       : '$base/share/doc/$dist_name',
    })
# The "home" scheme is also updated to include the Python version in the
# 'install_lib' location.
INSTALL_SCHEMES['unix_home'].update({
    'purelib'    : '$base/lib/python$py_version_short',
    'platlib'    : '$base/lib/python$py_version_short',
    'data'       : '$base/share/$dist_name',
    'sysconf'    : '$base/share/etc/$dist_name',
    'localstate' : '$base/share/var/$dist_name',
    'devel'      : '$base/lib/$dist_name',
    'l10n'       : '$base/share/locale',
    'man'        : '$base/share/man',
    'docs'       : '$base/share/doc/$dist_name',
    })
# Update the "other" schemes
for name, scheme in INSTALL_SCHEMES.iteritems():
    if name in ('unix_prefix', 'unix_home'):
        continue
    scheme.update({
        'data'       : '$base/Share/$dist_name',
        'sysconf'    : '$base/Share/Settings/$dist_name',
        'localstate' : '$base/Share/$dist_name',
        'devel'      : '$base/Share/$dist_name',
        'l10n'       : '$base/Share/Locale',
        'man'        : '$base/Share/Help',
        'docs'       : '$base/Share/Doc/$dist_name',
        })
# Add the installation scheme for FHS "local" directories
INSTALL_SCHEMES['unix_local'] = {
    'purelib'    : '/usr/local/lib/python$py_version_short/site-packages',
    'platlib'    : '/usr/local/lib/python$py_version_short/site-packages',
    'headers'    : '/usr/local/include/$dist_name',
    'scripts'    : '/usr/local/bin',
    'data'       : '/usr/local/share/$dist_name',
    'sysconf'    : '/usr/local/etc/$dist_name',
    'localstate' : '/var/local/lib/$dist_name',
    'devel'      : '/usr/local/lib/$dist_name',
    'l10n'       : '/usr/local/share/locale',
    'man'        : '/usr/local/share/man',
    'docs'       : '/usr/local/share/doc/$dist_name',
    }
# Add the installation scheme for FHS "system" directories
INSTALL_SCHEMES['unix_system'] = {
    'purelib'    : '/usr/lib/python$py_version_short/site-packages',
    'platlib'    : '/usr/lib/python$py_version_short/site-packages',
    'headers'    : '/usr/include/$dist_name',
    'scripts'    : '/usr/bin',
    'data'       : '/usr/share/$dist_name',
    'sysconf'    : '/etc/$dist_name',
    'localstate' : '/var/lib/$dist_name',
    'devel'      : '/usr/lib/$dist_name',
    'l10n'       : '/usr/share/locale',
    'man'        : '/usr/share/man',
    'docs'       : '/usr/share/doc/$dist_name',
    }
# The scheme used when no installation scheme options are given. It uses the
# values set by the 'config' command.
INSTALL_SCHEMES['default'] = {
    'base'       : '$exec',
    'platbase'   : '$exec_prefix',
    'purelib'    : '$pythonlibdir',
    'platlib'    : '$pythonlibdir',
    'headers'    : '$includedir',
    'scripts'    : '$bindir',
    'data'       : '$datadir',
    'sysconf'    : '$sysconfdir',
    'localstate' : '$localstatedir',
    'devel'      : '$libdir',
    'l10n'       : '$localedir',
    'man'        : '$mandir',
    'docs'       : '$docdir',
    }
# A special scheme for ZIPs or Python Eggs (or anything using PEP 302
# import hooks); entries that are None indicate that they are unused
INSTALL_SCHEMES['zip'] = {
    'base'       : '',
    'platbase'   : '',
    'purelib'    : '$base',
    'platlib'    : '$base',
    'headers'    : None,
    'scripts'    : None,
    'data'       : '$base/Share',
    'sysconf'    : None,
    'localstate' : None,
    'devel'      : None,
    'l10n'       : '$base/Share/Locale',
    'man'        : None,
    'docs'       : None,
    }

SCHEME_KEYS = install.SCHEME_KEYS + (
    'sysconf', 'localstate', 'devel', 'l10n', 'man', 'docs')

def GetBundleScheme():
    scheme = INSTALL_SCHEMES['zip'].copy()
    scheme['base'] = scheme['platbase'] = os.sep
    for key in SCHEME_KEYS:
        value = scheme[key]
        if value:
            value = util.subst_vars(value, scheme)
            value = util.convert_path(value)
            pathname = os.sep.join([ path for path in value.split(os.sep)
                                     if path not in ('', '.') ])
            if value.startswith(os.sep):
                pathname = os.sep + pathname
            scheme[key] = pathname
    scheme['lib'] = scheme['purelib']
    return scheme

class Install(install.install):

    command_name = 'install'

    user_options = install.install.user_options + [
        ('with-docs', None, 'enable documentation install'),
        ('without-docs', None, 'disable documentation install'),
        ]

    # PKG-INFO is created for source distributions, so allow "developer"
    # friendly features to be enabled/disabled.
    index = -1 - int(os.path.exists('PKG-INFO'))
    user_options[index] = (user_options[index][0],
                           user_options[index][1],
                           user_options[index][2] + ' [default]')

    # inject the scheme selection options at an appropriate place
    index = 0
    while user_options[index][0] != 'exec-prefix=':
        index += 1
    if os.name == 'posix':
        user_options[index+1:index+1] = [
            ('local', None,
             '(Unix only) Use FHS /usr/local installation scheme [default]'),
            ('system', None,
             '(Unix only) Use FHS /usr system installation scheme'),
            ]
    else:
        del user_options[index]

    # inject the "install-XXX" options at an appropriate place
    index = len(user_options)
    while not user_options[index-1][0].startswith('install-'):
        index -= 1
    user_options[index:index] = [
        ('install-sysconf=', None,
         'installation directory for read-only host-specific data'),
        ('install-localstate=', None,
         'installation directory for modifiable host-specific data'),
        ('install-devel=', None,
         'installation directory for development files (regression tests)'),
        ('install-l10n=', None,
         'installation directory for message catalogs'),
        #('install-man=', None,
        # 'installation directory for Unix man pages'),
        ('install-docs=', None,
         'installation directory for documentation files'),
        #('install-info=', None,
        # 'installation directory for GNU info pages'),
        ]

    boolean_options = install.install.boolean_options + ['with-docs']
    if os.name == 'posix':
        boolean_options += ['local', 'system']

    negative_opt = install.install.negative_opt.copy()
    negative_opt['without-docs'] = 'with-docs'

    def initialize_options(self):
        install.install.initialize_options(self)
        self.local = None
        self.system = None
        self.install_sysconf = None
        self.install_localstate = None
        self.install_devel = None
        self.install_l10n = None
        self.install_man = None
        self.install_docs = None
        self.install_info = None
        self.with_docs = None

        # options not selectable from the command-line
        self.scheme = None
        self.no_report = None
        return

    def finalize_options(self):
        if self.scheme is None and (self.install_base is not None or
                                    self.install_platbase is not None):
            if ((self.install_lib is None and
                 self.install_purelib is None and
                 self.install_platlib is None) or
                self.install_headers is None or
                self.install_scripts is None or
                self.install_data is None):
                raise DistutilsOptionError, \
                      ("install-base or install-platbase supplied, but "
                      "installation scheme is incomplete")
            self.scheme = "custom"

        if self.with_docs is None:
            self.with_docs = self.distribution.source_package

        install.install.finalize_options(self)
        return

    if sys.version < '2.3':
        def dump_dirs(self, msg):
            if not DEBUG:
                return
            from distutils.fancy_getopt import longopt_xlate
            print msg + ":"
            for opt in self.user_options:
                opt_name = opt[0]
                if opt_name[-1] == "=":
                    opt_name = opt_name[0:-1]
                if opt_name in self.negative_opt:
                    opt_name = self.negative_opt[opt_name]
                    val = not getattr(self, opt_name.translate(longopt_xlate))
                else:
                    opt_name = opt_name.translate(longopt_xlate)
                    val = getattr(self, opt_name)
                print "  %s: %s" % (opt_name, val)


    def finalize_unix(self):
        # Only one of local, system, home, prefix/exec-prefix may be given
        # The easiest determine this is to just count the Nones
        if ((self.local is not None) + (self.system is not None) +
            (self.home is not None) +
            (self.prefix is not None or self.exec_prefix is not None)) > 1:
            raise DistutilsOptionError("only one of --local, --system, --home"
                                       " or --prefix/exec-prefix allowed")

        if self.scheme is not None:
            return
        elif self.local:
            self.install_base = self.install_platbase = '/usr/local'
            self.select_scheme('unix_local')
        elif self.system:
            self.install_base = self.install_platbase = '/usr'
            self.select_scheme('unix_system')
        elif self.home is not None or self.prefix is not None:
            install.install.finalize_unix(self)
        else:
            # use the directories defined by the 'config' command
            self.select_scheme('default')
        return

    def finalize_other(self):
        if self.scheme is not None:
            return
        elif self.home is not None or self.prefix is not None:
            install.install.finalize_other(self)
        else:
            # use the directories defined by the 'config' command
            self.select_scheme('default')
        return

    def get_scheme(self, name):
        return INSTALL_SCHEMES[name]

    def get_scheme_keys(self):
        return SCHEME_KEYS

    def select_scheme(self, name):
        scheme = self.get_scheme(name)
        for key in self.get_scheme_keys():
            attrname = 'install_' + key
            if getattr(self, attrname) is None:
                setattr(self, attrname, scheme[key])
        self.scheme = name
        return

    def expand_basedirs(self):
        dist = self.distribution.main_distribution
        if dist is not None:
            self.config_vars['dist_name'] = dist.get_name()
            self.config_vars['dist_version'] = dist.get_version()
            self.config_vars['dist_fullname'] = dist.get_fullname()
        config = self.get_finalized_command('config')
        self.config_vars.update(config.config_vars)
        return install.install.expand_basedirs(self)

    def expand_dirs(self):
        install.install.expand_dirs(self)
        self._expand_attrs(['install_sysconf',
                            'install_localstate',
                            'install_devel',
                            'install_l10n',
                            #'install_man',
                            'install_docs',
                            #'install_info',
                            ])

    def convert_paths(self, *names):
        extras = [ key for key in self.get_scheme_keys()
                   if key not in names ]
        names = [ name for name in list(names) + extras
                  if getattr(self, 'install_' + name) is not None ]
        return install.install.convert_paths(self, *names)

    def change_roots(self, *names):
        extras = [ key for key in self.get_scheme_keys()
                   if key not in names ]
        names = [ name for name in list(names) + extras
                  if getattr(self, 'install_' + name) is not None ]
        return install.install.change_roots(self, *names)

    def run(self):
        # Disable the standard warning so we can display it as part of the
        # installation report.
        warn_dir = self.warn_dir
        self.warn_dir = False

        install.install.run(self)

        # Restore the previous value
        self.warn_dir = warn_dir

        report = self.get_installation_report()
        if report:
            # level 3 is WARN, Distutils default level is 2 (INFO) so this
            # will still be displayed if '--quiet' is given.
            self.announce(report, 3)
        return

    def get_installation_report(self):
        lines = []

        # Only display the installation locations if installed from a source
        # distribution (aka "user mode").
        if self.distribution.source_package and not self.no_report:
            package = '%s version %s' % (self.distribution.get_name(),
                                         self.distribution.get_version())
            lines.extend(['-'*72,
                          '%s has been successfully installed!' % package,
                          ''])

            for cmd_name in self.get_sub_commands():
                try:
                    header = INSTALL_LOCATIONS[cmd_name]
                except KeyError:
                    # Unknown install command, skip it
                    continue

                cmd = self.distribution.get_command_obj(cmd_name)
                try:
                    install_dir = getattr(cmd, 'install_dir')
                except AttributeError:
                    install_dir = getattr(self, cmd_name)

                # Strip off trailing separator
                if install_dir.endswith(os.sep):
                    install_dir = install_dir[:-len(os.sep)]

                # Only output location if something is installed there
                if cmd.get_outputs():
                    lines.extend([header,
                                  ' '*4 + install_dir,
                                  ''])
        # Check if the Python library code is installed to a directory that
        # is on Python's search path.
        lib_dir = Util.NormalizePath(self.install_lib)
        sys_path = map(Util.NormalizePath, sys.path)
        if self.warn_dir and (lib_dir not in sys_path):
            lines.append("WARNING: The installation directory %r must be on"
                         " Python's search path (sys.path) --"
                         " This can be done by adding it to PYTHONPATH or by"
                         "modifying sys.path in your code." % self.install_lib)

        return '\n'.join(lines)

    # -- Reporting methods ---------------------------------------------

    def get_source_files(self):
        files = []
        for cmd_name in self.get_sub_commands():
            cmd = self.get_finalized_command(cmd_name)
            files.extend(cmd.get_source_files())
        return files

    # -- Predicates for sub-command list -------------------------------

    def has_sysconf(self):
        return self.distribution.has_sysconf()

    def has_localstate(self):
        return self.distribution.has_localstate()

    def has_text(self):
        return self.distribution.has_text()

    def has_docs(self):
        return self.with_docs and self.distribution.has_docs()

    def has_devel(self):
        return self.distribution.has_devel()

    def has_l10n(self):
        return self.distribution.has_l10n()

    def has_config(self):
        return self.distribution.config_module is not None

    # a list of commands this command might have to run to do its work.
    sub_commands = install.install.sub_commands + [
        ('install_sysconf', has_sysconf),
        ('install_localstate', has_localstate),
        ('install_devel', has_devel),
        #('install_man', has_scripts),
        ('install_text', has_text),
        ('install_html', has_docs),
        #('install_info', has_docs),
        ('install_l10n', has_l10n),
        ('install_config', has_config),
        ]

    # Ensure that .egg-info files are provided for by the tool-chain.
    # (new in 2.5 or setuptools)
    if 'install_egg_info' not in dict(sub_commands):
        sub_commands.append(('install_egg_info', None))
