#! /usr/bin/env python
# -*- coding: utf-8 -*-

###
#
# @file lapack.py
#
#  LAPACK is a software package provided by Univ. of Tennessee,
#  Univ. of California Berkeley and Univ. of Colorado Denver
#
# @version 2.3.1
# @author Julie Langou
# @author Mathieu Faverge
# @date 2010-11-15
#
###

from utils import writefile, runShellCommand, killfiles, downloader, getURLName
import sys
import os
import shutil
import framework

class Lapack(framework.Framework):
    lapackurl = "http://www.netlib.org/lapack/lapack.tgz"

    """ This class takes care of the LAPACK installation. """
    def __init__(self, config, netlib):
        print "\n","="*40
        print "  Lapack installation/verification"
        print "="*40

        self.config     = config
        self.downblas = netlib.downblas;
        self.downlapack = netlib.downlapack
        self.downcmd    = netlib.downcmd
        self.prefix     = netlib.prefix
        self.netlib     = netlib

        if self.downblas==1:
           self.config.blaslib=os.path.join(self.prefix,'lib/librefblas.a')

        if self.downlapack == 2:
            self.down_install_lapack()

        ret = self.check_lapack()

        if ret != 0:
            if self.downlapack == 1:
                self.down_install_lapack()
            else:
                print """
Please provide a working LAPACK library. If a LAPACK library is not
present on the system, the netlib LAPACK library can be automatically
downloaded and installed by adding the --downlapack flag.
Most used BLAS implementations already include the LAPACK library as
MKL, ACML, Goto, Goto2 or ATLAS. If you want to use one of these
libraries, you just have to specify correctly the --blaslib option or
you can specify where is located your own LAPACK library by using the
--lapacklib option.

With LAPACK, netlib require also the tmglib from LAPACK which is only
included in MKL and netlib LAPACK (--lapacklib=-ltmg -llapack). For
other BLAS libraries, LAPACK will be automatically downloaded from
netlib.

The LAPACK library is not needed in the case where testing is disabled
by means of the --notesting flag.


What do you want to do ?
    - d : download the netlib LAPACK
    - q : quit and try with another BLAS library or define the
      lapacklib parameter.
                """
                answer = raw_input(">[q] ")
                if answer == "d":
                    self.down_install_lapack()
                else:
                    sys.exit()

    def check_lapack(self):

        if (self.config.lapacklib == ""):
           print "LAPACK library is set to "+self.config.blaslib
        else:
           print "LAPACK library is "+self.config.lapacklib
        print "Checking if provided LAPACK works...",
        # This function simply generates a Fortran program
        # that contains few calls to LAPACK routine and then
        # checks if compilation, linking and execution are succesful

        sys.stdout.flush()
        writefile('tmpf.f',"""

      program ftest
      integer  N
      parameter (N = 1)
      double precision A(N, N), B(N)
      integer  I(N)
      integer  INFO
      B(:)   = 1
      A(:,:) = 2
      I(:)   = 0
      call cheevd( 'N', 'U', N, A, N, B, B, -1,
     $     B, -1, I, -1, INFO)
      stop
      end\n""")

        ldflg = self.config.lapacklib+' '+self.config.blaslib+' '+self.config.ldflags_fc+' -lm'
        ccomm = self.config.fc+' -o tmpf '+'tmpf.f '+ldflg
        (output, error, retz) = runShellCommand(ccomm)

        if(retz != 0):
            if self.netlib.verbose:
                print '\n\nLAPACK: provided LAPACK cannot be used! aborting...'
                print 'error is:\n','*'*40,'\n',ccomm,'\n',error,'\n','*'*40
            else:
                print "no"
            return -1;

        comm = './tmpf'
        (output, error, retz) = runShellCommand(comm)
        if(retz != 0):
            if self.netlib.verbose:
                print '\n\nLAPACK: provided LAPACK cannot be used! aborting...'
                print 'error is:\n','*'*40,'\n',comm,'\n',error,'\n','*'*40
            else:
                print "no"
            return -1;

        killfiles(['tmpf.f','tmpf'])
        print 'yes'

        print "Getting LAPACK version number...",
        # This function simply calls LAPACK ilaver routine and then
        # checks if compilation, linking and execution are succesful

        sys.stdout.flush()
        writefile('tmpf.f',"""

      PROGRAM LAPACK_VERSION
*
      INTEGER MAJOR, MINOR, PATCH
*
      CALL ILAVER ( MAJOR, MINOR, PATCH )
      WRITE(*,  FMT = 9999 ) MAJOR, MINOR, PATCH
*
 9999 FORMAT(I1,'.',I1,'.',I1)
      END\n""")

        ldflg = self.config.lapacklib+' '+self.config.blaslib+' '+self.config.ldflags_fc+' -lm'
        ccomm = self.config.fc+' -o tmpf '+'tmpf.f '+ldflg
        (output, error, retz) = runShellCommand(ccomm)

        if(retz != 0):
            if self.netlib.verbose:
                print 'error is:\n','*'*40,'\n',ccomm,'\n',error,'\n','*'*40
            killfiles(['tmpf.f'])
        else:
          comm = './tmpf'
          (output, error, retz) = runShellCommand(comm)
          if(retz != 0):
            print 'cannot get LAPACK version number.'
            if self.netlib.verbose:
                print 'error is:\n','*'*40,'\n',comm,'\n',error,'\n','*'*40
          else:
            print output,
          killfiles(['tmpf'])

        print "Checking if provided LAPACK contains functions for test works...",
        # This function simply generates a C program
        # that contains few calls to LAPACK routine and then
        # checks if compilation, linking and execution are succesful

        sys.stdout.flush()
        writefile('tmpf.f',"""
      program ftest
        double precision D(1), A(1:1), B(2)
        integer          ISEED( 4 )
        integer          INFO
        B(1)   = 1

        do  I = 1, 4
            ISEED( I ) = 1
        enddo
        call dlarnv( 1, ISEED, 1, D )
        call dlagsy( 1, 0, D, A, 1, ISEED, B, INFO )
        stop
      end\n""")

        ccomm = self.config.fc+' -o tmpf '+'tmpf.f '+ldflg
        (output, error, retz) = runShellCommand(ccomm)

        if(retz != 0):
            print 'no'
            self.netlib.needtmg = 1
        else:
            comm = './tmpf'
            (output, error, retz) = runShellCommand(comm)
            if(retz != 0):
                print 'no'
                self.netlib.needtmg = 1;
            else:
                self.netlib.needtmg = 0;
                print 'yes'
        killfiles(['tmpf.f','tmpf'])

        return 0;


    def down_install_lapack(self):

        print "The reference LAPACK library is being installed."
        sys.stdout.flush()

        savecwd = os.getcwd()

        # creating the build,lib and log dirs if don't exist
        if not os.path.isdir(os.path.join(self.prefix,'lib')):
            os.mkdir(os.path.join(self.prefix,'lib'))

        if not os.path.isdir(os.path.join(os.getcwd(),'log')):
            os.mkdir(os.path.join(os.getcwd(),'log'))

        # Check if lapack.tgz is already present in the working dir
        # otherwise download it
        if not os.path.isfile(os.path.join(os.getcwd(),getURLName(self.lapackurl))):
            print "Downloading reference LAPACK...",
            downloader(self.lapackurl,self.downcmd)
            print "done"

        # unzip and untar
        print 'Unzip and untar reference LAPACK...',
        comm = 'gunzip -f lapack.tgz'
        (output, error, retz) = runShellCommand(comm)
        if retz:
            print '\n\nLAPACK: cannot unzip lapack.tgz'
            print 'stderr:\n','*'*40,'\n',comm,'\n',error,'\n','*'*40
            sys.exit()

        comm = 'tar xf lapack.tar'
        (output, error, retz) = runShellCommand(comm)
        if retz:
            print '\n\nLAPACK: cannot untar lapack.tgz'
            print 'stderr:\n','*'*40,'\n',comm,'\n',error,'\n','*'*40
            sys.exit()
        os.remove('lapack.tar')
        print 'done'

