# (c) Continuum Analytics, Inc. / http://continuum.io
# All Rights Reserved
#
# conda is distributed under the terms of the BSD 3-clause license.
# Consult LICENSE.txt or http://opensource.org/licenses/BSD-3-Clause.

from __future__ import absolute_import, division, print_function

import sys
from os.path import join, isdir, abspath, expanduser, exists
import shutil

from conda.cli.common import add_parser_prefix, get_prefix
from conda.cli.conda_argparse import ArgumentParser
from conda_build.main_build import args_func
from conda_build.post import mk_relative_osx
from conda_build.utils import _check_call, rec_glob

from conda.install import linked


def main():
    p = ArgumentParser(
        description="""

Install a Python package in 'development mode'.

This works by creating a conda.pth file in site-packages."""
        # TODO: Use setup.py to determine any entry-points to install.
    )

    p.add_argument(
        'source',
        action="store",
        metavar='PATH',
        nargs='+',
        help="Path to the source directory."
    )
    p.add_argument('-npf', '--no-pth-file',
                   action='store_true',
                   help=("Relink compiled extension dependencies against "
                         "libraries found in current conda env. "
                         "Do not add source to conda.pth."))
    p.add_argument('-b', '--build_ext',
                   action='store_true',
                   help=("Build extensions inplace, invoking: "
                         "python setup.py build_ext --inplace; "
                         "add to conda.pth; relink runtime libraries to "
                         "environment's lib/."))
    p.add_argument('-c', '--clean',
                   action='store_true',
                   help=("Invoke clean on setup.py: "
                         "python setup.py clean "
                         "use with build_ext to clean before building."))
    p.add_argument('-u', '--uninstall',
                   action='store_true',
                   help=("Removes package if installed in 'development mode' "
                         "by deleting path from conda.pth file. Ignore other "
                         "options - just uninstall and exit"))

    add_parser_prefix(p)
    p.set_defaults(func=execute)

    args = p.parse_args()
    args_func(args, p)


def relink_sharedobjects(pkg_path, build_prefix):
    '''
    invokes functions in post module to relink to libraries in conda env

    :param pkg_path: look for shared objects to relink in pkg_path
    :param build_prefix: path to conda environment which contains lib/. to find
        runtime libraries.

    .. note:: develop mode builds the extensions in place and makes a link to
        package in site-packages/. The build_prefix points to conda environment
        since runtime libraries should be loaded from environment's lib/. first
    '''
    # find binaries in package dir and make them relocatable
    bin_files = rec_glob(pkg_path, ['.so'])
    for b_file in bin_files:
        if sys.platform == 'darwin':
            mk_relative_osx(b_file, build_prefix)
        else:
            print("Nothing to do on Linux or Windows.")


def write_to_conda_pth(sp_dir, pkg_path):
    '''
    Append pkg_path to conda.pth in site-packages directory for current
    environment. Only add path if it doens't already exist.

    :param sp_dir: path to site-packages/. directory
    :param pkg_path: the package path to append to site-packes/. dir.
    '''
    c_file = join(sp_dir, 'conda.pth')
    with open(c_file, 'a') as f:
        with open(c_file, 'r') as cf:
            # make sure file exists, before we try to read from it hence nested
            # in append with block
            # expect conda.pth to be small so read it all in at once
            pkgs_in_dev_mode = cf.readlines()

        # only append pkg_path if it doesn't already exist in conda.pth
        if pkg_path + '\n' in pkgs_in_dev_mode:
            print("path exits, skipping " + pkg_path)
        else:
            f.write(pkg_path + '\n')
            print("added " + pkg_path)


def get_site_pkg(prefix, py_ver):
    '''
    Given the path to conda environment, find the site-packages directory

    :param prefix: path to conda environment. Look here for current
        environment's site-packages
    :returns: absolute path to site-packages directory
    '''
    # get site-packages directory
    stdlib_dir = join(prefix, 'Lib' if sys.platform == 'win32' else
                      'lib/python%s' % py_ver)
    sp_dir = join(stdlib_dir, 'site-packages')

    return sp_dir


def get_setup_py(path_):
    ''' Return full path to setup.py or exit if not found '''
    # build path points to source dir, builds are placed in the
    setup_py = join(path_, 'setup.py')

    if not exists(setup_py):
        sys.exit("No setup.py found in {0}. Exiting.".format(path_))

    return setup_py


def clean(setup_py):
    '''
    This invokes:
    $ python setup.py clean

    :param setup_py: path to setup.py
    '''
    # first call setup.py clean
    cmd = ['python', setup_py, 'clean']
    _check_call(cmd)
    print("Completed: " + " ".join(cmd))
    print("===============================================")


def build_ext(setup_py):
    '''
    Define a develop function - similar to build function
    todo: need to test on win32 and linux

    It invokes:
    $ python setup.py build_ext --inplace

    :param setup_py: path to setup.py
    '''

    # next call setup.py develop
    cmd = ['python', setup_py, 'build_ext', '--inplace']
    _check_call(cmd)
    print("Completed: " + " ".join(cmd))
    print("===============================================")


