# -*- coding: utf-8 -*-
# -----------------------------------------------------------------------------
# Copyright 2016 Continuum Analytics, Inc.
#
# May be copied and distributed freely only as part of an Anaconda or
# Miniconda installation.
# -----------------------------------------------------------------------------
"""Launch applications utilities."""

# Standard library imports
import json
import os
import shutil
import subprocess
import sys
import uuid

# Third party imports
from qtpy.QtCore import QProcess

# Local imports
from anaconda_navigator.api.anaconda_api import AnacondaAPI
from anaconda_navigator.config import HOME_PATH, LINUX, MAC
from anaconda_navigator.config.base import get_conf_path, get_home_dir
from anaconda_navigator.utils.logs import logger


def is_program_installed(basename):
    """
    Return program absolute path if installed in PATH.

    Otherwise, return None
    """
    for path in os.environ["PATH"].split(os.pathsep):
        abspath = os.path.join(path, basename)
        if os.path.isfile(abspath):
            return abspath


def run_app_on_win(prefix, command, package_name, root_prefix):
    """Run application on unix system an ensure proper env activation."""
    quote = '"' if ' ' in prefix and '"' not in prefix else ''

    # Call is needed to avoid the batch script from closing after running
    # the first (environment activation) line
    cmd = (
        'call {QUOTE}{CONDA_PREFIX}/Scripts/activate{QUOTE} '
        '{QUOTE}{CONDA_PREFIX}{QUOTE}\n'
        '{QUOTE}{COMMAND}{QUOTE}\n'
    ).format(
        CONDA_PREFIX=prefix,
        COMMAND=command,
        QUOTE=quote,
    )

    suffix = 'bat'
    fpath = create_app_run_script(
        cmd,
        package_name,
        prefix,
        root_prefix,
        suffix,
    )
    CREATE_NO_WINDOW = 0x08000000
    pid = subprocess.Popen(
        fpath,
        creationflags=CREATE_NO_WINDOW,
        shell=True,
        cwd=HOME_PATH,
    ).pid
    return pid


def run_app_on_unix(prefix, command, package_name, root_prefix):
    """Run application on unix system an ensure proper env activation."""
    if command.startswith('open'):
        command = command.replace('open ', '')
        start = 'open '
    else:
        start = ''

    quote = '"' if ' ' in prefix and '"' not in prefix else ''
    cmd = (
        '#!/usr/bin/env bash\n'
        'source {QUOTE}{CONDA_PREFIX}/bin/activate{QUOTE} '
        '{QUOTE}{CONDA_PREFIX}{QUOTE}\n'
        '{START}{QUOTE}{COMMAND}{QUOTE}\n'
    ).format(
        CONDA_PREFIX=prefix,
        COMMAND=command,
        START=start,
        QUOTE=quote,
    )
    suffix = 'sh'
    fpath = create_app_run_script(
        cmd,
        package_name,
        prefix,
        root_prefix,
        suffix,
    )
    process = QProcess()
    res = process.startDetached(fpath, [], HOME_PATH)
    process.waitForStarted(1000)
    return res[-1]


def create_app_run_script(command, package_name, prefix, root_prefix, suffix):
    """Create the script to run the application and activate th eenvironemt."""
    # qtpy is adding this to env on startup and this is messing qtconsole
    # and other apps on other envs with different versions of QT
    if 'QT_API' in os.environ:
        os.environ.pop('QT_API')

    package_name = package_name or 'app'

    if root_prefix == prefix:
        scripts_path = os.path.join(get_conf_path(), 'scripts')
    else:
        scripts_path = os.path.join(
            get_conf_path(), 'scripts', prefix.split('/')[-1]
        )

    if not os.path.isdir(scripts_path):
        os.makedirs(scripts_path)
    fpath = os.path.join(scripts_path, '{0}.{1}'.format(package_name, suffix))

    with open(fpath, 'w') as f:
        f.write(command)
    os.chmod(fpath, 0o777)

    return fpath


