# Copyright Spack Project Developers. See COPYRIGHT file for details.
#
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
import os

from spack_repo.builtin.build_systems import autotools, cmake, nmake
from spack_repo.builtin.build_systems.autotools import AutotoolsPackage
from spack_repo.builtin.build_systems.cmake import CMakePackage
from spack_repo.builtin.build_systems.nmake import NMakePackage

from spack.package import *


class Libxml2(AutotoolsPackage, CMakePackage, NMakePackage):
    """Libxml2 is the XML C parser and toolkit developed for the Gnome
    project (but usable outside of the Gnome platform), it is free
    software available under the MIT License."""

    homepage = "https://gitlab.gnome.org/GNOME/libxml2/-/wikis"
    url = "https://download.gnome.org/sources/libxml2/2.9/libxml2-2.9.13.tar.xz"
    list_url = "https://gitlab.gnome.org/GNOME/libxml2/-/releases"

    maintainers("AlexanderRichert-NOAA")

    def url_for_version(self, version):
        if version >= Version("2.9.13"):
            url = "https://download.gnome.org/sources/libxml2/{0}/libxml2-{1}.tar.xz"
            return url.format(version.up_to(2), version)
        return "http://xmlsoft.org/sources/libxml2-{0}.tar.gz".format(version)

    license("MIT")

    version("2.13.5", sha256="74fc163217a3964257d3be39af943e08861263c4231f9ef5b496b6f6d4c7b2b6")
    version("2.13.4", sha256="65d042e1c8010243e617efb02afda20b85c2160acdbfbcb5b26b80cec6515650")
    version("2.12.9", sha256="59912db536ab56a3996489ea0299768c7bcffe57169f0235e7f962a91f483590")
    version("2.11.9", sha256="780157a1efdb57188ec474dca87acaee67a3a839c2525b2214d318228451809f")

    # http support was deprecated in 2.13 and removed in 2.15
    # https://github.com/GNOME/libxml2/commit/b85d77d156476bcb910b2a25b21a091957d10de6
    variant("http", default=False, description="Enable HTTP support", when="@:2.14")
    variant("python", default=False, description="Enable Python support")
    variant("shared", default=True, description="Build shared library")
    variant("pic", default=True, description="Enable position-independent code (PIC)")

    conflicts("~pic+shared")

    depends_on("c", type="build")

    depends_on("pkgconfig", type="build", when="build_system=autotools")
    # conditional on non Windows, but rather than specify for each platform
    # specify for non Windows builder, which has equivalent effect
    depends_on("iconv", when="build_system=autotools")
    depends_on("zlib-api")
    depends_on("xz")

    # avoid cycle dependency for concretizer
    with when("+python"):
        extends("python")
        depends_on("python+shared~libxml2")

    # XML Conformance Test Suites
    # See https://www.w3.org/XML/Test/ for information
    resource(
        name="xmlts",
        url="https://www.w3.org/XML/Test/xmlts20080827.tar.gz",
        sha256="96151685cec997e1f9f3387e3626d61e6284d4d6e66e0e440c209286c03e9cc7",
    )

    build_system(
        conditional("nmake", when="platform=windows"), "cmake", "autotools", default="autotools"
    )

    def flag_handler(self, name, flags):
        if name == "cflags" and self.spec.satisfies("+pic"):
            flags.append(self["c"].pic_flag)
            flags.append("-DPIC")
        return (flags, None, None)

    @property
    def command(self):
        return Executable(self.prefix.bin.join("xml2-config"))

    @property
    def headers(self):
        include_dir = self.spec.prefix.include.libxml2
        hl = find_all_headers(include_dir)
        hl.directories = [include_dir, self.spec.prefix.include]
        return hl

    def patch(self):
        # Remove flags not recognized by the NVIDIA compiler
        if self.spec.satisfies("%nvhpc"):
            filter_file(
                "-pedantic -Wall -Wextra -Wshadow -Wpointer-arith "
                "-Wcast-align -Wwrite-strings -Waggregate-return "
                "-Wstrict-prototypes -Wmissing-prototypes "
                "-Wnested-externs -Winline -Wredundant-decls",
                "-Wall",
                "configure",
            )
            filter_file("-Wno-long-long -Wno-format-extra-args", "", "configure")

    def test_import(self):
        """import module test"""
        if "+python" not in self.spec:
            raise SkipTest("Package must be built with +python")

        with working_dir("spack-test", create=True):
            python("-c", "import libxml2")

    def test_xmlcatalog(self):
        """check minimal creation output"""
        path = self.prefix.bin.xmlcatalog
        if not os.path.exists(path):
            raise SkipTest("xmlcatalog is not installed")

        xmlcatalog = which(path)
        out = xmlcatalog("--create", output=str.split, error=str.split)

        expected = [r"<catalog xmlns", r'catalog"/>']
        check_outputs(expected, out)

    def test_xml2_config(self):
        """check version output"""
        path = join_path(self.prefix.bin, "xml2-config")
        if not os.path.exists(path):
            raise SkipTest("xml2-config is not installed")

        xml2_config = which(path)
        out = xml2_config("--version", output=str.split, error=str.split)
        assert str(self.spec.version) in out

    def test_xmllint(self):
        """run xmllint generation and validation checks"""
        path = self.prefix.bin.xmllint
        if not os.path.exists(path):
            raise SkipTest("xmllint is not installed")

        test_filename = "test.xml"
        xmllint = which(path)

        with test_part(self, "test_xmllint_auto", purpose="generate {0}".format(test_filename)):
            xmllint("--auto", "-o", test_filename)

        with test_part(
            self,
            "test_xmllint_validate_no_dtd",
            purpose="validate {0} without a DTD".format(test_filename),
        ):
            out = xmllint(
                "--postvalid",
                test_filename,
                output=str.split,
                error=str.split,
                fail_on_error=False,
            )

            expected = [r"validity error", r"no DTD found", r"does not validate"]
            check_outputs(expected, out)

        data_dir = self.test_suite.current_test_data_dir
        dtd_path = data_dir.join("info.dtd")

        with test_part(
            self,
            "test_xmllint_validate_with_dtd",
            purpose="validate {0} with a DTD".format(test_filename),
        ):
            out = xmllint(
                "--dtdvalid",
                dtd_path,
                test_filename,
                output=str.split,
                error=str.split,
                fail_on_error=False,
            )

            expected = [r"validity error", r"does not follow the DTD"]
            check_outputs(expected, out)

        test_filename = data_dir.join("info.xml")
        with test_part(
            self,
            "test_xmllint_validate_works",
            purpose="validate {0} with a DTD".format(test_filename),
        ):
            xmllint("--dtdvalid", dtd_path, data_dir.join("info.xml"))


