#!/usr/bin/python3
"""
Module file handling for Fortran

Copyright (C) 2025 Alastair McKinstry <mckinstry@debian.org>
Released under the GPL-3 Gnu Public License.
"""

import click
import magic
import os
import dhfortran.debhelper as dh
import dhfortran.cli as cli
import dhfortran.compilers as cmplrs
import gzip




def which_compiler_flavor_and_mod_version(modfile: str):
    """For a modfile (path), return a tuple of strings for the compiler and modversion"""

    compiler = ""
    mod_version = ""

    # Helper
    def get_first_line(modfile):
        try:
            magick = magic.from_file(modfile)
        except Exception as ex:
            raise Exception(f"Can't open modfile {modfile}: {ex}")
        if magick.startswith("gzip compressed"):
            with gzip.open(modfile, "rt") as f:
                return f.readline()
        # TODO: Other case(s)

    match get_first_line(modfile):
        case line if line.startswith("GFORTRAN"):
            mod_version = line.split()[3][1:-1]
            if line.find("created from flang") != -1:
                compiler = "flangext"
            else:
                compiler = "gfortran"
        case line if line.startswith("LCompilers Modfile"):
            version = line[18:]  # eg '0.45.0'
            compiler, mod_version = "lfortran", "0"
        case line if line.startswith("V35"):
            compiler, mod_version = "nvhpc", "35"
        case line if line.startswith("V"):
            # could also be PGI, but we don't ship PGI on Debian ... its an old flang
            compiler = "flang"
            mod_version = line.split()[0][1:]
        case line if line.startswith("!mod$"):
            compiler = ("flang",)
            mod_version = line.split()[1][1:]
        case _:
            compiler, mod_version = "unknown", "0"

        # mime = magick.split(";")[0]

        # if (mime == "test/plain") or (mime == "text/x-c++"):
        #     with open(modfile) as mf:
        #         strings = mf.readstrings()
        # else:
        #     if mime == "application/gzip":
        #         pass

    return (compiler, mod_version)


##
## Modfile-specific interface for debhelper
##
class ModFileHelper(dh.DhFortranHelper):

    def __init__(self, options):
        super().__init__(options, "fortran-mod", "dh_fortran_mod")
        # For tracking which compilers we need
        self.modversions = {}

    def compute_dest(self, modfile, target_dest=None):
        """Where does modfile go ?"""
        # cli.verbose_print("compute_dest [mod]  {modfile} {target_dest}")
        # Should be called by base DebHelper() to move files
        cmplr, version = which_compiler_flavor_and_mod_version(modfile)
        fmoddir = f"/usr/lib/{cmplrs.multiarch}/fortran/{cmplr}-mod-{version}"
        if target_dest is None:
            return fmoddir
        if target_dest.startswith("/"):
            return target_dest
        else:
            return f"{fmoddir}/{target_dest}"

    def process_file(self, pkg, oldfile, target_pkg, target_dest=None):
        """Called by DebHelper
        Module file gets copied; metadata gets stored"""
        cli.verbose_print(
            f"DEBUG: process_file [module]  name {oldfile} target_pkg {target_pkg} dest {target_dest}"
        )
        compiler, version = which_compiler_flavor_and_mod_version(oldfile)
        if pkg not in self.modversions:
            self.modversions[pkg] = set()
        self.modversions[pkg].add(f"{compiler}-mod-{version}")
        dest = self.compute_dest(oldfile, target_dest)
        self.install_dir(f"{target_pkg}/{dest}")
        self.doit(["cp", oldfile, f"{target_pkg}/{dest}"])


@click.command(
    context_settings=dict(
        ignore_unknown_options=True,
    )
)
@click.argument("files", nargs=-1, type=click.UNPROCESSED)
@cli.debhelper_common_args
def dh_fortran_mod(files, *args, **kwargs):
    """
        B<dh_fortran_mod> is a debhelper program that finds Fortran module and submodule files and
    adds dependencies to B<gfortran-$version> as required to the package using
    via the variable B<${fortran:Depends}>.

    B<dh_fortran_mod> is expected to be automatically added using the debhelper "addon" B<fortran_mod>
    ie. either automatically, by build-depending on 'dh-sequence-fortran-mod', or explicitly:

        dh $@ --with fortran


    B<dh_fortran_mod>Searches the debian/ directory for files B<debian/pkg.fortran-mod>$ which list
    module files to include, with the same syntax as debhelper install files.

    =head1 OPTIONS

    =over 4

    =item B<--sourcedir=>I<dir>

    Look in the specified directory for files to be installed.

    Typically Fortran module files are included in library development packages.

    =back

    =head1 TODO

    Add dh-fortran-mod support for generic fortran compilers (ifx, etc).

    =over 4

    B<dh_fortran_mod> will be expanded to find mod files automatically from the I<debian/tmp> directory.
    It will enable the installation of mod files in parallel for multiple compilers.
    It will install .smod files for Fortran 2018.

    The fortran-mod file syntax follows dh_install: pairs of sources and optional target directories.
    The default directory will be $fmoddir ( /usr/lib/$multiarch/fortran/$compiler_mod_directory/)
    If the target directory is absolute (starts with a  '/'), this directory is used in the target package.
    If the target  does not absolute, it will be treated as a subdirectory of $fmoddir.

    Currently four flavours of Fortran compiler are supported: gfortran-*  ('gfortran'),
    flang-new-* ('flang') , flang-to-external-fc-* ('flangext') and lfortran ('lfortran').

    $compiler_mod_directory is based on the compiler module version: currently gfortran-mod-15 for
    gfortran-14 (and older), flang-mod-15 for flang-new-15+ and lfortran-mod-0 for lfortran.

    For flang-to-external-fc-* the version is flangext-mod-15 (assuming gfortran-14 as the external compiler);
    in principle flang-to-external-fc ('flangext' flavour) and gfortran are intercompatible but
    intermixing is avoided.

    These will be updated for incompatible compiler versions.

    Support for Makefiles and debian/rules is given in /usr/share/debhelper/dh-fortran/fortran-support.mk

    This enables, for example:

            /usr/share/debhelper/dh-fortran/fortran-support.mk
            FMODDIR:= $(call get_fmoddir, gfortran)
            FC_EXE:= $(call get_fc_exe, $(FC_DEFAULT))

    """
    cli.verbose_print(f"dh_fortran_mod called with files {files} kwargs {kwargs}")
    d = ModFileHelper(dh.build_options(**kwargs))
    d.process_and_move_files(*files)
    depends = []
    for package in d.packages:
        if package in d.modversions:
            for m in d.modversions[package]:
                comps = set()
                for c in cmplrs.compilers:
                    if cmplrs.compilers[c]["mod"] == m:
                        comps.add(c)
                depends.append("|".join(comps) + " | " + m)
            d.addsubstvar(package, "fortran:Depends", ",".join(depends))
        if d.packages[package]["config_file"] and package in d.modversions:
            # config file defined, so create cleanup script
            for modversion in d.modversions[package]:
                d.autoscript(
                    package,
                    "postrm",
                    "postrm-fortran-mod",
                    {
                        "MULTIARCH": cmplrs.multiarch,
                        "FCOMPILERMOD": modversion,
                    },
                )
    d.save_substvars()


if __name__ == "__main__":
    import pytest

    pytest.main(["tests/module.py"])
