# (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', ([], {'description': '"""\n\nInstall a Python package in \'development mode\'.\n\nThis works by creating a conda.pth file in site-packages."""'}), False, 'from conda.cli.conda_argparse import ArgumentParser\n'), (61, 'conda.cli.common.add_parser_prefix', 'add_parser_prefix', (['p'], {}), False, 'from conda.cli.common import add_parser_prefix, get_prefix\n'), (65, 'conda_build.main_build.args_func', 'args_func', (['args', 'p'], {}), False, 'from conda_build.main_build import args_func\n'), (81, 'conda_build.utils.rec_glob', 'rec_glob', (['pkg_path', "['.so']"], {}), False, 'from conda_build.utils import _check_call, rec_glob\n'), (97, 'os.path.join', 'join', (['sp_dir', '"""conda.pth"""'], {}), False, 'from os.path import join, isdir, abspath, expanduser, exists\n'), (122, 'os.path.join', 'join', (['prefix', "('Lib' if sys.platform == 'win32' else 'lib/python%s' % py_ver)"], {}), False, 'from os.path import join, isdir, abspath, expanduser, exists\n'), (124, 'os.path.join', 'join', (['stdlib_dir', '"""site-packages"""'], {}), False, 'from os.path import join, isdir, abspath, expanduser, exists\n'), (132, 'os.path.join', 'join', (['path_', '"""setup.py"""'], {}), False, 'from os.path import join, isdir, abspath, expanduser, exists\n'), (149, 'conda_build.utils._check_call', '_check_call', (['cmd'], {}), False, 'from conda_build.utils import _check_call, rec_glob\n'), (167, 'conda_build.utils._check_call', '_check_call', (['cmd'], {}), False, 'from conda_build.utils import _check_call, rec_glob\n'), (181, 'os.path.join', 'join', (['sp_dir', '"""conda.pth"""'], {}), False, 'from os.path import join, isdir, abspath, expanduser, exists\n'), (182, 'os.path.join', 'join', (['sp_dir', '"""conda.pth.temp"""'], {}), False, 'from os.path import join, isdir, abspath, expanduser, exists\n'), (197, 'shutil.move', 'shutil.move', (['n_c_pth', 'o_c_pth'], {}), False, 'import shutil\n'), (201, 'conda.cli.common.get_prefix', 'get_prefix', (['args'], {}), False, 'from conda.cli.common import add_parser_prefix, get_prefix\n'), (208, 'conda.install.linked', 'linked', (['prefix'], {}), False, 'from conda.install import linked\n'), (134, 'os.path.exists', 'exists', (['setup_py'], {}), False, 'from os.path import join, isdir, abspath, expanduser, exists\n'), (202, 'os.path.isdir', 'isdir', (['prefix'], {}), False, 'from os.path import join, isdir, abspath, expanduser, exists\n'), (203, 'sys.exit', 'sys.exit', (['("""Error: environment does not exist: %s\n#\n# Use \'conda create\' to create the environment first.\n#"""\n     % prefix)'], {}), False, 'import sys\n'), (84, 'conda_build.post.mk_relative_osx', 'mk_relative_osx', (['b_file', 'build_prefix'], {}), False, 'from conda_build.post import mk_relative_osx\n'), (220, 'os.path.expanduser', 'expanduser', (['path'], {}), False, 'from os.path import join, isdir, abspath, expanduser, exists\n'), (225, 'sys.exit', 'sys.exit', (['(0)'], {}), False, 'import sys\n'), (232, 'sys.exit', 'sys.exit', (['(0)'], {}), False, 'import sys\n')]