#!/usr/bin/env python3
# SPDX-FileCopyrightInfo: Copyright © DUNE Project contributors, see file LICENSE.md in module root
# SPDX-License-Identifier: LicenseRef-GPL-2.0-only-with-DUNE-exception

try:
    import skbuild
except ImportError:
    print("skbuild needed for packaging, run 'pip install scikit-build'")
    import sys
    sys.exit(0)

import sys, os, io, getopt, re, shutil
import importlib, subprocess
import email.utils
import pkg_resources
from datetime import date

# make sure that 'metadata' is taken from the current `dune-common` folder
# and not some installed version which might be different from the one I'm
# packaging (by mistake). The path to `packagemetadata.py` needs to be
# added to the python path (to work here) and to the environment so that a
# later call to `python setup.py` also works.
here = os.path.dirname(os.path.abspath(__file__))
mods = os.path.join(here, "..", "python", "dune")
sys.path.append(mods)
pythonpath  = mods + ":" + os.environ.get('PYTHONPATH','.')
os.environ['PYTHONPATH'] = pythonpath
try:
    from packagemetadata import metaData
except ImportError:
    # not calling from within a dune-common source module so use installed
    # version after all
    from dune.packagemetadata import metaData

def main(argv):

    repositories = ["gitlab", "testpypi", "pypi"]
    def usage():
        return 'usage: dunepackaging.py [--upload <'+"|".join(repositories)+'> | -c | --clean | --version <version> | --onlysdist | --bdist_conda]'

    try:
        opts, args = getopt.getopt(argv, "hc", ["upload=", "clean", "version=", "onlysdist", "bdist_conda"])
    except getopt.GetoptError:
        print(usage())
        sys.exit(2)

    upload = False
    repository = "gitlab"
    clean = False
    version = None
    onlysdist = False
    bdistconda = False
    for opt, arg in opts:
        if opt == '-h':
            print(usage())
            sys.exit(2)
        elif opt in ("--upload"):
            upload = True
            if arg != '':
                repository = arg
                if repository not in repositories:
                    print("Specified repository must be one of: " + " ".join(repositories))
                    sys.exit(2)
        elif opt in ("-c", "--clean"):
            clean = True
        elif opt in ("--version"):
            version = arg
        elif opt in ("--onlysdist"):
            onlysdist = True
        elif opt in ("--bdist_conda"):
            onlysdist  = True
            bdistconda = True

    # Remove generated files
    def removeFiles():
        import glob
        files = ['MANIFEST', 'dist', '_skbuild', '__pycache__']
        print("Remove generated files: " + ", ".join(files))
        remove = ['rm', '-rf'] + files
        subprocess.call(remove)
        # checkout setup.py and pyproject.toml
        checkout = ['git', 'checkout', 'setup.py', 'pyproject.toml']
        subprocess.call(checkout)

    if clean:
        removeFiles()
        sys.exit(0)

    data, cmake_flags = metaData(version, dependencyCheck=False)

    if version is None:
        version = data.version

    # Generate setup.py
    print("Generate setup.py")
    f = open("setup.py", "w")
    f.write("""#
# DO NOT MODIFY THIS FILE!
# This file is autogenerated by the `dunepackaging.py` script and
# only used for the pypi dune packages. This file will not be included in
# the build directory.
#
# See https://www.dune-project.org/dev/adding_python/ for docs on
# Python packaging for Dune modules.
#
""")
    f.write("import os, sys\n")
    if data.name == 'dune-common':
        f.write("here = os.path.dirname(os.path.abspath(__file__))\n")
        f.write("mods = os.path.join(here, \"python\", \"dune\")\n")
        f.write("sys.path.append(mods)\n\n")
    f.write("try:\n")
    f.write("    from dune.packagemetadata import metaData\n")
    f.write("except ImportError:\n")
    f.write("    from packagemetadata import metaData\n")
    f.write("from skbuild import setup\n")

    f.write("setup(**metaData('"+version+"')[1])\n")
    f.close()

    # Generate pyproject.toml
    print("Generate pyproject.toml")
    f = open("pyproject.toml", "w")
    f.write("""#
# DO NOT MODIFY THIS FILE!
# This file is autogenerated by the `dunepackaging.py` script and
# only used for the pypi dune packages. This file will not be included in
# the build directory.
#
# See https://www.dune-project.org/dev/adding_python/ for docs on
# Python packaging for Dune modules.
#
# This is uses the `Python-Requires` field in the `dune.modules` file to
# populate the `requires` entry. Additional packages needed for the package
# build should be added in the `dune.modules`. These packages will then also be
# included in the package install from source.
#
""")
    requires = data.asPythonRequirementString(data.depends + data.python_requires)
    requires = list(set(requires)) # make requirements unique
    minimal = ["pip", "setuptools", "wheel", "scikit-build", "cmake>=3.16", "ninja", "requests"]
    requires += [r for r in minimal if not any([a.startswith(r) for a in requires])]
    requires.sort()
    f.write("[build-system]\n")
    f.write("requires = "+requires.__str__()+"\n")
    f.write("build-backend = 'setuptools.build_meta'\n")
    f.close()

    # Create source distribution and upload to repository
    python = sys.executable
    if upload or onlysdist:
        print("Remove dist")
        remove = ['rm', '-rf', 'dist']
        subprocess.call(remove)

        # check if we have scikit-build
        import pkg_resources
        installed = {pkg.key for pkg in pkg_resources.working_set}
        if not 'scikit-build' in installed:
            print("Please install the pip package 'scikit-build' to build the source distribution.")
            sys.exit(2)

        # append hash of current git commit to README
        shutil.copy('README.md', 'tmp_README.md')
        githash = ['git', 'rev-parse', 'HEAD']
        hash = subprocess.check_output(githash, encoding='UTF-8')
        with open("README.md", "a") as f:
            f.write("\n\ngit-" + hash)

        print("Create source distribution")
        # make sure setup.py/pyproject.toml are tracked by git so that
        # they get added to the package by scikit
        gitadd = ['git', 'add', 'setup.py', 'pyproject.toml']
        subprocess.call(gitadd)
        # run sdist
        build = [python, 'setup.py', 'sdist']
        subprocess.call(build, stdout=subprocess.DEVNULL)
        # undo the above git add
        gitreset = ['git', 'reset', 'setup.py', 'pyproject.toml']
        subprocess.call(gitreset)

        # restore README.md
        shutil.move('tmp_README.md', 'README.md')

        if not onlysdist:
            # check if we have twine
            import pkg_resources
            installed = {pkg.key for pkg in pkg_resources.working_set}
            if not 'twine' in installed:
                print("Please install the pip package 'twine' to upload the source distribution.")
                sys.exit(2)

            twine = [python, '-m', 'twine', 'upload']
            twine += ['--repository', repository]
            twine += ['dist/*']
            subprocess.call(twine)

            removeFiles()

        # create conda package meta.yaml (experimental)
        if bdistconda:
            import hashlib
            remove = ['rm', '-rf', 'dist/'+data.name]
            subprocess.call(remove)
            mkdir  = ['mkdir', 'dist/'+data.name ]
            subprocess.call(mkdir)

            print("Create bdist_conda (experimental)")
            distfile = 'dist/'+data.name+'-'+version+'.tar.gz'
            datahash = ''
            with open(distfile, "rb") as include:
                source = include.read()
                datahash = hashlib.sha256( source ).hexdigest()

            print("Generate ",'dist/'+data.name+'/meta.yaml')
            f = open('dist/'+data.name+'/meta.yaml', "w")
            f.write('{% set name = "' + data.name + '" %}\n')
            f.write('{% set version = "' + version + '" %}\n')
            f.write('{% set hash = "' + datahash + '" %}\n\n')
            f.write('package:\n')
            f.write('  name: "{{ name|lower }}"\n')
            f.write('  version: "{{ version }}"\n\n')
            f.write('source:\n')
            f.write('  path: ../{{ name }}-{{ version }}/\n')
            f.write('  sha256: {{ hash }}\n\n')
            f.write('build:\n')
            f.write('  number: 1\n')
            if 'TMPDIR' in os.environ:
                f.write('  script_env:\n')
                f.write('    - TMPDIR=' + os.environ['TMPDIR'] +'\n')
            f.write('  script: "{{ PYTHON }} -m pip install . --no-deps --ignore-installed -vv "\n\n')
            f.write('requirements:\n')

            requirements = ['pip', 'python', 'mkl', 'tbb', 'intel-openmp',
                            'libgcc-ng', 'libstdcxx-ng', 'gmp', 'scikit-build',
                            'mpi4py', 'matplotlib', 'numpy', 'scipy', 'ufl']

            for dep in data.depends:
                requirements += [dep[0]]

            f.write('  host:\n')
            for dep in requirements:
                f.write('    - ' + dep + '\n')

            f.write('\n')
            f.write('  run:\n')
            for dep in requirements:
                f.write('    - ' + dep + '\n')

            f.write('\n')
            f.write('test:\n')
            f.write('  imports:\n')
            f.write('    - ' + data.name.replace('-','.') + '\n\n')
            f.write('about:\n')
            f.write('  home: '+data.url+'\n')
            f.write('  license: GPLv2 with linking exception.\n')
            f.write('  license_family: GPL\n')
            f.write('  summary: '+data.description+'\n')
            f.close()

if __name__ == "__main__":
    main(sys.argv[1:])
