# -*- coding=utf-8 -*-

import contextlib
import distlib
import distutils
import packagebuilder
import pip_shims
import setuptools
import vistir


class RequirementUninstaller(object):
    """A context manager to remove a package for the inner block.

    This uses `UninstallPathSet` to control the workflow. If the inner block
    exits correctly, the uninstallation is committed, otherwise rolled back.
    """
    def __init__(self, ireq, auto_confirm, verbose):
        self.ireq = ireq
        self.pathset = None
        self.auto_confirm = auto_confirm
        self.verbose = verbose

    def __enter__(self):
        self.pathset = self.ireq.uninstall(
            auto_confirm=self.auto_confirm,
            verbose=self.verbose,
        )
        return self.pathset

    def __exit__(self, exc_type, exc_value, traceback):
        if self.pathset is None:
            return
        if exc_type is None:
            self.pathset.commit()
        else:
            self.pathset.rollback()


def uninstall(name, **kwargs):
    ireq = pip_shims.InstallRequirement.from_line(name)
    return RequirementUninstaller(ireq, **kwargs)


@contextlib.contextmanager
def _suppress_distutils_logs():
    """Hack to hide noise generated by `setup.py develop`.

    There isn't a good way to suppress them now, so let's monky-patch.
    See https://bugs.python.org/issue25392.
    """
    f = distutils.log.Log._log

    def _log(log, level, msg, args):
        if level >= distutils.log.ERROR:
            f(log, level, msg, args)

    distutils.log.Log._log = _log
    yield
    distutils.log.Log._log = f


class NoopInstaller(object):
    """An installer.

    This class is not designed to be instantiated by itself, but used as a
    common interface for subclassing.

    An installer has two methods, `prepare()` and `install()`. Neither takes
    arguments, and should be called in that order to prepare an installation
    operation, and to actually install things.
    """
    def prepare(self):
        pass

    def install(self):
        pass


class EditableInstaller(NoopInstaller):
    """Installer to handle editable.
    """
    def __init__(self, requirement):
        ireq = requirement.as_ireq()
        self.working_directory = ireq.setup_py_dir
        self.setup_py = ireq.setup_py

    def install(self):
        with vistir.cd(self.working_directory), _suppress_distutils_logs():
            # Access from Setuptools to ensure things are patched correctly.
            setuptools.dist.distutils.core.run_setup(
                self.setup_py, ["develop", "--no-deps"],
            )


class WheelInstaller(NoopInstaller):
    """Installer by building a wheel.

    The wheel is built during `prepare()`, and installed in `install()`.
    """
    def __init__(self, requirement, sources, paths):
        self.ireq = requirement.as_ireq()
        self.sources = packagebuilder.filter_sources(requirement, sources)
        self.hashes = requirement.hashes or None
        self.paths = paths
        self.wheel = None

    def prepare(self):
        self.wheel = packagebuilder.build_wheel(self.ireq, self.sources, self.hashes)

    def install(self):
        self.wheel.install(self.paths, distlib.scripts.ScriptMaker(None, None))
