# ptufile/setup.py

"""Ptufile package Setuptools script."""

import os
import re
import sys
import sysconfig

import numpy
from setuptools import Extension, setup

DEBUG = bool(os.environ.get('CG_DEBUG', ''))
LIMITED_API = os.environ.get('CG_LIMITED_API', '1').lower() in ('1', 'true')
LLVM_PATH = os.environ.get('CG_LLVM_PATH', '')

if LLVM_PATH:
    # Redirect the MSVC compiler class to use clang-cl/lld-link.
    # setuptools on Windows ignores CC; patching MSVCCompiler.initialize
    # is the only reliable way to swap the compiler executable.
    import importlib
    from collections.abc import Callable
    from typing import Any

    clang_cl = os.path.join(LLVM_PATH, 'bin', 'clang-cl.exe')
    lld_link = os.path.join(LLVM_PATH, 'bin', 'lld-link.exe')

    for modname in (
        'setuptools._distutils._msvccompiler',
        'distutils._msvccompiler',
    ):
        try:
            mod = importlib.import_module(modname)

            def make_clang_init(
                orig: Callable[..., None], cc: str, linker: str
            ) -> Callable[..., None]:
                """Return patched MSVCCompiler.initialize method using LLVM."""

                def clang_init(
                    self: Any, plat_name: str | None = None
                ) -> None:
                    orig(self, plat_name)
                    self.cc = cc
                    self.linker = linker
                    # remove MSVC flags unsupported by clang-cl
                    for attr in ('compile_options', 'compile_options_debug'):
                        opts = getattr(self, attr, None)
                        if opts is not None:
                            setattr(
                                self,
                                attr,
                                [f for f in opts if f != '/GL'],
                            )

                return clang_init

            mod.MSVCCompiler.initialize = make_clang_init(
                mod.MSVCCompiler.initialize, clang_cl, lld_link
            )
        except (ImportError, AttributeError):
            pass

if LIMITED_API and not sysconfig.get_config_var('Py_GIL_DISABLED'):
    py_limited_api = True
    define_macros = [
        ('Py_LIMITED_API', 0x030C0000),
        ('CYTHON_LIMITED_API', '1'),
    ]
    setup_options = {'bdist_wheel': {'py_limited_api': 'cp312'}}
else:
    py_limited_api = False
    define_macros = []
    setup_options = {}


def search(pattern: str, string: str, flags: int = 0) -> str:
    """Return first match of pattern in string."""
    match = re.search(pattern, string, flags)
    if match is None:
        msg = f'{pattern=!r} not found'
        raise ValueError(msg)
    return match.groups()[0]


def fix_docstring_examples(docstring: str) -> str:
    """Return docstring with examples fixed for GitHub."""
    start = True
    indent = False
    lines = ['..', '  This file is generated by setup.py', '']
    for line in docstring.splitlines():
        if not line.strip():
            start = True
            indent = False
        if line.startswith('>>> '):
            indent = True
            if start:
                lines.extend(['.. code-block:: python', ''])
                start = False
        lines.append(('    ' if indent else '') + line)
    return '\n'.join(lines)


with open('ptufile/ptufile.py', encoding='utf-8') as fh:
    code = fh.read()

version = search(r"__version__ = '(.*?)'", code).replace('.x.x', '.dev0')

description = search(r'"""(.*)\.(?:\r\n|\r|\n)', code)

readme = search(
    r'(?:\r\n|\r|\n){2}"""(.*)"""(?:\r\n|\r|\n){2}from __future__',
    code,
    re.MULTILINE | re.DOTALL,
)
readme = '\n'.join(
    [description, '=' * len(description), *readme.splitlines()[1:]]
)

if 'sdist' in sys.argv:
    # update README, LICENSE, and CHANGES files

    with open('README.rst', 'w', encoding='utf-8', newline='\n') as fh:
        fh.write(fix_docstring_examples(readme))

    license = search(
        r'(# Copyright.*?(?:\r\n|\r|\n))(?:\r\n|\r|\n)+""',
        code,
        re.MULTILINE | re.DOTALL,
    )
    license = license.replace('# ', '').replace('#', '')

    with open('LICENSE', 'w', encoding='utf-8', newline='\n') as fh:
        fh.write('BSD-3-Clause license\n\n')
        fh.write(license)

    revisions = search(
        r'(?:\r\n|\r|\n){2}(Revisions.*)- …',
        readme,
        re.MULTILINE | re.DOTALL,
    ).strip()

    with open('CHANGES.rst', encoding='utf-8') as fh:
        old = fh.read()

    old = old.split(revisions.splitlines()[-1])[-1]
    with open('CHANGES.rst', 'w', encoding='utf-8', newline='\n') as fh:
        fh.write(revisions.replace('---------', '=========').strip())
        fh.write(old)

ext_modules = [
    Extension(
        'ptufile._ptufile',
        ['ptufile/_ptufile.pyx'],
        define_macros=[
            *define_macros,
            # ('CYTHON_TRACE_NOGIL', '1'),
            ('NPY_NO_DEPRECATED_API', 'NPY_2_0_API_VERSION'),
        ],
        py_limited_api=py_limited_api,
        extra_compile_args=(
            ['/Zi', '/Od'] if DEBUG else ['/GS-'] if LLVM_PATH else []
        ),
        extra_link_args=['-debug:full'] if DEBUG else [],
        include_dirs=[numpy.get_include()],
    )
]

setup(
    name='ptufile',
    version=version,
    license='BSD-3-Clause',
    description=description,
    long_description=readme,
    long_description_content_type='text/x-rst',
    author='Christoph Gohlke',
    author_email='cgohlke@cgohlke.com',
    url='https://www.cgohlke.com',
    project_urls={
        'Bug Tracker': 'https://github.com/cgohlke/ptufile/issues',
        'Source Code': 'https://github.com/cgohlke/ptufile',
        'Documentation': 'https://www.cgohlke.com/docs/ptufile/',
    },
    packages=['ptufile'],
    package_data={'ptufile': ['py.typed']},
    entry_points={'console_scripts': ['ptufile = ptufile.ptufile:main']},
    python_requires='>=3.12',
    install_requires=['numpy>=2.1'],
    extras_require={
        'all': [
            'xarray',
            'tifffile',
            'matplotlib',
            'python-dateutil',
            'numcodecs',
        ],
    },
    ext_modules=ext_modules,
    options=setup_options,
    platforms=['any'],
    classifiers=[
        'Development Status :: 4 - Beta',
        'Intended Audience :: Science/Research',
        'Intended Audience :: Developers',
        'Operating System :: OS Independent',
        'Programming Language :: Cython',
        'Programming Language :: Python :: 3 :: Only',
        'Programming Language :: Python :: 3.12',
        'Programming Language :: Python :: 3.13',
        'Programming Language :: Python :: 3.14',
        'Programming Language :: Python :: 3.15',
    ],
)
