#!/usr/bin/env python
#
# Copyright (C) 2014 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

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

import subprocess
import os
import re
import sys

class MyConfigParser(ConfigParser):

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

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

#
# Subroutines
#

# Script template
def config_script(name,stamp,fbk_versions,fbk_items,fbk_order,fbk_vars,fbk_deps,
  fbk_libs,fbk_prqs):

  # Init
  ret = """\
#!/bin/sh
#
# Generated by %s on %s

#
# Provide information about Abinit Fallbacks configuration
#

#
# IMPORTANT NOTE
#
# This file has been automatically generated by the %s
# script. Any manual changes will systematically be overwritten.
#

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

# Init fallbacks parameters@FBK_INIT@

# Set fallbacks parameters@FBK_DUMP@

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

# Check input parameters and print usage if needed
my_name=`basename "${0}"`
if test "${#}" -lt "2"; then
  cat <<EOF
Usage: ${my_name} --<item> <package>

where <package> is the selected fallback and <item> is the kind of
information desired.

<item> can be:

  * avail    : get a space-separated list of available items for the
               selected package, i.e. which of the following queries
               can be performed for this package;
  * bins     : get path to the binaries of the package;
  * cflags   : get cflags used to build the package;
  * cppflags : get C preprocessing flags used to build the package
               (except includes, see "incs" below);
  * deps     : get a list of packages the selected one depends on;
  * depincs  : get include flags for the package dependencies;
  * deplibs  : get library flags for the package dependencies;
  * enabled  : tell whether the package has been enabled;
  * fcflags  : get Fortran flags used to build the package;
  * fppflags : get Fortran preprocessing flags used to build the package;
               (except includes, see "incs" below);
  * incs     : get include flags to installed C headers and/or Fortran modules;
  * ldflags  : get link flags used to build the package;
  * libs     : get library flags to link against the installed package;
  * pkgver   : get package version;
  * shell    : get shell code to use the package.

<package> can be:
@FBK_LIST@

EOF
  exit 0
fi

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

# Prepare run
my_cmd=`echo "${1}" | sed -e 's/..//'`
my_pkg="${2}"
my_item=`eval echo \$\{afb_${my_pkg}_${my_cmd}\}`

# Check package
case "${my_pkg}" in

  @FBK_PKGS@)
    :
    ;;

  *)
    echo "Error: unknown package '${my_pkg}'" >&2
    exit 1

esac

# Execute command
case "${my_cmd}" in

  avail)
    item_list=""
    for item in @FBK_ENUM@; do
      chk_enab=`eval echo \$\{enable_${my_pkg}\}`
      chk_item=`eval echo \$\{afb_${my_pkg}_has_${item}\}`
      test "${chk_enab}" = "yes" -a "${chk_item}" = "yes" && \
        item_list="${item_list} ${item}"
    done
    item_list=`echo "${item_list}" | sed -e 's/^ //'`
    echo "${item_list}"
    ;;

  depincs|deplibs)
    pkg_has_deps=`eval echo \$\{afb_${my_pkg}_has_${my_cmd}\}`
    if test "${pkg_has_deps}" = "yes"; then
      echo "${my_item}"
    else
      echo "Error: no '${my_cmd}' item for package '${my_pkg}'" >&2
      exit 1
    fi
    ;;

  deps)
    pkg_deps=`eval echo \$\{afb_${my_pkg}_${my_cmd}\}`
    if test "${pkg_deps}" != ""; then
      echo "${pkg_deps}"
    fi
    ;;

  enabled)
    pkg_enabled=`eval echo \$\{enable_${my_pkg}\}`
    echo "${pkg_enabled}"
    ;;

  @FBK_CASE@)
    pkg_has_item=`eval echo \$\{afb_${my_pkg}_has_${my_cmd}\}`
    if test "${pkg_has_item}" = "yes"; then
      echo "${my_item}"
    else
      echo "Error: no '${my_cmd}' item for package '${my_pkg}'" >&2
      exit 1
    fi
    ;;

  pkgver)
    eval echo \$\{afb_${my_pkg}_version\}
    ;;

  shell)
    pkg_has_libs=`eval echo \$\{afb_${my_pkg}_has_libs\}`
    if test "${pkg_has_libs}" = "yes"; then
      echo 'LD_LIBRARY_PATH="@prefix@/lib:${LD_LIBRARY_PATH}"'
      echo 'export LD_LIBRARY_PATH'
      echo 'LIBRARY_PATH="@prefix@/lib:${LIBRARY_PATH}"'
      echo 'export LIBRARY_PATH'
    fi
    pkg_has_bins=`eval echo \$\{afb_${my_pkg}_has_bins\}`
    if test "${pkg_has_bins}" = "yes"; then
      echo 'MANPATH="@prefix@/share/man:${MANPATH}"'
      echo 'export MANPATH'
      echo 'PATH="@prefix@/bin:${PATH}"'
      echo 'export PATH'
    fi
    ;;

  *)
    echo "Error: unrecognized option '${1}'" >&2
    exit 1

esac

exit 0
""" % (name,stamp,name)

  # Generate substitutions
  fbk_case = "|".join(fbk_items)
  fbk_enum = " ".join(fbk_items)
  fbk_init = ""
  fbk_dump = ""

  for pkg in fbk_order:
    fbk_inst  = "%s/%s" % (pkg, fbk_versions[pkg])
    fbk_init += "\nenable_%s='@enable_%s@'" % (pkg,pkg)
    fbk_init += "\nafb_%s_version='%s'" % (pkg, fbk_versions[pkg])
    fbk_init += "\nafb_%s_status='disabled'" % pkg
    fbk_dump += "\nif test \"${enable_%s}\" = \"yes\"; then" % pkg
    fbk_dump += "\n  afb_%s_status='enabled'" % pkg

    if ( fbk_deps[pkg] ):
      pkg_deps="yes"
    else:
      pkg_deps="no"
    fbk_init += "\nafb_%s_has_deps='no'" % pkg
    fbk_init += "\nafb_%s_deps=''" % pkg
    fbk_dump += "\n  afb_%s_has_deps='%s'" % (pkg,pkg_deps)
    if ( pkg_deps == "yes" ):
      fbk_dump += "\n  afb_%s_deps='%s'" % (pkg," ".join(fbk_deps[pkg]))

    for item in fbk_items:

      fbk_init += "\nafb_%s_has_%s='no'" % (pkg,item)
      fbk_init += "\nafb_%s_%s=''" % (pkg,item)

      if ( item in fbk_vars[pkg] ):
        pkg_item = "yes"
      else:
        pkg_item = "no"
      fbk_dump += "\n  afb_%s_has_%s='%s'" % (pkg,item,pkg_item)

      if ( pkg_item == "yes" ):

        if ( item == "bins" ):

          fbk_init += "\nafb_%s_has_dep%s='no'" % (pkg,item)
          fbk_dump += "\n  afb_%s_%s='@prefix@/%s/bin'" % (pkg,item,fbk_inst)

        elif ( item == "incs" ):

          fbk_init += "\nafb_%s_has_dep%s='no'" % (pkg,item)
          fbk_dump += "\n  afb_%s_%s='-I@prefix@/%s/include'" % (pkg,item,fbk_inst)

          if ( fbk_deps[pkg] ):
            fbk_dump += "\n  afb_%s_has_dep%s='yes'" % (pkg,item)
            tmp_dump = ""
            for dep in fbk_deps[pkg]:
              if ( item in fbk_vars[dep] ):
                tmp_dump += " ${afb_%s_%s}" % (dep,item)
            fbk_dump += "\n  afb_%s_dep%s=\"%s\"" % (pkg,item,tmp_dump[1:])

        elif ( item == "libs" ):

          fbk_init += "\nafb_%s_has_dep%s='no'" % (pkg,item)
          tmp_dump = ""
          for pkg_lib in fbk_libs[pkg][-1::-1]:
            tmp_dump += " -l%s" % re.sub("lib","",pkg_lib[:-2])
          fbk_dump += "\n  afb_%s_%s='-L@prefix@/%s/lib%s'" % (pkg,item,fbk_inst,tmp_dump)

          if ( fbk_deps[pkg] ):
            fbk_dump += "\n  afb_%s_has_dep%s='yes'" % (pkg,item)
            tmp_dump = ""
            for dep in fbk_deps[pkg][-1::-1]:
              if ( item in fbk_vars[dep] ):
                tmp_dump += " ${afb_%s_%s}" % (dep,item)
            fbk_dump += "\n  afb_%s_dep%s=\"%s\"" % (pkg,item,tmp_dump[1:])

        else:

          fbk_dump += "\n  afb_%s_%s='@%s_%s@'" % \
            (pkg,item,item.upper(),pkg.upper())

    if ( fbk_prqs[pkg] ):

      fbk_dump += "\nelse"
      for item in ("bins","incs","libs"):
        if ( item in fbk_vars[pkg] ):
          fbk_dump += "\n  afb_%s_%s='@with_%s_%s@'" % (pkg,item,pkg,item)

    fbk_dump += "\nfi"

  # Substitute template
  ret = re.sub("@FBK_CASE@",fbk_case,ret)
  ret = re.sub("@FBK_DUMP@",fbk_dump,ret)
  ret = re.sub("@FBK_ENUM@",fbk_enum,ret)
  ret = re.sub("@FBK_INIT@",fbk_init,ret)

  fbk_order.sort()
  fbk_list = ""
  for pkg in fbk_order:
    fbk_list += "\n  * %-16s (${afb_%s_status} in this bundle)" % (pkg,pkg)
  ret = re.sub("@FBK_LIST@",fbk_list,ret)
  ret = re.sub("@FBK_PKGS@","|".join(fbk_order),ret)

  return ret



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

