#!/usr/bin/env python3
# This file is part of 'miniver': https://github.com/jbweston/miniver

import sys
import os
import os.path
import argparse
import tempfile
import shutil
import textwrap
from zipfile import ZipFile
from importlib.util import find_spec
from urllib.request import urlretrieve

if sys.version_info < (3, 5):
    print('Miniver needs at least Python 3.5')
    sys.exit(1)

try:
    import miniver
    _miniver_version = miniver.__version__
    del miniver
    _miniver_is_installed = True
except ImportError:
    _miniver_version = 'unknown'
    _miniver_is_installed = False

# When we fetch miniver from local files
_miniver_modules = ('_version',)


# When we fetch miniver from GitHub
_miniver_zip_url = 'https://github.com/jbweston/miniver/archive/master.zip'
_zipfile_root = 'miniver-master'  # tied to the fact that we fetch master.zip

# File templates
_setup_template = textwrap.dedent('''
    def get_version_and_cmdclass(package_path):
        """Load version.py module without importing the whole package.

        Template code from miniver
        """
        import os
        from importlib.util import module_from_spec, spec_from_file_location
        spec = spec_from_file_location('version',
                                       os.path.join(package_path, '_version.py'))
        module = module_from_spec(spec)
        spec.loader.exec_module(module)
        return module.__version__, module.cmdclass


    version, cmdclass = get_version_and_cmdclass('{package_dir}')

    setup(
        ...,
        version=version,
        cmdclass=cmdclass,
    )
''')

_static_version_template = textwrap.dedent('''\
    # -*- coding: utf-8 -*-
    # This file is part of 'miniver': https://github.com/jbweston/miniver
    #
    # This file will be overwritten by setup.py when a source or binary
    # distribution is made.  The magic value "__use_git__" is interpreted by
    # version.py.

    version = "__use_git__"

    # These values are only set if the distribution was created with 'git archive'
    refnames = "$Format:%D$"
    git_hash = "$Format:%h$"
''')

_init_template = 'from ._version import __version__'
_gitattribute_template = '{package_dir}/_static_version.py export-subst'


def _line_in_file(to_find, filename):
    """Return True if the specified line exists in the named file."""
    assert '\n' not in to_find
    try:
        with open(filename) as f:
            for line in f:
                if to_find in line:
                    return True
            return False
    except FileNotFoundError:
        return False


def _write_line(content, filename):
    assert '\n' not in content
    if not _line_in_file(content, filename):
        with open(filename, 'a') as f:
            f.write(content)


def _write_content(content, filename):
    with open(filename, 'w') as f:
        f.write(content)


def _fail(msg):
    print(msg, file=sys.stderr)
    print('Miniver was not installed', file=sys.stderr)
    sys.exit(1)


def extract_miniver_from_github():
    filename, _ = urlretrieve(_miniver_zip_url)
    z = ZipFile(filename)
    tmpdir = tempfile.mkdtemp()
    input_paths = ['/'.join((_zipfile_root, 'miniver', module+'.py'))
                   for module in _miniver_modules]
    for p in input_paths:
        z.extract(p, path=tmpdir)
    return [os.path.join(tmpdir, *p.split()) for p in input_paths]


def extract_miniver_from_local():
    return [find_spec('.' + module, package='miniver').origin
            for module in _miniver_modules]


def get_args():
    parser = argparse.ArgumentParser(
        description="Install 'miniver' into the current Python package")
    parser.add_argument("-v", "--version", action="version",
                        version=_miniver_version)
    parser.add_argument('package_directory',
                        help="Directory to install 'miniver' into.")
    return parser.parse_args().package_directory


def main():
    package_dir = get_args()
    if not os.path.isdir(package_dir):
        _fail("Directory '{}' does not exist".format(package_dir))
    if package_dir != os.path.relpath(package_dir):
        _fail("'{}' is not a relative directory".format(package_dir))

    # Get miniver files
    if _miniver_is_installed:
        miniver_paths = extract_miniver_from_local()
    else:
        miniver_paths = extract_miniver_from_github()
    output_paths = [os.path.join(package_dir, os.path.basename(path))
                    for path in miniver_paths]

    for path in output_paths:
        if os.path.exists(path):
            _fail("'{}' already exists".format(path))

    # Write content to local package directory
    for path, output_path in zip(miniver_paths, output_paths):
        shutil.copy(path, output_path)
    _write_content(_static_version_template,
                   os.path.join(package_dir, '_static_version.py'))
    _write_line(_gitattribute_template.format(package_dir=package_dir),
                '.gitattributes')
    _write_line(_init_template.format(package_dir=package_dir),
                os.path.join(package_dir, '__init__.py'))

    msg = '\n'.join(textwrap.wrap(
        "Miniver is installed into '{package_dir}/'. "
        "You still have to copy the following snippet into your 'setup.py':"
    ))
    print('\n'.join((msg, _setup_template)).format(package_dir=package_dir))


if __name__ == '__main__':
    main()