# (c) 2016 Anaconda, Inc. / https://anaconda.com
# All Rights Reserved
#
# constructor 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 os
from os.path import abspath, basename, expanduser, isdir, join
import sys

from .conda_interface import cc_platform
from .construct import parse as construct_parse, verify as construct_verify
from .fcp import main as fcp_main
from .utils import normalize_path, yield_lines

from . import __version__

DEFAULT_CACHE_DIR = os.getenv('CONSTRUCTOR_CACHE', '~/.conda/constructor')


def get_installer_type(info):
    osname, unused_arch = info['_platform'].split('-')

    os_allowed = {'linux': ('sh',), 'osx': ('sh', 'pkg'), 'win': ('exe',)}
    all_allowed = set(sum(os_allowed.values(), ('all',)))

    itype = info.get('installer_type')
    if not itype:
        return os_allowed[osname][:1]
    elif itype == 'all':
        return os_allowed[osname]
    elif itype not in all_allowed:
        all_allowed = ', '.join(sorted(all_allowed))
        sys.exit("Error: invalid installer type '%s'; allowed: %s" % (itype, all_allowed))
    elif itype not in os_allowed[osname]:
        os_allowed = ', '.join(sorted(os_allowed[osname]))
        sys.exit("Error: invalid installer type '%s' for %s; allowed: %s" %
                 (itype, osname, os_allowed))
    else:
        return itype,


def get_output_filename(info):
    try:
        return info['installer_filename']
    except KeyError:
        pass

    osname, arch = info['_platform'].split('-')
    os_map = {'linux': 'Linux', 'osx': 'MacOSX', 'win': 'Windows'}
    arch_name_map = {'64': 'x86_64', '32': 'x86'}
    ext = info['installer_type']
    return '%s-%s-%s.%s' % ('%(name)s-%(version)s' % info,
                            os_map.get(osname, osname),
                            arch_name_map.get(arch, arch),
                            ext)


def main_build(dir_path, output_dir='.', platform=cc_platform,
               verbose=True, cache_dir=DEFAULT_CACHE_DIR,
               dry_run=False, conda_exe="conda.exe"):
    print('platform: %s' % platform)
    if not os.path.isfile(conda_exe):
        sys.exit("Error: Conda executable '%s' does not exist!" % conda_exe)
    cache_dir = abspath(expanduser(cache_dir))
    try:
        osname, unused_arch = platform.split('-')
    except ValueError:
        sys.exit("Error: invalid platform string '%s'" % platform)

    construct_path = join(dir_path, 'construct.yaml')
    info = construct_parse(construct_path, platform)
    construct_verify(info)
    info['_platform'] = platform
    info['_download_dir'] = join(cache_dir, platform)
    info['_conda_exe'] = abspath(conda_exe)
    itypes = get_installer_type(info)

    if platform != cc_platform and 'pkg' in itypes and not cc_platform.startswith('osx-'):
        sys.exit("Error: cannot construct a macOS 'pkg' installer on '%s'" % cc_platform)

    if verbose:
        print('conda packages download: %s' % info['_download_dir'])

    for key in ('welcome_image_text', 'header_image_text'):
        if key not in info:
            info[key] = info['name']

    for key in ('license_file', 'welcome_image', 'header_image', 'icon_image',
                'pre_install', 'post_install', 'pre_uninstall', 'environment_file'):
        if key in info:
            info[key] = abspath(join(dir_path, info[key]))

    for key in 'specs', 'packages':
        if key not in info:
            continue
        if isinstance(info[key], str):
            info[key] = list(yield_lines(join(dir_path, info[key])))

    for key in 'channels', 'specs', 'exclude', 'packages', 'menu_packages':
        if key in info:
            # ensure strings in those lists are stripped
            info[key] = [line.strip() for line in info[key]]
            # ensure there are no empty strings
            if any((not s) for s in info[key]):
                sys.exit("Error: found empty element in '%s:'" % key)

    info['installer_type'] = itypes[0]
    fcp_main(info, verbose=verbose, dry_run=dry_run, conda_exe=conda_exe)
    if dry_run:
        print("Dry run, no installer created.")
        return

    # info has keys
    # 'name', 'version', 'channels', 'exclude',
    # '_platform', '_download_dir', '_outpath'
    # 'specs': ['python 3.5*', 'conda', 'nomkl', 'numpy', 'scipy', 'pandas',
    #           'notebook', 'matplotlib', 'lighttpd']
    # 'license_file': '/Users/kfranz/continuum/constructor/examples/maxiconda/EULA.txt'
    # '_dists': List[Dist]
    # '_urls': List[Tuple[url, md5]]

    for itype in itypes:
        if itype == 'sh':
            from .shar import create as shar_create
            create = shar_create
        elif itype == 'pkg':
            from .osxpkg import create as osxpkg_create
            create = osxpkg_create
        elif itype == 'exe':
            from .winexe import create as winexe_create
            create = winexe_create
        info['installer_type'] = itype
        info['_outpath'] = abspath(join(output_dir, get_output_filename(info)))
        create(info, verbose=verbose)
        print("Successfully created '%(_outpath)s'." % info)
    if 0:
        with open(join(output_dir, 'pkg-list.txt'), 'w') as fo:
            fo.write('# installer: %s\n' % basename(info['_outpath']))
            for dist in info['_dists']:
                fo.write('%s\n' % dist)


