#!/usr/bin/env python
#
# Copyright (C) 2011-2018 ABINIT Group (Yann Pouillon)
#
# This file is part of the ABINIT software package. For license information,
# please see the COPYING file in the top-level directory of the ABINIT source
# distribution.
#
from __future__ import print_function, division, absolute_import #, unicode_literals

try:
    from ConfigParser import ConfigParser,NoOptionError
except ImportError:
    from configparser import ConfigParser,NoOptionError
    
from time import gmtime,strftime

import os
import re
import sys

class MyConfigParser(ConfigParser):

  def optionxform(self,option):
    return str(option)

# ---------------------------------------------------------------------------- #

#
# Subroutines
#

# Macro header
def macro_header(name,stamp):

  return """# Generated by %s on %s

#
# ABINIT fallback support for the "configure" script
#

#
# IMPORTANT NOTE
#
# This file has been automatically generated by the %s
# script. If you try to edit it, your changes will systematically be
# overwritten.
#
""" % (name,stamp,name)



# Init macro header
def macro_fallback(nick,name,desc,libs,bins,md5s,urls,deps=None,include=None,tricks="${fc_vendor},${fc_version}"):

  # Create template
  ret = """


# ABI_FALLBACK_@PKG_NICK@()
# ---------------------
#
# Sets all variables needed to handle the @PKG_NICK@ fallback.
#
AC_DEFUN([ABI_FALLBACK_@PKG_NICK@],[
  dnl Initial setup
  lib_@pkg_nick@_incs=""
  lib_@pkg_nick@_libs=""
  afb_download="no"
  afb_ready="no"
  afb_tarball="no"
  afb_bins=""
  afb_incs=""
  afb_libs=""

  dnl Define variables needed to build the package
  @pkg_nick@_pkg_name="@PKG_NAME@"
  AC_SUBST(@pkg_nick@_pkg_name)
  @pkg_nick@_pkg_string="@PKG_DESC@"
  AC_SUBST(@pkg_nick@_pkg_string)

  dnl Define options associated to the package
  AC_ARG_ENABLE([@PKG_NICK@],
    AC_HELP_STRING([--enable-@pkg_nick@],
      [@PKG_DESC@]))
  AC_SUBST(enable_@pkg_nick@)

  dnl Set options associated to the package
  if test "${enable_@pkg_nick@}" = ""; then
    enable_@pkg_nick@="yes"
  fi

  dnl Define environment variables for the package
  AC_ARG_VAR([CFGFLAGS_@PKG_NICK@],
    [Configuration options for @pkg_nick@])
  AC_SUBST(CFGFLAGS_@PKG_NICK@)
  AC_ARG_VAR([CPP],
    [C preprocessor])
  AC_SUBST(CPP)
  AC_ARG_VAR([CPPFLAGS_@PKG_NICK@],
    [C preprocessing flags for @pkg_nick@])
  AC_SUBST(CPPFLAGS_@PKG_NICK@)
  AC_ARG_VAR([CC],
    [C compiler])
  AC_SUBST(CC)
  AC_ARG_VAR([CFLAGS_@PKG_NICK@],
    [C flags for @pkg_nick@])
  AC_SUBST(CFLAGS_@PKG_NICK@)
  AC_ARG_VAR([CXX],
    [C++ compiler])
  AC_SUBST(CC)
  AC_ARG_VAR([CXXFLAGS_@PKG_NICK@],
    [C++ flags for @pkg_nick@])
  AC_SUBST(CXXFLAGS_@PKG_NICK@)
  AC_ARG_VAR([FC],
    [Fortran compiler])
  AC_SUBST(FC)
  AC_ARG_VAR([FPPFLAGS_@PKG_NICK@],
    [Fortran preprocessor flags for @pkg_nick@ (not implemented)])
  AC_SUBST(FPPFLAGS_@PKG_NICK@)
  AC_ARG_VAR([FCFLAGS_@PKG_NICK@],
    [Fortran flags for @pkg_nick@])
  AC_SUBST(FCFLAGS_@PKG_NICK@)
  AC_ARG_VAR([LDFLAGS_@PKG_NICK@],
    [Link flags for @pkg_nick@])
  AC_SUBST(LDFLAGS_@PKG_NICK@)
  AC_ARG_VAR([LIBS_@PKG_NICK@],
    [Additional libraries for @pkg_nick@])
  AC_SUBST(LIBS_@PKG_NICK@)

  dnl Set environment variables for the package
  if test -z "${CFGFLAGS_@PKG_NICK@}"; then
    CFGFLAGS_@PKG_NICK@=""
  fi
  if test -z "${CPPFLAGS_@PKG_NICK@}"; then
    CPPFLAGS_@PKG_NICK@="${CPPFLAGS}"
  fi
  if test -z "${CFLAGS_@PKG_NICK@}"; then
    CFLAGS_@PKG_NICK@="${CFLAGS}"
  fi
  if test -z "${CXXFLAGS_@PKG_NICK@}"; then
    CXXFLAGS_@PKG_NICK@="${CXXFLAGS}"
  fi
  if test -z "${FPPFLAGS_@PKG_NICK@}"; then
    FPPFLAGS_@PKG_NICK@="${FPPFLAGS}"
  fi
  if test -z "${FCFLAGS_@PKG_NICK@}"; then
    FCFLAGS_@PKG_NICK@="${FCFLAGS}"
  fi
  if test -z "${LDFLAGS_@PKG_NICK@}"; then
    LDFLAGS_@PKG_NICK@="${LDFLAGS}"
  fi
  if test -z "${LIBS_@PKG_NICK@}"; then
    LIBS_@PKG_NICK@="${LIBS}"
  fi

@BINS_SPECS@

  dnl Check whether to activate fallback
  AC_MSG_CHECKING([whether to enable the @PKG_NICK@ fallback])
  AC_MSG_RESULT([${enable_@pkg_nick@}])
  if test "${enable_@pkg_nick@}" = "yes"; then

    dnl Check whether command-line fallback options have been specified
    if test @INCS_TEST@"${with_@pkg_nick@_libs}" = ""; then

      dnl Check for a tarball repository
      dnl Check MD5 sum of source tarball
      AC_MSG_CHECKING([for a source tarball of @PKG_NICK@])
      if test -s "${abinit_tardir}/@PKG_NAME@.tar.gz"; then
        AC_MSG_RESULT([yes])
        AX_CHECK_MD5SUM([${abinit_tardir}/@PKG_NAME@.tar.gz],[@MD5SUM@])
        case "${abi_md5_ok}" in
          yes)
            afb_tarball="yes"
            AC_MSG_NOTICE([tarball MD5 check succeeded])
            ;;
          unknown)
            afb_tarball="yes"
            AC_MSG_WARN([could not check integrity of tarball])
            ;;
          no)
            AC_MSG_WARN([tarball MD5 check failed])
            ;;
        esac
      else
        AC_MSG_RESULT([no])
      fi

      dnl Get the package
      if test "${afb_ready}" = "no"; then
        for dl_url in @PKG_URLS@; do
          if test "${afb_tarball}" = "no"; then
            AC_MSG_NOTICE([downloading @PKG_NICK@ - this may take a while])
            rm -f "${abinit_tardir}/@PKG_NAME@.tar.gz"
            ${afb_downloader} ${afb_dlopts} \
              "${abinit_tardir}/@PKG_NAME@.tar.gz" \
              "${dl_url}"
            if test -s "${abinit_tardir}/@PKG_NAME@.tar.gz"; then
              AX_CHECK_MD5SUM([${abinit_tardir}/@PKG_NAME@.tar.gz],[@MD5SUM@])
              case "${abi_md5_ok}" in
                yes)
                  afb_tarball="yes"
                  AC_MSG_NOTICE([tarball MD5 check succeeded])
                ;;
                unknown)
                  afb_tarball="yes"
                  AC_MSG_WARN([could not check integrity of tarball])
                  ;;
                no)
                  AC_MSG_WARN([tarball MD5 check failed])
                  ;;
              esac
            fi
          fi
        done

        dnl Enable fallback support only if the download was successful
        if test "${afb_tarball}" = "yes"; then
          lib_@pkg_nick@_incs="@INCS_PATH@"
          lib_@pkg_nick@_libs="@LIBS_SPECS@"
        else
          AC_MSG_ERROR([could not download @PKG_NICK@ fallback tarball
    Disable support for @PKG_NICK@ or download the tarball manually to
    ${abinit_tardir}])
        fi
      fi
    else
      lib_@pkg_nick@_incs="${with_@pkg_nick@_incs}"
      lib_@pkg_nick@_libs="${with_@pkg_nick@_libs}"
    fi

  fi

  dnl Apply tricks
  if test "${enable_@pkg_nick@}" = "yes"; then
    ABI_TRICKS_@PKG_NICK@(@TRICKS@)
  fi

  dnl Substitute variables needed for the use of the fallback
  AC_SUBST(lib_@pkg_nick@_incs)
  AC_SUBST(lib_@pkg_nick@_libs)

  dnl Inform Automake
  AM_CONDITIONAL(DO_TEST_@PKG_NICK@,test "${enable_@pkg_nick@}" = "yes")
  AM_CONDITIONAL(DO_BUILD_@PKG_NICK@,test "${enable_@pkg_nick@}" = "yes")
]) # ABI_FALLBACK_@PKG_NICK@
"""

  # Set include specs
  incs_specs = ""
  if ( include is None ):
    incs_test = ""
    incs_path = ""
  else:
    incs_test = "\"${with_@pkg_nick@_incs}\" = \"\" -o "
    incs_path = "-I\$(fallbacks_instdir)/include"
  if ( not deps is None ):
    for dep in deps:
      incs_specs += """
  if test "${enable_%s}" = "no"; then
    CPPFLAGS_@PKG_NICK@="${CPPFLAGS_@PKG_NICK@} ${lib_%s_incs}"
  fi\n""" % (dep,dep)

  # Set lib specs
  tmp_libs = ""
  for lib in libs:
    if ( re.match("lib.*\.a",lib) ):
      tmp_libs = " -l%s" % (re.sub(r"lib(.*)\.a",r"\1",lib)) + tmp_libs
  libs_specs = "-L\$(fallbacks_instdir)/lib" + tmp_libs

  # Set bin specs
  bins_specs = ""
  if ( (not bins is None) and ( not deps is None) ):
    for dep in deps:
      tmp_libs = ""
      for lib in deps[dep]:
        if ( re.match("lib.*\.a",lib) ):
          tmp_libs = " -l%s" % (re.sub(r"lib(.*)\.a",r"\1",lib)) + tmp_libs
      tmp_libs = "-L\\$(fallbacks_instdir)/lib" + tmp_libs
      bins_specs += """\
  if test "${enable_%s}" = "yes"; then
    LIBS_@PKG_NICK@="%s ${LIBS_@PKG_NICK@}"
  else
    LIBS_@PKG_NICK@="${lib_%s_libs} ${LIBS_@PKG_NICK@}"
  fi\n""" % (dep,tmp_libs,dep)

  # Generate install-test procedure
  inst_proc = ""
  if ( bins != None ):
    for bin in bins:
      inst_proc += "\n" \
                +  "      if test ! -x \"${with_fallbacks_prefix}/bin/%s\"; then\n"  % (bin) \
                +  "        afb_ready=\"no\"\n" \
                +  "      fi\n"

  # Set URL specs
  urls_specs = '"' + '" "'.join(urls) + '"'

  # Transform template (the order is important)
  ret = re.sub("@INCS_TEST@",incs_test,ret)
  ret = re.sub("@INCS_PATH@",incs_path,ret)
  ret = re.sub("@LIBS_SPECS@",libs_specs,ret)
  ret = re.sub("@BINS_SPECS@",bins_specs,ret)
  ret = re.sub("@MD5SUM@",md5s,ret)
  ret = re.sub("@TRICKS@",tricks,ret)
  ret = re.sub("@PKG_NAME@",name,ret)
  ret = re.sub("@PKG_DESC@",desc,ret)
  ret = re.sub("@PKG_NICK@",nick.upper(),ret)
  ret = re.sub("@PKG_URLS@",urls_specs,ret)
  ret = re.sub("@pkg_nick@",nick,ret)

  return ret