def launch(
    prefix,
    command,
    leave_path_alone,
    working_directory=HOME_PATH,
    package_name=None,
    root_prefix=None,
):
    """Handle launching commands from projects."""
    logger.debug(str((prefix, command)))
    command = command.replace('\\', '/')
    prefix = prefix.replace('\\', '/')
    root_prefix = root_prefix.replace('\\', '/')

    pid = -1

    if os.name == 'nt' and not leave_path_alone:
        command = command.replace('/bin', '/Scripts')

    if MAC or LINUX:
        pid = run_app_on_unix(
            prefix=prefix,
            command=command,
            package_name=package_name,
            root_prefix=root_prefix
        )
    else:
        pid = run_app_on_win(
            prefix=prefix,
            command=command,
            package_name=package_name,
            root_prefix=root_prefix
        )
    return pid


def console(activate=None):
    """Open command prompt console and optionally activate the environment."""
    if os.name == 'nt':
        if activate:
            cmd = 'start cmd.exe /k activate ' + activate
        else:
            cmd = 'start cmd.exe'
        logger.debug(cmd)
        subprocess.Popen(cmd, shell=True)
    elif sys.platform == 'darwin':
        if activate:
            cmd = (
                'bash --init-file '
                '<(echo "source activate {};")'.format(activate)
            )
        else:
            cmd = 'bash'
        fname = os.path.join(get_conf_path(), 'a.tool')

        with open(fname, 'w') as f:
            f.write(cmd)
        os.chmod(fname, 0o777)

        logger.debug('open ' + fname)
        subprocess.call(['open ' + fname], shell=True)
    else:  # Linux, solaris, etc
        if is_program_installed('gnome-terminal'):
            if activate:
                cmd = [
                    'gnome-terminal',
                    '-x',
                    'bash',
                    '-c',
                    'bash --init-file'
                    ' <(echo "source activate {0};")'.format(activate),
                ]
            else:
                cmd = ['gnome-terminal', '-e', 'bash']
            logger.debug(' '.join(cmd))
            subprocess.Popen(cmd)
        elif is_program_installed('xterm'):
            if activate:
                cmd = [
                    'xterm',
                    '-e',
                    'bash --init-file'
                    ' <(echo "source activate {0};")'.format(activate),
                ]
            else:
                cmd = ['xterm']
            logger.debug(' '.join(cmd))
            subprocess.Popen(cmd)


def check_prog(prog, prefix=None):
    """Check if program exists in prefix."""
    api = AnacondaAPI()
    prefix = prefix or api.conda_get_prefix_envname(name='root')

    if prog in ['notebook', 'jupyter notebook']:
        pkgs = ['notebook', 'ipython-notebook', 'jupyter-notebook']
    elif prog in ['ipython', 'jupyter console']:
        pkgs = ['ipython', 'jupyter']
    else:
        pkgs = [prog]

    return any(
        api.conda_package_version(
            prefix=prefix, pkg=p
        ) is not None for p in pkgs
    )


def py_in_console(activate=None, prog='python'):
    """
    Run (i)python in a new console.

    It optionally run activate first on the given env name/path.
    """
    logger.debug("%s, %s", activate, prog)
    if not check_prog(prog, activate):
        raise RuntimeError(
            'Program not available in environment: %s, %s', prog, activate
        )
    if prog == 'python':
        cmd = 'python -i'
    elif prog == 'ipython':
        cmd = 'ipython -i'
    elif 'notebook' in prog:
        cmd = 'jupyter notebook'
    if os.name == 'nt':
        if activate:
            cmd = '"activate {} & {}"'.format(activate, cmd)
        else:
            cmd = '"{}"'.format(cmd)
        logger.debug('start cmd.exe /k ' + cmd)
        subprocess.Popen(
            'start cmd.exe /k {}'.format(cmd),
            shell=True,
            cwd=HOME_PATH,
        )
    elif sys.platform == 'darwin':
        if activate:
            if '/' not in activate:
                out = subprocess.check_output(['conda', 'env', 'list'])
                for line in out.decode().split('\n'):
                    # This is too fragile...!
                    if line and line.split()[0] == activate:
                        activate = line.split()[-1]
                        break
            cmd = activate + '/bin/' + cmd
        else:
            cmd = cmd
        fname = os.path.join(get_conf_path(), 'a.tool')

        command = "source activate {0};{1}".format(activate, cmd)
        with open(fname, 'w') as f:
            f.write(command)

        os.chmod(fname, 0o777)
        logger.debug('open ' + fname)
        subprocess.call(['open ' + fname], shell=True, cwd=HOME_PATH)
    else:  # linux, solaris, etc
        if activate:
            cmd = "source activate " + activate + "; " + cmd
        if is_program_installed('gnome-terminal'):
            cmd = ['gnome-terminal', '-x', 'bash', '-c', cmd]
            logger.debug(' '.join(cmd))
            subprocess.Popen(cmd, cwd=HOME_PATH)
            return
        elif is_program_installed('xterm'):
            cmd = ['xterm', '-e', cmd]
            logger.debug(' '.join(cmd))
            subprocess.Popen(cmd, cwd=HOME_PATH)
            return