def main():
    import argparse

    p = argparse.ArgumentParser(
        description="build an installer from <DIRECTORY>/construct.yaml")

    p.add_argument('--debug',
                   action="store_true")

    p.add_argument('--output-dir',
                   action="store",
                   default=os.getcwd(),
                   help='path to directory in which output installer is written '
                   "to, defaults to CWD ('{}')".format(os.getcwd()),
                   metavar='PATH')

    p.add_argument('--cache-dir',
                   action="store",
                   default=DEFAULT_CACHE_DIR,
                   help='cache directory, used for downloading conda packages, '
                   'may be changed by CONSTRUCTOR_CACHE, '
                   "defaults to '{}'".format(DEFAULT_CACHE_DIR),
                   metavar='PATH')

    p.add_argument('--clean',
                   action="store_true",
                   help='clean out the cache directory and exit')

    p.add_argument('--platform',
                   action="store",
                   default=cc_platform,
                   help="the platform for which installer is for, "
                   "defaults to '{}'".format(cc_platform))

    p.add_argument('--dry-run',
                   help="solve package specs but do not create installer",
                   default=False,
                   action="store_true")

    p.add_argument('-v', '--verbose',
                   action="store_true")

    p.add_argument('-V', '--version',
                   help="display the version being used and exit",
                   action="version",
                   version='%(prog)s {version}'.format(version=__version__))

    p.add_argument('--conda-exe',
                   help="path to conda executable",
                   action="store",
                   metavar="CONDA_EXE")

    p.add_argument('dir_path',
                   help="directory containing construct.yaml",
                   action="store",
                   nargs="?",
                   default=os.getcwd(),
                   metavar='DIRECTORY')

    args = p.parse_args()

    if args.clean:
        import shutil
        cache_dir = abspath(expanduser(args.cache_dir))
        print("cleaning cache: '%s'" % cache_dir)
        if isdir(cache_dir):
            shutil.rmtree(cache_dir)
        return

    if args.debug:
        import logging
        logging.basicConfig(level=logging.DEBUG)

    dir_path = args.dir_path
    if not isdir(dir_path):
        p.error("no such directory: %s" % dir_path)

    conda_exe = args.conda_exe
    conda_exe_default_path = os.path.join(sys.prefix, "standalone_conda", "conda.exe")
    conda_exe_default_path = normalize_path(conda_exe_default_path)
    if conda_exe:
        conda_exe = normalize_path(os.path.abspath(conda_exe))
    elif args.platform != cc_platform:
        p.error("setting --conda-exe is required for building a non-native installer")
    else:
        conda_exe = conda_exe_default_path
    if not os.path.isfile(conda_exe):
        if conda_exe != conda_exe_default_path:
            p.error("file not found: %s" % args.conda_exe)
        p.error("""
no standalone conda executable was found. The
easiest way to obtain one is to install the 'conda-standalone' package.
Alternatively, you can download an executable manually and supply its
path with the --conda-exe argument. Self-contained executables can be
downloaded from https://repo.anaconda.com/pkgs/misc/conda-execs/""".lstrip())

    out_dir = normalize_path(args.output_dir)
    main_build(dir_path, output_dir=out_dir, platform=args.platform,
               verbose=args.verbose, cache_dir=args.cache_dir,
               dry_run=args.dry_run, conda_exe=conda_exe)


if __name__ == '__main__':
    main()
