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

from spack_repo.builtin_mock.build_systems.cmake import CMakePackage

from spack.package import *


class NetlibLapack(CMakePackage):
    """LAPACK version 3.X is a comprehensive FORTRAN library that does
    linear algebra operations including matrix inversions, least squared
    solutions to linear sets of equations, eigenvector analysis, singular
    value decomposition, etc. It is a very comprehensive and reputable
    package that has found extensive use in the scientific community.

    """

    homepage = "http://www.netlib.org/lapack/"
    url = "http://www.netlib.org/lapack/lapack-3.5.0.tgz"

    version(
        "3.8.0",
        "96591affdbf58c450d45c1daa540dbd2",
        url="http://www.netlib.org/lapack/lapack-3.8.0.tar.gz",
    )
    version("3.7.1", md5="dcdeeed73de152c4643ccc5b1aeb453c")
    version("3.7.0", md5="697bb8d67c7d336a0f339cc9dd0fa72f")
    version("3.6.1", md5="421b2cb72e15f237e144428f9c460ee0")
    version("3.6.0", md5="f2f6c67134e851fe189bb3ca1fbb5101")
    version("3.5.0", md5="b1d3e3e425b2e44a06760ff173104bdf")
    version("3.4.2", md5="61bf1a8a4469d4bdb7604f5897179478")
    version("3.4.1", md5="44c3869c38c8335c2b9c2a8bb276eb55")
    version("3.4.0", md5="02d5706ec03ba885fc246e5fa10d8c70")
    version("3.3.1", md5="d0d533ec9a5b74933c2a1e84eedc58b4")

    variant("shared", default=True, description="Build shared library version")
    variant("external-blas", default=False, description="Build lapack with an external blas")

    variant("lapacke", default=True, description="Activates the build of the LAPACKE C interface")
    variant("xblas", default=False, description="Builds extended precision routines using XBLAS")

    patch("ibm-xl.patch", when="@3.7: %xl")
    patch("ibm-xl.patch", when="@3.7: %xl_r")

    # https://github.com/Reference-LAPACK/lapack/issues/228
    # TODO: update 'when' once the version of lapack
    # containing the fix is released and added to Spack.
    patch("undefined_declarations.patch", when="@3.8.0:")

    # https://github.com/Reference-LAPACK/lapack/pull/268
    # TODO: update 'when' once the version of lapack
    # containing the fix is released and added to Spack.
    patch("testing.patch", when="@3.7.0:")

    # virtual dependency
    provides("blas", when="~external-blas")
    provides("lapack")

    depends_on("blas", when="+external-blas")
    depends_on("netlib-xblas+fortran+plain_blas", when="+xblas")
    depends_on("python@2.7:", type="test")

    # We need to run every phase twice in order to get static and shared
    # versions of the libraries. When ~shared, we run the default
    # implementations of the CMakePackage's phases and get only one building
    # directory 'spack-build-static' with -DBUILD_SHARED_LIBS:BOOL=OFF (see
    # implementations of self.build_directory and self.cmake_args() below).
    # When +shared, we run the overridden methods for the phases, each
    # running the default implementation twice with different values for
    # self._building_shared. As a result, we get two building directories:
    # 'spack-build-static' with -DBUILD_SHARED_LIBS:BOOL=OFF and
    # 'spack-build-shared' with -DBUILD_SHARED_LIBS:BOOL=ON.
    _building_shared = False

    def patch(self):
        # Fix cblas CMakeLists.txt -- has wrong case for subdirectory name.
        if self.spec.satisfies("@3.6.0:"):
            filter_file(
                "${CMAKE_CURRENT_SOURCE_DIR}/CMAKE/",
                "${CMAKE_CURRENT_SOURCE_DIR}/cmake/",
                "CBLAS/CMakeLists.txt",
                string=True,
            )

    @property
    def blas_libs(self):
        shared = True if "+shared" in self.spec else False
        query_parameters = self.spec.last_query.extra_parameters
        query2libraries = {
            tuple(): ["libblas"],
            ("c", "fortran"): ["libcblas", "libblas"],
            ("c",): ["libcblas"],
            ("fortran",): ["libblas"],
        }
        key = tuple(sorted(query_parameters))
        libraries = query2libraries[key]
        return find_libraries(libraries, root=self.prefix, shared=shared, recursive=True)

    # TUTORIAL: add a proper `lapack_lib` property, along the lines
    # of the `blas_lib` property above. The library that provides
    # the lapack API is called `liblapack`.

    @property
    def headers(self):
        include_dir = self.spec.prefix.include
        cblas_h = join_path(include_dir, "cblas.h")
        lapacke_h = join_path(include_dir, "lapacke.h")
        return HeaderList([cblas_h, lapacke_h])

    @property
    def build_directory(self):
        return join_path(
            self.stage.source_path,
            "spack-build-shared" if self._building_shared else "spack-build-static",
        )

    def cmake_args(self):
        args = ["-DBUILD_SHARED_LIBS:BOOL=" + ("ON" if self._building_shared else "OFF")]

        if self.spec.satisfies("+lapacke"):
            args.extend(["-DLAPACKE:BOOL=ON", "-DLAPACKE_WITH_TMG:BOOL=ON"])
        else:
            args.extend(["-DLAPACKE:BOOL=OFF", "-DLAPACKE_WITH_TMG:BOOL=OFF"])

        if self.spec.satisfies("@3.6.0:"):
            args.append("-DCBLAS=ON")  # always build CBLAS

        if self.spec.satisfies("%intel"):
            # Intel compiler finds serious syntax issues when trying to
            # build CBLAS and LapackE
            args.extend(["-DCBLAS=OFF", "-DLAPACKE:BOOL=OFF"])

        if self.spec.satisfies("%xl") or self.spec.satisfies("%xl_r"):
            # use F77 compiler if IBM XL
            args.extend(
                [
                    "-DCMAKE_Fortran_COMPILER=" + self.compiler.f77,
                    "-DCMAKE_Fortran_FLAGS="
                    + (" ".join(self.spec.compiler_flags["fflags"]))
                    + " -O3 -qnohot",
                ]
            )

        # deprecated routines are commonly needed by, for example, suitesparse
        # Note that OpenBLAS spack is built with deprecated routines
        args.append("-DBUILD_DEPRECATED:BOOL=ON")

        if self.spec.satisfies("+external-blas"):
            args.extend(
                [
                    "-DUSE_OPTIMIZED_BLAS:BOOL=ON",
                    "-DBLAS_LIBRARIES:PATH=" + self.spec["blas"].libs.joined(";"),
                ]
            )

        if self.spec.satisfies("+xblas"):
            args.extend(
                [
                    "-DXBLAS_INCLUDE_DIR=" + self.spec["netlib-xblas"].prefix.include,
                    "-DXBLAS_LIBRARY=" + self.spec["netlib-xblas"].libs.joined(";"),
                ]
            )

        args.append("-DBUILD_TESTING:BOOL=" + ("ON" if self.run_tests else "OFF"))

        return args

    # Build, install, and check both static and shared versions of the
    # libraries when +shared
    @when("+shared")
    def cmake(self, spec, prefix):
        for self._building_shared in (False, True):
            super().cmake(spec, prefix)

    @when("+shared")
    def build(self, spec, prefix):
        for self._building_shared in (False, True):
            super().build(spec, prefix)

    @when("+shared")
    def install(self, spec, prefix):
        for self._building_shared in (False, True):
            super().install(spec, prefix)

    @when("+shared")
    def check(self):
        for self._building_shared in (False, True):
            super().check()
