
# Copyright (c) Jupyter Development Team.
# Distributed under the terms of the Modified BSD License.

import errno
import os.path
import sys
import json

from traitlets.config.application import Application
from jupyter_core.application import (
    JupyterApp, base_flags, base_aliases
)
from traitlets import Instance, Dict, Unicode, Bool, List

from . import __version__
from .kernelspec import KernelSpecManager


class ListKernelSpecs(JupyterApp):
    version = __version__
    description = """List installed kernel specifications."""
    kernel_spec_manager = Instance(KernelSpecManager)
    json_output = Bool(False, help='output spec name and location as machine-readable json.',
            config=True)

    flags = {'json': ({'ListKernelSpecs': {'json_output': True}},
                "output spec name and location as machine-readable json."),
             'debug': base_flags['debug'],
            }

    def _kernel_spec_manager_default(self):
        return KernelSpecManager(parent=self, data_dir=self.data_dir)

    def start(self):
        paths = self.kernel_spec_manager.find_kernel_specs()
        specs = self.kernel_spec_manager.get_all_specs()
        if not self.json_output:
            if not specs:
                print("No kernels available")
                return
            # pad to width of longest kernel name
            name_len = len(sorted(paths, key=lambda name: len(name))[-1])

            def path_key(item):
                """sort key function for Jupyter path priority"""
                path = item[1]
                for idx, prefix in enumerate(self.jupyter_path):
                    if path.startswith(prefix):
                        return (idx, path)
                # not in jupyter path, artificially added to the front
                return (-1, path)

            print("Available kernels:")
            for kernelname, path in sorted(paths.items(), key=path_key):
                print("  %s    %s" % (kernelname.ljust(name_len), path))
        else:
            print(json.dumps({
                'kernelspecs': specs
            }, indent=2))



class InstallKernelSpec(JupyterApp):
    version = __version__
    description = """Install a kernel specification directory.

    Given a SOURCE DIRECTORY containing a kernel spec,
    jupyter will copy that directory into one of the Jupyter kernel directories.
    The default is to install kernelspecs for all users.
    `--user` can be specified to install a kernel only for the current user.
    """
    examples = """
    jupyter kernelspec install /path/to/my_kernel --user
    """
    usage = "jupyter kernelspec install SOURCE_DIR [--options]"
    kernel_spec_manager = Instance(KernelSpecManager)

    def _kernel_spec_manager_default(self):
        return KernelSpecManager(data_dir=self.data_dir)

    sourcedir = Unicode()
    kernel_name = Unicode("", config=True,
        help="Install the kernel spec with this name"
    )
    def _kernel_name_default(self):
        return os.path.basename(self.sourcedir)

    user = Bool(False, config=True,
        help="""
        Try to install the kernel spec to the per-user directory instead of
        the system or environment directory.
        """
    )
    prefix = Unicode('', config=True,
        help="""Specify a prefix to install to, e.g. an env.
        The kernelspec will be installed in PREFIX/share/jupyter/kernels/
        """
    )
    replace = Bool(False, config=True,
        help="Replace any existing kernel spec with this name."
    )

    aliases = {
        'name': 'InstallKernelSpec.kernel_name',
        'prefix': 'InstallKernelSpec.prefix',
    }
    aliases.update(base_aliases)

    flags = {'user': ({'InstallKernelSpec': {'user': True}},
                "Install to the per-user kernel registry"),
             'replace': ({'InstallKernelSpec': {'replace': True}},
                "Replace any existing kernel spec with this name."),
             'sys-prefix': ({'InstallKernelSpec': {'prefix': sys.prefix}},
                "Install to Python's sys.prefix. Useful in conda/virtual environments."),
             'debug': base_flags['debug'],
            }

    def parse_command_line(self, argv):
        super().parse_command_line(argv)
        # accept positional arg as profile name
        if self.extra_args:
            self.sourcedir = self.extra_args[0]
        else:
            print("No source directory specified.")
            self.exit(1)

    def start(self):
        if self.user and self.prefix:
            self.exit("Can't specify both user and prefix. Please choose one or the other.")
        try:
            self.kernel_spec_manager.install_kernel_spec(self.sourcedir,
                                                 kernel_name=self.kernel_name,
                                                 user=self.user,
                                                 prefix=self.prefix,
                                                 replace=self.replace,
                                                )
        except OSError as e:
            if e.errno == errno.EACCES:
                print(e, file=sys.stderr)
                if not self.user:
                    print("Perhaps you want to install with `sudo` or `--user`?", file=sys.stderr)
                self.exit(1)
            elif e.errno == errno.EEXIST:
                print("A kernel spec is already present at %s" % e.filename, file=sys.stderr)
                self.exit(1)
            raise