class AnyBuilder(BaseBuilder):
    @run_after("install")
    @on_package_attributes(run_tests=True)
    def import_module_test(self):
        if self.spec.satisfies("+python"):
            with working_dir("spack-test", create=True):
                python("-c", "import libxml2")


class AutotoolsBuilder(AnyBuilder, autotools.AutotoolsBuilder):
    def configure_args(self):
        spec = self.spec

        args = [
            "--with-lzma={0}".format(spec["xz"].prefix),
            "--with-iconv={0}".format(spec["iconv"].prefix),
        ]

        if spec.satisfies("+python"):
            args.extend(
                [
                    "--with-python={0}".format(spec["python"].home),
                    "--with-python-install-dir={0}".format(python_platlib),
                ]
            )
        else:
            args.append("--without-python")

        args.extend(self.with_or_without("http"))

        args.extend(self.enable_or_disable("shared"))
        # PIC setting is taken care of above by self.flag_handler()
        args.append("--without-pic")

        return args


class CMakeBuilder(AnyBuilder, cmake.CMakeBuilder):
    def cmake_args(self):
        args = [
            self.define_from_variant("BUILD_SHARED_LIBS", "shared"),
            self.define_from_variant("LIBXML2_WITH_PYTHON", "python"),
            self.define_from_variant("LIBXML2_WITH_HTTP", "http"),
            self.define("LIBXML2_WITH_LZMA", True),
            self.define("LIBXML2_WITH_ZLIB", True),
            self.define("LIBXML2_WITH_TESTS", True),
        ]
        return args


class NMakeBuilder(AnyBuilder, nmake.NMakeBuilder):
    phases = ("configure", "build", "install")

    @property
    def makefile_name(self):
        return "Makefile.msvc"

    @property
    def build_directory(self):
        return windows_sfn(os.path.join(self.stage.source_path, "win32"))

    def configure(self, pkg, spec, prefix):
        with working_dir(self.build_directory):
            opts = [
                "prefix=%s" % windows_sfn(prefix),
                "compiler=msvc",
                "iconv=no",
                "zlib=yes",
                "lzma=yes",
                "lib=%s"
                % ";".join(
                    (windows_sfn(spec["zlib-api"].prefix.lib), windows_sfn(spec["xz"].prefix.lib))
                ),
                "include=%s"
                % ";".join(
                    (
                        windows_sfn(spec["zlib-api"].prefix.include),
                        windows_sfn(spec["xz"].prefix.include),
                    )
                ),
            ]
            if spec.satisfies("+python"):
                opts.append("python=yes")
            if spec.satisfies("+http"):
                opts.append("http=yes")
            cscript("configure.js", *opts)