#
# Main program
#

# Initial setup
my_name    = "make-fallbacks-config-in"
my_configs = ["config/specs/fallbacks.conf"]
my_output  = "src/abinit-fallbacks-config.in"

# Check if we are in the top of the ABINIT-FALLBACKS 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 ABINITi-FALLBACKS source tree." % my_name)
  print("%s: Aborting now." % my_name)
  sys.exit(1)

# Read config open(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()
fbk_deps = {}
fbk_libs = {}
fbk_vars = {}
fbk_versions = {}

# Define recognized items
fbk_items = [
  "bins",
  "cflags",
  "cppflags",
  "fcflags",
  "fppflags",
  "incs",
  "ldflags",
  "libs"]

# Sort fallbacks by dependencies
fbk_prqs = {}
all_deps = {}
pkg_num = {}
i = 0
for pkg in abinit_fallbacks:
  fbk_versions[pkg] = cnf.get(pkg,"name").split("-")[-1]
  fbk_prqs[pkg] = False
  pkg_num[pkg] = i
  i += 1
for i in range(len(abinit_fallbacks)):
  for pkg in abinit_fallbacks:
    if ( cnf.has_option(pkg,"depends") ):
      all_deps[pkg] = cnf.get(pkg,"depends").split()
      pkg_deps = all_deps[pkg]
      for dep in pkg_deps:
        fbk_prqs[dep] = True
        if ( pkg_num[dep] >= pkg_num[pkg] ):
          pkg_num[pkg] = pkg_num[dep] + 1
    else:
      all_deps[pkg] = None