class RemoveKernelSpec(JupyterApp):
    version = __version__
    description = """Remove one or more Jupyter kernelspecs by name."""
    examples = """jupyter kernelspec remove python2 [my_kernel ...]"""

    force = Bool(False, config=True,
        help="""Force removal, don't prompt for confirmation."""
    )
    spec_names = List(Unicode())

    kernel_spec_manager = Instance(KernelSpecManager)
    def _kernel_spec_manager_default(self):
        return KernelSpecManager(data_dir=self.data_dir, parent=self)

    flags = {
        'f': ({'RemoveKernelSpec': {'force': True}}, force.get_metadata('help')),
    }
    flags.update(JupyterApp.flags)

    def parse_command_line(self, argv):
        super().parse_command_line(argv)
        # accept positional arg as profile name
        if self.extra_args:
            self.spec_names = sorted(set(self.extra_args)) # remove duplicates
        else:
            self.exit("No kernelspec specified.")

    def start(self):
        self.kernel_spec_manager.ensure_native_kernel = False
        spec_paths = self.kernel_spec_manager.find_kernel_specs()
        missing = set(self.spec_names).difference(set(spec_paths))
        if missing:
            self.exit("Couldn't find kernel spec(s): %s" % ', '.join(missing))

        if not self.force:
            print("Kernel specs to remove:")
            for name in self.spec_names:
                print("  %s\t%s" % (name.ljust(20), spec_paths[name]))
            answer = input("Remove %i kernel specs [y/N]: " % len(self.spec_names))
            if not answer.lower().startswith('y'):
                return

        for kernel_name in self.spec_names:
            try:
                path = self.kernel_spec_manager.remove_kernel_spec(kernel_name)
            except OSError as e:
                if e.errno == errno.EACCES:
                    print(e, file=sys.stderr)
                    print("Perhaps you want sudo?", file=sys.stderr)
                    self.exit(1)
                else:
                    raise
            self.log.info("Removed %s", path)


class InstallNativeKernelSpec(JupyterApp):
    version = __version__
    description = """[DEPRECATED] Install the IPython kernel spec directory for this Python."""
    kernel_spec_manager = Instance(KernelSpecManager)

    def _kernel_spec_manager_default(self):
        return KernelSpecManager(data_dir=self.data_dir)

    user = Bool(False, config=True,
        help="""
        Try to install the kernel spec to the per-user directory instead of
        the system or environment directory.
        """
    )

    flags = {'user': ({'InstallNativeKernelSpec': {'user': True}},
                "Install to the per-user kernel registry"),
             'debug': base_flags['debug'],
            }

    def start(self):
        self.log.warning("`jupyter kernelspec install-self` is DEPRECATED as of 4.0."
            " You probably want `ipython kernel install` to install the IPython kernelspec.")
        try:
            from ipykernel import kernelspec
        except ImportError:
            print("ipykernel not available, can't install its spec.", file=sys.stderr)
            self.exit(1)
        try:
            kernelspec.install(self.kernel_spec_manager, user=self.user)
        except OSError as e:
            if e.errno == errno.EACCES:
                print(e, file=sys.stderr)
                if not self.user:
                    print("Perhaps you want to install with `sudo` or `--user`?", file=sys.stderr)
                self.exit(1)
            self.exit(e)

class KernelSpecApp(Application):
    version = __version__
    name = "jupyter kernelspec"
    description = """Manage Jupyter kernel specifications."""

    subcommands = Dict({
        'list': (ListKernelSpecs, ListKernelSpecs.description.splitlines()[0]),
        'install': (InstallKernelSpec, InstallKernelSpec.description.splitlines()[0]),
        'uninstall': (RemoveKernelSpec, "Alias for remove"),
        'remove': (RemoveKernelSpec, RemoveKernelSpec.description.splitlines()[0]),
        'install-self': (InstallNativeKernelSpec, InstallNativeKernelSpec.description.splitlines()[0]),
    })

    aliases = {}
    flags = {}

    def start(self):
        if self.subapp is None:
            print("No subcommand specified. Must specify one of: %s"% list(self.subcommands))
            print()
            self.print_description()
            self.print_subcommands()
            self.exit(1)
        else:
            return self.subapp.start()


if __name__ == '__main__':
    KernelSpecApp.launch_instance()
