#!/usr/bin/env python3

__author__    = 'RADICAL-Cybertools Team'
__email__     = 'info@radical-cybertools.org'
__copyright__ = 'Copyright 2013-23, The RADICAL-Cybertools Team'
__license__   = 'MIT'


''' Setup script, only usable via pip. '''

import os
import stat
import sys
import sysconfig

import subprocess as sp

from glob       import glob
from setuptools import setup, Command, find_namespace_packages

from distutils.command.build_scripts import build_scripts as _build_scripts


base     = 'edge'
name     = 'radical.%s'      % base
mod_root = 'src/radical/%s/' % base

scripts  = list(glob('bin/*'))
root     = os.path.dirname(__file__) or '.'
readme   = open('%s/README.md' % root, encoding='utf-8').read()
descr    = 'RADICAL Edge connects external RCT applications with HPC resources'
keywords = ['radical', 'cybertools', 'HPC', 'edge', 'distributed']

share    = 'share/%s' % name
data     = [('%s/examples'      % share, glob('examples/*.{py,cfg,json,sh}')),
            ('%s/examples/zmq'  % share, glob('examples/zmq/*.md'          )),
            ('%s/examples/zmq'  % share, glob('examples/zmq/queue/*'       )),
            ('%s/examples/zmq'  % share, glob('examples/zmq/pubsub/*'      ))
]


def sh_callout(cmd):
    p = sp.Popen(cmd, stdout=sp.PIPE, stderr=sp.PIPE, shell=True)
    stdout, stderr = p.communicate()
    ret            = p.returncode
    return stdout, stderr, ret


def get_version(_mod_root):
    '''
    Read the base version from the repo-level VERSION file, optionally
    enrich it with git metadata, then write the result into a generated
    ``_version.py`` inside the package.  That file is git-ignored so the
    source tree is never dirtied; ``__init__.py`` imports from it at
    runtime.
    '''

    _out = None
    _err = None
    _ret = None
    try:
        _version_path   = '%s/%s/_version.py' % (root, _mod_root)
        _version_base   = None
        _version_detail = None

        # get `version_base` from distribution's 'VERSION' file
        with open('%s/VERSION' % root, 'r', encoding='utf-8') as fin:
            _version_base = fin.readline().strip()

        _, _, ret = sh_callout('cd %s && git rev-parse --git-dir && which git'
                               % root)
        _in_git = (ret == 0)

        if _in_git:

            # get details from git
            _out, _err, _ret = sh_callout(
                'cd %s && git describe --tags --always' % root)
            assert _ret == 0, 'git describe failed'
            _out = _out.decode().strip()

            _version_tag = _out

            _out, _err, _ret = sh_callout(
                'cd %s && git branch --show-current' % root)
            assert _ret == 0, 'git branch failed'
            _out = _out.decode().strip()

            _version_branch = _out or 'detached'
            _version_branch = _version_branch.replace('detached from ', '~')

            if _version_tag:
                _version_detail = '%s-%s@%s' % (_version_base, _version_tag,
                                                _version_branch)
            else:
                _version_detail = '%s@%s' % (_version_base, _version_branch)

        # Write _version.py — this file is git-ignored
        _version_string = _version_detail or _version_base

        with open(_version_path, 'w', encoding='utf-8') as fout:
            fout.write("# This file is auto-generated by setup.py — do not edit.\n")
            fout.write("version        = '%s'\n" % _version_base)
            fout.write("version_detail = '%s'\n" % _version_string)

        return _version_base, _version_path

    except Exception as e:
        _msg = 'Could not extract/set version: %s' % e
        if _ret:
            _msg += '\n' + _out + '\n\n' + _err
        raise RuntimeError(_msg) from e


# get version info — this will create _version.py in the package dir
version, version_path = get_version(mod_root)


class RunTwine(Command):
    user_options = []
    def initialize_options(self): pass
    def finalize_options(self):   pass
    def run(self):
        _, _, _ret = sh_callout('python3 setup.py sdist upload -r pypi')
        raise SystemExit(_ret)