#         # Overwrite [sd]lamch.f
#         shutil.copy(os.path.join(self.netlib.installerdir,'src/dlamch.f'),
#                     os.path.join(os.getcwd(),'lapack-3.3.0/INSTALL'))
#         shutil.copy(os.path.join(self.netlib.installerdir,'src/slamch.f'),
#                     os.path.join(os.getcwd(),'lapack-3.3.0/INSTALL'))

        # change to LAPACK dir
        os.chdir(os.path.join(os.getcwd(),'lapack-3.5.0'))

        # Write Makefile.in
        writefile('make.inc', """
# -*- Makefile -*-
####################################################################
#  LAPACK make include file.                                       #
#  LAPACK, Version 3.3.0                                           #
#  April 2009                                                      #
####################################################################
#
# See the INSTALL/ directory for more examples.
#
SHELL = /bin/sh
#
#  The machine (platform) identifier to append to the library names
#
PLAT = _LINUX
#
#  Modify the FORTRAN and OPTS definitions to refer to the
#  compiler and desired compiler options for your machine.  NOOPT
#  refers to the compiler options desired when NO OPTIMIZATION is
#  selected.  Define LOADER and LOADOPTS to refer to the loader
#  and desired load options for your machine.
#
FORTRAN  = """+self.config.fc+"""
OPTS     = """+self.config.fcflags+"""
DRVOPTS  = $(OPTS)
NOOPT    = """+self.config.noopt+"""
LOADER   = """+self.config.fc+"""
LOADOPTS = """+self.config.ldflags_fc+"""
#
# Timer for the SECOND and DSECND routines
#
# Default : SECOND and DSECND will use a call to the EXTERNAL FUNCTION ETIME
# TIMER    = EXT_ETIME
# For RS6K : SECOND and DSECND will use a call to the EXTERNAL FUNCTION ETIME_
# TIMER    = EXT_ETIME_
# For gfortran compiler: SECOND and DSECND will use a call to the INTERNAL FUNCTION ETIME
# TIMER    = INT_ETIME
# If your Fortran compiler does not provide etime (like Nag Fortran Compiler, etc...)
# SECOND and DSECND will use a call to the Fortran standard INTERNAL FUNCTION CPU_TIME
TIMER    = INT_CPU_TIME
# If neither of this works...you can use the NONE value... In that case, SECOND and DSECND will always return 0
# TIMER     = NONE
#
#  The archiver and the flag(s) to use when building archive (library)
#  If you system has no ranlib, set RANLIB = echo.
#
ARCH     = ar
ARCHFLAGS= """+self.config.arflags+"""
RANLIB   = """+self.config.ranlib+"""
#
#  The location of BLAS library for linking the testing programs.
#  The target's machine-specific, optimized BLAS library should be
#  used whenever possible.
#
BLASLIB      = """+self.config.blaslib+"""
#
#  Location of the extended-precision BLAS (XBLAS) Fortran library
#  used for building and testing extended-precision routines.  The
#  relevant routines will be compiled and XBLAS will be linked only if
#  USEXBLAS is defined.
#
# USEXBLAS    = Yes
XBLASLIB     =
# XBLASLIB    = -lxblas
#
#  Names of generated libraries.
#
LAPACKLIB    = libreflapack.a
TMGLIB       = libtmg.a
EIGSRCLIB    = libeigsrc.a
LINSRCLIB    = liblinsrc.a
""")

        log = " *******  LOG LAPACK *******"
        # compile and generate Reference BLAS library
        if self.downblas==1:
          print 'Compile and generate Reference BLAS...',
          sys.stdout.flush()
          comm = self.make+' blaslib'
          (output, error, retz) = runShellCommand(comm)
          if retz:
            print "\n\nLAPACK: cannot compile Reference BLAS"
            print "stderr:\n","*"*40,"\n",comm,'\n',error,"\n","*"*40
            sys.exit()
          log += output+error
          self.config.blaslib  = '-L'+os.path.join(self.prefix,'lib')+' -lrefblas'
          print 'done'
        
        # compile and generate LAPACK library
        print 'Compile and generate LAPACK...',
        sys.stdout.flush()
        comm = self.make+' lapacklib tmglib'
        (output, error, retz) = runShellCommand(comm)
        if retz:
            print "\n\nLAPACK: cannot compile LAPACK"
            print "stderr:\n","*"*40,"\n",comm,'\n',error,"\n","*"*40
            sys.exit()
        log += output+error
        print 'done'

        # write the log on a file
        fulllog = os.path.join(savecwd,'log/lapacklog')
        writefile(fulllog, log)
        print 'Installation of libreflapack.a successful.'
        print '(log is in ',fulllog,')'

        # move lib to the lib directory
        shutil.copy('libreflapack.a',os.path.join(self.prefix,'lib/libreflapack.a'))
        shutil.copy('libtmg.a',os.path.join(self.prefix,'lib/libtmg.a'))

        # set framework variables to point to the freshly installed LAPACK library
        self.config.lapacklib  = '-L'+os.path.join(self.prefix,'lib')+' -ltmg -lreflapack'

        if(self.netlib.testing == 1):
           print 'Compiling and Running LAPACK test suite (this will take several minutes)...',
           sys.stdout.flush()
           comm = self.make+' lapack_testing'
           (output, error, retz) = runShellCommand(comm)
           if(retz != 0):
               print '\n\nLAPACK: error running LAPACK TESTING SUITE'
               print 'stderr:\n','*'*40,'\n',error,'\n','*'*40
               sys.exit()
           log = output+error
           fulllog = os.path.join(savecwd,'log/lapack_testing')
           writefile(fulllog, log)
           print 'done'
           print '(log is in ',fulllog,')'

        os.chdir(savecwd)

        # Check if the installation is successful
        self.netlib.verbose = 1
        ret = self.check_lapack()
        self.netlib.verbose = 0
        if ret != 0:
            sys.exit()


