import os
import sys

from distutils.cmd import Command


class depends(Command):
    """Download and install dependencies, if needed"""

    description = "download and install dependencies, if needed"

    user_options = [
        ('temp=', 't',
         "directory where dependencies will be downloaded and built"),
        ('ignore-extra-args', 'i',
         "ignore options that won't be passed to child setup scripts"),
        ]

    path_attrs = [
        # Note: these must be in *reverse* order, as they are pushed onto the
        # *front* of a copy of sys.path.
        ('install', 'install_libbase'),     # installation base if extra_path
        ('install_lib', 'install_dir'),     # where modules are installed
        ]

    # Command options that can be safely passed to dependencies' setup scripts
    safe_opts = {
        'install': [
            'prefix', 'exec-prefix', 'home', 'install-base',
            'install-platbase', 'root', 'optimize', 'force', 'verbose', 'quiet'
            ],
        'build': ['compiler', 'debug', 'force', 'verbose', 'quiet'],
        }

    # Options with string arguments that are *not* directories or files, and
    # so should *not* have absolute-path fixups applied.
    non_fs_opts = {'build': ['compiler']}

    def initialize_options(self):
        self.temp = None; self.ignore_extra_args = None

    def finalize_options(self):
        self.set_undefined_options('build',('build_temp', 'temp'))
        self.set_search_path()
        self.set_subcommand_args()

    def set_subcommand_args(self):
        safe = {'install':[]}   # ensure we at least perform an install
        unsafe = {}
        copts = self.distribution.get_cmdline_options()

        if 'depends' in copts:
            del copts['depends']

        for cmd,opts in copts.items():
            safe_opts = self.safe_opts.get(cmd,())
            non_fs_opts = self.non_fs_opts.get(cmd,())

            for opt,val in opts.items():
                if opt in safe_opts:
                    cmdline = safe.setdefault(cmd,[])
                    if val is not None and opt not in non_fs_opts:
                        val = os.path.abspath(val)
                else:
                    cmdline = unsafe.setdefault(cmd,[])

                cmdline.append('--'+opt)
                if val is not None:
                    cmdline.append(val)

        self.safe_options = safe
        self.unsafe_options = unsafe

    def set_search_path(self):
        """Determine paths to check for installed dependencies"""
        path = sys.path[:]  # copy sys path
        for cmd,attr in self.path_attrs:
            dir = getattr(self.get_finalized_command(cmd),attr,None)
            if dir and dir not in path:
                path.insert(0,dir)  # prepend
        self.search_path = path

    def run(self):
        self.announce("checking for installed dependencies")

        needed = [
            dep for dep in self.distribution.requires if self.is_needed(dep)
            ]

        if not needed:
            self.announce("all dependencies are present and up-to-date")
            return

        argv = [sys.executable,'setup.py']
        for cmd,line in self.safe_options.items():
            argv.append(cmd); argv.extend(line)

        self.announce(
            "dependencies will be installed using:\n    "+' '.join(argv)+'\n'
            )

        # Alert for unsupported commands/options, unless '-i' was used
        if self.unsafe_options:
            self.warn_unsafe_options_used()
            if not self.ignore_extra_args:
                raise SystemExit(
                    "Unsupported options for building dependencies; please"
                    " add 'depends -i'\nto your command line if you want to"
                    " force the build to proceed.\nOtherwise, you will need"
                    " to omit the unsupported options,\nor install the"
                    " dependencies manually."
                    )

        # Alert the user to missing items
        fmt = "\t%s\t%s\n"
        items = [fmt % (dep.full_name(), dep.homepage) for dep in needed]
        items.insert(0,"Please install the following packages *first*:\n")
        items.append('')
        raise SystemExit('\n'.join(items))  # dump msg to stderr and exit

    def warn_unsafe_options_used(self):
        lines = []; write = lines.append
        write("the following command options are not supported for building")
        write("dependencies, and will be IGNORED:")
        for cmd,line in self.unsafe_options.items():
            write('\t%s %s' % (cmd, ' '.join(line)))
        write('')
        self.warn('\n'.join(lines))

    def is_needed(self,dep):
        """Does the specified dependency need to be installed/updated?"""
        self.announce("searching for "+dep.full_name())
        version = dep.get_version(self.search_path)

        if version is None:
            self.announce(name+" not found!")
            return True

        if str(version)=="unknown":
            status = dep.name+" is installed"
        else:
            status = dep.name+" version "+str(version)+" found"

        if dep.version_ok(version):
            self.announce(status+" (OK)")
            return False
        else:
            self.announce(status+" (update needed)")
            return True