class BuildScripts(_build_scripts):
    """Generate wrapper scripts from .sh.in templates at install time.

    Substitutes @PYTHON@, @SITEPKGS@, and @BINDIR@ with the values captured
    from the active Python environment at install time, then delegates to the
    standard build_scripts for all other scripts.
    """

    def run(self):
        # Determine install-time Python paths.
        # If a virtualenv is active (VIRTUAL_ENV env var is set), use it.
        # This handles pip build-isolation where sys.executable points to the
        # isolated build venv rather than the user's venv.
        venv = os.environ.get('VIRTUAL_ENV')
        if venv:
            py_ver  = '%d.%d' % sys.version_info[:2]
            python   = os.path.join(venv, 'bin', 'python3')
            bindir   = os.path.join(venv, 'bin')
            sitepkgs = os.path.join(venv, 'lib', 'python' + py_ver,
                                    'site-packages')
        else:
            python   = sys.executable
            sitepkgs = sysconfig.get_path('purelib')
            bindir   = os.path.dirname(python)

        self.mkpath(self.build_dir)

        remaining = []
        for src in self.scripts:
            if src.endswith('.sh.in'):
                dst_name = os.path.basename(src)[:-3]  # strip .in
                dst      = os.path.join(self.build_dir, dst_name)
                with open(src, 'r', encoding='utf-8') as fh:
                    content = fh.read()
                content = (content
                           .replace('@PYTHON@',   python)
                           .replace('@SITEPKGS@', sitepkgs)
                           .replace('@BINDIR@',   bindir))
                with open(dst, 'w', encoding='utf-8') as fh:
                    fh.write(content)
                os.chmod(dst, (stat.S_IRWXU |
                               stat.S_IRGRP | stat.S_IXGRP |
                               stat.S_IROTH | stat.S_IXOTH))
            else:
                remaining.append(src)

        self.scripts = remaining
        _build_scripts.run(self)


with open('%s/requirements.txt' % root, encoding='utf-8') as freq:
    requirements = freq.readlines()


setup_args = {
    'name'               : name,
    'version'            : version,
    'description'        : descr,
    'long_description'   : readme,
    'long_description_content_type' : 'text/markdown',
    'author'             : 'RADICAL Group at Rutgers University',
    'author_email'       : 'radical@rutgers.edu',
    'maintainer'         : 'The RADICAL Group',
    'maintainer_email'   : 'radical@rutgers.edu',
    'url'                : 'http://radical-cybertools.github.io/%s/' % name,
    'project_urls'       : {
        'Documentation': 'https://radical%s.readthedocs.io/en/latest/' % base,
        'Source'       : 'https://github.com/radical-cybertools/%s/'   % name,
        'Issues' : 'https://github.com/radical-cybertools/%s/issues'   % name,
    },
    'license'            : 'MIT',
    'keywords'           : keywords,
    'python_requires'    : '>=3.10',
    'classifiers'        : [
        'Development Status :: 4 - Beta',
        'Intended Audience :: Developers',
        'Environment :: Console',
        'License :: OSI Approved :: MIT License',
        'Programming Language :: Python',
        'Programming Language :: Python :: 3',
        'Programming Language :: Python :: 3.10',
        'Programming Language :: Python :: 3.11',
        'Programming Language :: Python :: 3.12',
        'Topic :: Utilities',
        'Topic :: System :: Distributed Computing',
        'Topic :: Scientific/Engineering',
        'Operating System :: MacOS :: MacOS X',
        'Operating System :: POSIX',
        'Operating System :: Unix'
    ],
    'packages'           : find_namespace_packages('src', include=['radical.*']),
    'package_dir'        : {'': 'src'},
    'scripts'            : scripts,
    'package_data'       : {'': ['*.txt', '*.sh', '*.json', '*.gz', '*.c',
                                 '*.md', '*.html'],
                            'radical.edge': ['data/*.html', 'data/*.json',
                                             'data/plugins/*.js']},
    'install_requires'   : requirements,
    'zip_safe'           : False,
    'data_files'         : data,
    'cmdclass'           : {'upload'        : RunTwine,
                            'build_scripts' : BuildScripts},
}


setup(**setup_args)


# clean temporary files from source tree
os.system('rm -vrf src/%s.egg-info' % name)