def uninstall(sp_dir, pkg_path):
    '''
    Look for pkg_path in conda.pth file in site-packages directory and remove
    it. If pkg_path is not found in conda.pth, it means package is not
    installed in 'development mode' via conda develop.

    :param sp_dir: path to site-packages/. directory
    :param pkg_path: the package path to be uninstalled.
    '''
    o_c_pth = join(sp_dir, 'conda.pth')
    n_c_pth = join(sp_dir, 'conda.pth.temp')
    found = False
    with open(n_c_pth, 'w') as new_c:
        with open(o_c_pth, 'r') as orig_c:
            for line in orig_c:
                if line != pkg_path + '\n':
                    new_c.write(line)
                else:
                    print("uninstalled: " + pkg_path)
                    found = True

    if not found:
        print("conda.pth does not contain path: " + pkg_path)
        print("package not installed via conda develop")

    shutil.move(n_c_pth, o_c_pth)


def execute(args, parser):
    prefix = get_prefix(args)
    if not isdir(prefix):
        sys.exit("""\
Error: environment does not exist: %s
#
# Use 'conda create' to create the environment first.
#""" % prefix)
    for package in linked(prefix):
        name, ver, _ = package .rsplit('-', 2)
        if name == 'python':
            py_ver = ver[:3]  # x.y
            break
    else:
        raise RuntimeError("python is not installed in %s" % prefix)

    # current environment's site-packages directory
    sp_dir = get_site_pkg(prefix, py_ver)

    for path in args.source:
        pkg_path = abspath(expanduser(path))

        if args.uninstall:
            # uninstall then exit - does not do any other operations
            uninstall(sp_dir, pkg_path)
            sys.exit(0)

        if args.clean or args.build_ext:
            setup_py = get_setup_py(pkg_path)
            if args.clean:
                clean(setup_py)
                if not args.build_ext:
                    sys.exit(0)

            # build extensions before adding to conda.pth
            if args.build_ext:
                build_ext(setup_py)

        if not args.no_pth_file:
            write_to_conda_pth(sp_dir, pkg_path)

        # go through the source looking for compiled extensions and make sure
        # they use the conda environment for loading libraries at runtime
        relink_sharedobjects(pkg_path, prefix)
        print("completed operation for: " + pkg_path)

if __name__ == '__main__':
    main()

[(23, 'conda.cli.conda_argparse.ArgumentParser', 'ArgumentParser', False, 'from conda.cli.conda_argparse import ArgumentParser\n'), (61, 'conda.cli.common.add_parser_prefix', 'add_parser_prefix', False, 'from conda.cli.common import add_parser_prefix, get_prefix\n'), (65, 'conda_build.main_build.args_func', 'args_func', False, 'from conda_build.main_build import args_func\n'), (81, 'conda_build.utils.rec_glob', 'rec_glob', False, 'from conda_build.utils import _check_call, rec_glob\n'), (97, 'os.path.join', 'join', False, 'from os.path import join, isdir, abspath, expanduser, exists\n'), (122, 'os.path.join', 'join', False, 'from os.path import join, isdir, abspath, expanduser, exists\n'), (124, 'os.path.join', 'join', False, 'from os.path import join, isdir, abspath, expanduser, exists\n'), (132, 'os.path.join', 'join', False, 'from os.path import join, isdir, abspath, expanduser, exists\n'), (149, 'conda_build.utils._check_call', '_check_call', False, 'from conda_build.utils import _check_call, rec_glob\n'), (167, 'conda_build.utils._check_call', '_check_call', False, 'from conda_build.utils import _check_call, rec_glob\n'), (181, 'os.path.join', 'join', False, 'from os.path import join, isdir, abspath, expanduser, exists\n'), (182, 'os.path.join', 'join', False, 'from os.path import join, isdir, abspath, expanduser, exists\n'), (197, 'shutil.move', 'shutil.move', False, 'import shutil\n'), (201, 'conda.cli.common.get_prefix', 'get_prefix', False, 'from conda.cli.common import add_parser_prefix, get_prefix\n'), (208, 'conda.install.linked', 'linked', False, 'from conda.install import linked\n'), (134, 'os.path.exists', 'exists', False, 'from os.path import join, isdir, abspath, expanduser, exists\n'), (202, 'os.path.isdir', 'isdir', False, 'from os.path import join, isdir, abspath, expanduser, exists\n'), (203, 'sys.exit', 'sys.exit', False, 'import sys\n'), (84, 'conda_build.post.mk_relative_osx', 'mk_relative_osx', False, 'from conda_build.post import mk_relative_osx\n'), (220, 'os.path.expanduser', 'expanduser', False, 'from os.path import join, isdir, abspath, expanduser, exists\n'), (225, 'sys.exit', 'sys.exit', False, 'import sys\n'), (232, 'sys.exit', 'sys.exit', False, 'import sys\n')]