def run_ex(*args):
    """Start new console window, run command (given as string list)."""
    logger.debug(str(args))
    cmd = '{0}'.format(" ".join(args))

    if os.name == 'nt':
        subprocess.Popen(['start', 'cmd.exe', '/K', cmd])

    elif sys.platform == 'darwin':
        fname = 'a.tool'
        open(fname, 'w').write(cmd)
        os.chmod(fname, 0o777)
        subprocess.call(['open ' + fname], shell=True)
        os.unlink(fname)

    else:
        terms = ['gnome-terminal', 'konsole', 'xterm']
        for term in terms:
            if is_program_installed(term):
                subprocess.Popen([term, '-e', '"{}"'.format(cmd)])
                break


def run_notebook(project_path, project=None, filename=""):
    """Start notebook server."""
    from anaconda_navigator.api import AnacondaAPI
    api = AnacondaAPI()

    if project is None:
        project = api.load_project(project_path)

    kernels_folder = os.sep.join([get_home_dir(), ".ipython", "kernels"])
    display_name = '{0} ({1})'.format(project.name, project_path)
    kernel_uuid = uuid.uuid1()
    kernel_path = os.sep.join([kernels_folder, "{name}", "kernel.json"])
    pyexec = os.sep.join([project.env_prefix(project_path), 'bin', 'python'])
    spec = {
        "argv": [pyexec, "-m", "IPython.kernel", "-f", "{connection_file}"],
        "display_name": display_name,
        "language": "python",
    }

    # Delete any other kernel sec mathching this name!
    kernels = os.listdir(kernels_folder)
    for kernel in kernels:
        path = os.sep.join([kernels_folder, kernel])
        file_path = os.sep.join([path, 'kernel.json'])

        if os.path.isfile(file_path):
            with open(file_path, 'r') as f:
                data = json.loads(f.read())

            name = data.get('display_name')
            if name is not None and project_path in name:
                shutil.rmtree(path)

    os.makedirs(os.path.split(kernel_path.format(name=kernel_uuid))[0])

    with open(kernel_path.format(name=kernel_uuid), 'w') as f:
        f.write(json.dumps(spec))

    # This is not working!
    cmd = (
        'jupyter notebook '
        '--KernelSpecManager.whitelist={0}'.format(kernel_uuid)
    )

    cmd = ('jupyter notebook')
    command = (cmd + ' ' + filename)
    logger.debug(",".join([command, project_path]))
    subprocess.Popen(command.split(), cwd=project_path)


def run_python_file(project_path, project=None, filename=""):
    """Execute python in environment."""
    from anaconda_navigator.api import AnacondaAPI
    api = AnacondaAPI()

    if project is None:
        project = api.load_project(project_path)

    cmd = os.sep.join([project.env_prefix(project_path), 'bin', 'python'])
    logger.debug(",".join([cmd, filename]))
    subprocess.Popen([cmd, filename])


def run_python_console(project_path, project=None, filename=""):
    """Execute python in env with console."""
    from anaconda_navigator.api import AnacondaAPI
    api = AnacondaAPI()

    if project is None:
        project = api.load_project(project_path)

    cmd = os.sep.join([project.env_prefix(project_path), 'bin', 'python'])
    run_ex(cmd, filename)