# ---------------------------------------------------------------------------- #

#
# Main program
#

# Initial setup
my_name    = "make-macros-fallbacks"
my_configs = ["config/specs/fallbacks.conf"]
my_output  = "config/m4/auto-fallbacks.m4"

# Check if we are in the top of the ABINIT source tree
if ( not os.path.exists("configure.ac") or
     not os.path.exists("config/specs/fallbacks.conf") ):
  print("%s: You must be in the top of an ABINIT source tree." % my_name)
  print("%s: Aborting now." % my_name)
  sys.exit(1)

# Read config file(s)
for cnf_file in my_configs:
  if ( os.path.exists(cnf_file) ):
    if ( re.search("\.cf$",cnf_file) ):
      exec(compile(open(cnf_file).read(), cnf_file, 'exec'))
  else:
    print("%s: Could not find config file (%s)." % (my_name,cnf_file))
    print("%s: Aborting now." % my_name)
    sys.exit(2)

# What time is it?
now = strftime("%Y/%m/%d %H:%M:%S +0000",gmtime())

# Init
cnf = MyConfigParser()
cnf.read(my_configs[0])
abinit_fallbacks = cnf.sections()
abinit_fallbacks.sort()

# Start writing macro
m4 = open(my_output, "wt")
m4.write(macro_header(my_name,now))

