# Copyright 2013-2020 Lawrence Livermore National Security, LLC and other
# Spack Project Developers. See the top-level COPYRIGHT file for details.
#
# SPDX-License-Identifier: (Apache-2.0 OR MIT)

from spack import *
import glob
import os


class SuperluMt(Package):
    """SuperLU is a general purpose library for the direct solution of large,
    sparse, nonsymmetric systems of linear equations on high performance
    machines. SuperLU_MT is designed for shared memory parallel machines."""

    homepage = "http://crd-legacy.lbl.gov/~xiaoye/SuperLU/#superlu_mt"
    url      = "http://crd-legacy.lbl.gov/~xiaoye/SuperLU/superlu_mt_3.1.tar.gz"

    version('3.1', sha256='407b544b9a92b2ed536b1e713e80f986824cf3016657a4bfc2f3e7d2a76ecab6')

    variant('int64',   default=False,
            description='Build with 64 bit integers')
    variant('pic',     default=True,
            description='Build with position independent code')
    variant('blas',    default=True,
            description='Build with external BLAS library')

    # Must choose one or the other
    variant('openmp',  default=False,
            description='Build with OpenMP support')
    variant('pthread', default=True,
            description='Build with POSIX threads support')

    # NOTE: must link with a single-threaded BLAS library
    depends_on('blas', when='+blas')

    # Cannot be built in parallel
    parallel = False

    def configure(self, spec):
        # Validate chosen variants
        if '+openmp' in spec and '+pthread' in spec:
            msg = 'You cannot choose both +openmp and +pthread'
            raise RuntimeError(msg)
        if '~openmp' in spec and '~pthread' in spec:
            msg = 'You must choose either +openmp or +pthread'
            raise RuntimeError(msg)

        # List of configuration options
        config = []

        # The machine (platform) identifier to append to the library names
        if '+openmp' in spec:
            # OpenMP
            config.extend([
                'PLAT       = _OPENMP',
                'TMGLIB     = libtmglib.a',
                'MPLIB      = {0}'.format(self.compiler.openmp_flag),
                'CFLAGS     = {0}'.format(self.compiler.openmp_flag),
                'FFLAGS     = {0}'.format(self.compiler.openmp_flag),
                'LOADOPTS   += {0}'.format(self.compiler.openmp_flag)
            ])
        elif '+pthread' in spec:
            # POSIX threads
            config.extend([
                'PLAT       = _PTHREAD',
                'TMGLIB     = libtmglib$(PLAT).a',
                'MPLIB      = -lpthread'
            ])

        # The BLAS library
        # NOTE: must link with a single-threaded BLAS library
        if '+blas' in spec:
            config.extend([
                'BLASDEF    = -DUSE_VENDOR_BLAS',
                'BLASLIB    = {0}'.format(spec['blas'].libs.ld_flags)
            ])
        else:
            config.append('BLASLIB    = ../lib/libblas$(PLAT).a')

        # Generic options
        config.extend([
            # The name of the libraries to be created/linked to
            'SUPERLULIB = libsuperlu_mt$(PLAT).a',
            'MATHLIB    = -lm',
            # The archiver and the flag(s) to use when building archives
            'ARCH       = ar',
            'ARCHFLAGS  = cr',
            'RANLIB     = {0}'.format('ranlib' if which('ranlib') else 'echo'),
            # Definitions used by CPP
            'PREDEFS    = -D_$(PLAT)',
            # Compilers and flags
            'CC         = {0}'.format(os.environ['CC']),
            'CFLAGS    += $(PREDEFS)',
            'NOOPTS     = -O0',
            'FORTRAN    = {0}'.format(os.environ['FC']),
            'LOADER     = {0}'.format(os.environ['CC']),
            # C preprocessor defs for compilation
            'CDEFS      = -DAdd_'
        ])

        if '+int64' in spec:
            config.extend([
                'CFLAGS    += -D_LONGINT',
            ])

        if '+pic' in spec:
            config.extend([
                'CFLAGS     += {0}'.format(self.compiler.cc_pic_flag),
                'NOOPTS     += {0}'.format(self.compiler.cc_pic_flag),
                'FFLAGS     += {0}'.format(self.compiler.f77_pic_flag),
                'LOADOPTS   += {0}'.format(self.compiler.cc_pic_flag)
            ])

        # Write configuration options to include file
        with open('make.inc', 'w') as inc:
            for option in config:
                inc.write('{0}\n'.format(option))

    def install(self, spec, prefix):
        # Set up make include file manually
        self.configure(spec)

        # BLAS needs to be compiled separately if using internal BLAS library
        if '+blas' not in spec:
            make('blaslib')

        make()

        # Install manually
        install_tree('lib', prefix.lib)

        headers = glob.glob(join_path('SRC', '*.h'))
        mkdir(prefix.include)
        for h in headers:
            install(h, prefix.include)