pkg_ranks = list(pkg_num.values())
pkg_ranks = list(set(pkg_ranks))
pkg_ranks.sort()

fbk_order = []
for rnk in pkg_ranks:
  for pkg in pkg_num:
    if ( pkg_num[pkg] == rnk ):
      fbk_order.append(pkg)

# Process fallbacks
for pkg in abinit_fallbacks:
  fbk_deps[pkg] = None
  fbk_libs[pkg] = None
  fbk_vars[pkg] = []
  pkg_langs = cnf.get(pkg,"languages").split()
  if ( cnf.has_option(pkg,"depends") ):
    fbk_deps[pkg] = cnf.get(pkg,"depends").split()
  if ( cnf.has_option(pkg,"binaries") ):
    fbk_vars[pkg].append("bins")
  if ( "C" in pkg_langs ):
    fbk_vars[pkg].append("cflags")
    fbk_vars[pkg].append("cppflags")
  if ( "Fortran" in pkg_langs ):
    fbk_vars[pkg].append("fcflags")
    fbk_vars[pkg].append("fppflags")
  if ( cnf.has_option(pkg,"headers") or cnf.has_option(pkg,"modules") ):
    fbk_vars[pkg].append("incs")
  if ( cnf.has_option(pkg,"binaries") or cnf.has_option(pkg,"libraries") ):
    fbk_vars[pkg].append("ldflags")
  if ( cnf.has_option(pkg,"libraries") ):
    fbk_vars[pkg].append("libs")
    fbk_libs[pkg] = cnf.get(pkg,"libraries").split()

# Write down macro
if ( not os.path.exists("src") ):
  os.mkdir("src")
open(my_output,"w").write(config_script(my_name,now,fbk_versions,fbk_items,fbk_order,
  fbk_vars,fbk_deps,fbk_libs,fbk_prqs))