# Process fallbacks
for pkg in abinit_fallbacks:

  # Extract mandatory package information
  pkg_name = cnf.get(pkg,"name")
  pkg_desc = cnf.get(pkg,"description")
  pkg_md5s = cnf.get(pkg,"md5sum")
  pkg_urls = cnf.get(pkg,"urls").split()

  # Extract optional package information
  try:
    pkg_bins = cnf.get(pkg,"binaries").split()
  except NoOptionError:
    pkg_bins = None
  try:
    pkg_hdrs = cnf.get(pkg,"headers").split()
  except NoOptionError:
    pkg_hdrs = None
  try:
    pkg_libs = cnf.get(pkg,"libraries").split()
  except NoOptionError:
    pkg_libs = None
  try:
    pkg_mods = cnf.get(pkg,"modules").split()
  except NoOptionError:
    pkg_mods = None
  try:
    pkg_deps = cnf.get(pkg,"depends").split()
  except NoOptionError:
    pkg_deps = None

  # Expand dependencies
  if ( not pkg_deps is None ):
    tmp_deps = dict()
    for dep in pkg_deps:
      tmp_deps[dep] = cnf.get(dep,"libraries").split()
    pkg_deps = tmp_deps

  # Check whether the fallback exports C headers or modules
  if ( (pkg_hdrs is None) and (pkg_mods is None) ):
    pkg_incs = None
  else:
    pkg_incs = "yes"

  # Write macro
  m4.write(macro_fallback(pkg,pkg_name,pkg_desc,pkg_libs,pkg_bins,pkg_md5s,pkg_urls,pkg_deps,pkg_incs))

# Finish
m4.close()
