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

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

#
# Files to distribute
#

fbk_dist_files = [
  "autogen.sh",
  "wipeout.sh",
  "config/scripts/add-header-typed",
  "config/scripts/make-config-dumper",
  "config/scripts/make-fallbacks-config-in",
  "config/scripts/make-install-symlinks-in",
  "config/scripts/make-macros-environment",
  "config/scripts/make-macros-fallbacks",
  "config/scripts/make-macros-info",
  "config/scripts/make-macros-options",
  "config/scripts/make-macros-patches",
  "config/scripts/make-makefiles-fallbacks",
  "config/specs/atompaw.cfg",
  "config/specs/bigdft.cfg",
  "config/specs/environment.conf",
  "config/specs/fallbacks.cfg",
  "config/specs/fallbacks.conf",
  "config/specs/hdf5.cfg",
  "config/specs/languages.conf",
  "config/specs/libpsml.cfg",
  "config/specs/libxc.cfg",
  "config/specs/linalg.cfg",
  "config/specs/netcdf4.cfg",
  "config/specs/netcdf4_fortran.cfg",
  "config/specs/options.conf",
  "config/specs/wannier90.cfg",
  "config/specs/xmlf90.cfg",
]

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

#
# Subroutines
#

# Makefile header
def makefile_header(name,stamp,pkgs):

  tmp_cmds = ""
  for pkg in pkgs:
    tmp_cmds += "\n\t-$(MAKE) %s" % (pkg)
    tmp_cmds += "\n\t@echo 'Checking build of %s fallback'" % (pkg)
    tmp_cmds += "\n\ttest -e stamps/%s-install-stamp" % (pkg)

  return """\
#                                                          -*- Automake -*-
# Makefile for ABINIT
# Generated by %s on %s

#
# IMPORTANT NOTE
#
# Any manual change to this file will systematically be overwritten.
# Please modify the %s script or its config file instead.
#

ACLOCAL_AMFLAGS = -I config/m4

SUBDIRS = src .

all-local:%s
	@echo ""
	@if test -x "${DESTDIR}${bindir}/abinit-fallbacks-config"; then \\
	  echo "The fallbacks are now ready to use."; \\
	  #echo ""; \\
	  #echo "You may point Abinit to them through one of the following choices:"; \\
	  #echo ""; \\
          #echo "  - set with_fallbacks_prefix='${DESTDIR}${prefix}' in your Abinit config file;"; \\
	  #echo ""; \\
	  #echo "  - use the --with-fallbacks-prefix='${DESTDIR}${prefix}' option of Abinit's configure."; \\
	else \\
	  echo "All requested fallbacks have been built."; \\
	  echo "you may now type 'make install' to finish the installation."; \\
	fi
	@echo ""
""" % (name,stamp,name,tmp_cmds)



# Fallback template
def fallback_template(pkg,pkg_name,pkg_make,pkg_patches=None,pkg_deps=None,pkg_cfg=None,comp_suffix=False,pkg_instdir=None):

  pkg_version = re.sub(pkg+"-", "", pkg_name, flags=re.IGNORECASE)
  if ( pkg_version == pkg_name ):
    pkg_version = re.sub(".*-([0-9])", "\\1", pkg_name, flags=re.IGNORECASE)
  if ( pkg_version == pkg_name ):
    pkg_version = pkg_name.split("-")[-1]
  if ( pkg_instdir ):
    pkg_inst = "%s/%s" % (pkg_instdir, pkg_version)
  else:
    pkg_inst = "%s/%s" % (pkg, pkg_version)
  tmp_mk_bdir = "mkdir sources/$(%s_pkg_name)/tmp-build" % pkg
  tmp_bdir    = "/tmp-build"
  tmp_cfg_cmd = """\
	  /bin/sh ../configure \\
	    --prefix="@prefix@/%s" \\
	    --bindir="@prefix@/%s/bin" \\
	    --includedir="@prefix@/%s/include" \\
	    --libdir="@prefix@/%s/lib" \\
	    $(CFGFLAGS_%s)""" % (pkg_inst,pkg_inst,pkg_inst,pkg_inst,pkg.upper())

  if ( pkg_cfg ):
    if ( pkg_cfg["type"] == "autotools" ):
      pass
    elif ( pkg_cfg["type"] == "obsolete" ):
      tmp_mk_bdir = "$(LN_S) \\. sources/$(%s_pkg_name)/tmp-build" % pkg
      tmp_bdir = ""
      tmp_cfg_cmd = """\
	  /bin/sh ./configure \\
	    --prefix="@prefix@/%s" \\
	    --bindir="@prefix@/%s/bin" \\
	    --includedir="@prefix@/%s/include" \\
	    --libdir="@prefix@/%s/lib" \\
	    $(CFGFLAGS_%s)""" % (pkg_inst,pkg_inst,pkg_inst,pkg_inst,pkg.upper())
    else:
      sys.stderr.write("Error: undefined configuration type\n")
      sys.exit(10)

  tmp_patches = ""
  for diff in pkg_patches:
    tmp_patches += "\n\t$(PATCH) -d sources/$(%s_pkg_name) -p1 <$(top_srcdir)/patches/%s" % \
      (pkg,diff)
  if ( tmp_patches == "" ):
    tmp_patches = "\n\t@echo \"No patch to apply\""

  if ( pkg_deps ):
    tmp_depincs = "$(afb_" + "_incs) $(afb_".join(pkg_deps) + "_incs)"
    tmp_deplibs = "$(afb_" + "_libs) $(afb_".join(pkg_deps) + "_libs)"
    tmp_libpaths = ":".join(["@prefix@/%s/%s/lib" % (item, "default") for item in pkg_deps]) + ":"
  else:
    tmp_depincs = ""
    tmp_deplibs = ""
    tmp_libpaths = ""

  pkg_dict = {
    "name": pkg,
    "NAME": pkg.upper(),
    "version": pkg_version,
    "patches": tmp_patches,
    "threads": pkg_make,
    "builddir": tmp_bdir,
    "dep_incs": tmp_depincs,
    "dep_libs": tmp_deplibs,
    "cfg_cmd": tmp_cfg_cmd,
    "mk_builddir": tmp_mk_bdir,
  }

  if ( pkg_instdir ):
    pkg_dict["installdir"] = pkg_instdir
  else:
    pkg_dict["installdir"] = pkg

  ret = """\

                    ########################################

# Package: %(name)s
%(name)s_pkg_name = @%(name)s_pkg_name@

if DO_BUILD_%(NAME)s
%(name)s: stamps/%(name)s-install-stamp

stamps/%(name)s-install-stamp: stamps/%(name)s-build-stamp
	cd sources/$(%(name)s_pkg_name)/tmp-build && $(MAKE) install @SET_MAKE@
	rm -f $(DESTDIR)@prefix@/%(installdir)s/default
	$(LN_S) %(version)s $(DESTDIR)@prefix@/%(name)s/default
	@touch stamps/%(name)s-install-stamp

stamps/%(name)s-build-stamp: stamps/%(name)s-config-stamp
	cd sources/$(%(name)s_pkg_name)/tmp-build && $(MAKE) -j %(threads)s @SET_MAKE@
	@touch stamps/%(name)s-build-stamp

stamps/%(name)s-config-stamp: stamps/%(name)s-patch-stamp
	cd sources/$(%(name)s_pkg_name)%(builddir)s && \\
	  export DYLD_LIBRARY_PATH="@LIB_PATHS@${DYLD_LIBRARY_PATH}" && \\
	  export LD_LIBRARY_PATH="@LIB_PATHS@${LD_LIBRARY_PATH}" && \\
	  export LIBRARY_PATH="@LIB_PATHS@${LIBRARY_PATH}" && \\
	  CPP="$(CPP)" \\
	  CPPFLAGS="$(CPPFLAGS_%(NAME)s) %(dep_incs)s $(AM_CPPFLAGS)" \\
	  CC="$(CC@COMP_SUFFIX@)" \\
	  CFLAGS="$(CFLAGS_%(NAME)s)" \\
	  CXX="$(CXX@COMP_SUFFIX@)" \\
	  CXXFLAGS="$(CXXFLAGS_%(NAME)s)" \\
          FCCPP="$(TRUE_CPP)" \\
	  F77="$(FC@COMP_SUFFIX@)" \\
	  FFLAGS="$(FCFLAGS_FIXEDFORM) $(FCFLAGS_%(NAME)s) %(dep_incs)s $(AM_CPPFLAGS)" \\
	  F90="$(FC@COMP_SUFFIX@)" \\
	  F90FLAGS="$(FCFLAGS_FREEFORM) $(FCFLAGS_%(NAME)s) %(dep_incs)s $(AM_CPPFLAGS)" \\
	  FC="$(FC@COMP_SUFFIX@)" \\
	  FCFLAGS="$(FCFLAGS_FREEFORM) $(FCFLAGS_%(NAME)s) %(dep_incs)s $(AM_CPPFLAGS)" \\
	  LDFLAGS="$(FC_LDFLAGS) $(LDFLAGS_%(NAME)s)" \\
	  LIBS="$(LIBS_%(NAME)s) %(dep_libs)s" \\
	  AR="$(AR)" \\
	  ARFLAGS="$(ARFLAGS) $(ARFLAGS_CMD)" \\
	  RANLIB="$(RANLIB)" \\
	  ac_fc_srcext="F90" \\
%(cfg_cmd)s
	@touch stamps/%(name)s-config-stamp

stamps/%(name)s-patch-stamp: stamps/%(name)s-unfold-stamp%(patches)s
	@touch stamps/%(name)s-patch-stamp

stamps/%(name)s-unfold-stamp:
	cd sources && \\
	  gzip -cd $(abinit_tardir)/$(%(name)s_pkg_name).tar.gz | tar xf -
	%(mk_builddir)s
	@touch stamps/%(name)s-unfold-stamp
else
%(name)s:
	@echo "The build of %(name)s has been disabled"
	@touch stamps/%(name)s-install-stamp
endif
""" % pkg_dict

  if ( comp_suffix ):
    pkg_suffix = "_%s" % pkg.upper()
  else:
    pkg_suffix = ""

  ret = re.sub("@COMP_SUFFIX@", pkg_suffix, ret)
  ret = re.sub("@LIB_PATHS@", tmp_libpaths, ret)

  return ret


# Fallback clean-up template
def fallback_clean_template(pkg,name,bins=None,hdrs=None,libs=None,mods=None):
  return """\

                    ########################################

# Package clean-up: %s
clean_%s:
	rm -rf exports/%s sources/%s stamps/%s-*
""" % (pkg,pkg,name,name,pkg)



# Makefile footer
def makefile_footer(pkgs):

  chk_cmds = ""
  for pkg in pkgs:
    chk_cmds += "\n\t-cd sources/$(%s_pkg_name) && $(MAKE) check @SET_MAKE@" % \
      (pkg)

  return """\

                    ########################################
                    ########################################

check-local:%s

clean-local:
	rm -rf sources/* exports/* stamps/*

""" % (chk_cmds)



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

#
# Main program
#

# Initial setup
my_name    = "make-makefiles-fallbacks"
my_configs = ["config/specs/fallbacks.conf"]

# 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 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 fallbacks
cnf = MyConfigParser()
cnf.read(my_configs[0])
pkg_list = cnf.sections()
pkg_list.sort()

# Sort fallbacks by dependencies
all_deps = dict()
pkg_num = dict()
i = 0
for pkg in pkg_list:
  pkg_num[pkg] = i
  i += 1
for i in range(len(pkg_list)):
  for pkg in pkg_list:
    if ( cnf.has_option(pkg,"depends") ):
      all_deps[pkg] = cnf.get(pkg,"depends").split()
      pkg_deps = all_deps[pkg]
      for dep in pkg_deps:
        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()

# Create package list in the correct order
abinit_fallbacks = list()
for rnk in pkg_ranks:
  for pkg in pkg_num:
    if ( pkg_num[pkg] == rnk ):
      abinit_fallbacks.append(pkg)

# Init
makefile = makefile_header(my_name,now,abinit_fallbacks)
comp_suff_trigger = {"no":False, "yes":True}

# Process each package
for pkg in abinit_fallbacks:

  # Extract package information
  pkg_name = cnf.get(pkg,"name")
  pkg_make = cnf.get(pkg,"makej")
  pkg_suff = comp_suff_trigger[cnf.get(pkg,"comp_suffix")]
  pkg_bins = pkg_hdrs = pkg_libs = pkg_mods = pkg_instdir = None
  pkg_conf = {"type":cnf.get(pkg,"configure"), "script":cnf.get(pkg,"cfg_script")}
  if ( cnf.has_option(pkg,"binaries") ):
    pkg_bins = cnf.get(pkg,"binaries").split()
  if ( cnf.has_option(pkg,"headers") ):
    pkg_hdrs = cnf.get(pkg,"headers").split()
  if ( cnf.has_option(pkg,"libraries") ):
    pkg_libs = cnf.get(pkg,"libraries").split()
  if ( cnf.has_option(pkg,"modules") ):
    pkg_mods = cnf.get(pkg,"modules").split()
  if ( cnf.has_option(pkg,"install") ):
    pkg_instdir = cnf.get(pkg,"install")

  # Get patch list
  pkg_patches = []
  if ( os.path.exists("./patches") ):
    for diff in os.listdir("./patches"):
      if ( re.match(pkg_name,diff) ):
          pkg_patches.append(diff)
    pkg_patches.sort()

  # Generate templates
  makefile += fallback_template(pkg,pkg_name,pkg_make,pkg_patches,all_deps[pkg],pkg_conf,pkg_suff,pkg_instdir)
  makefile += fallback_clean_template(pkg,pkg_name,pkg_bins,pkg_hdrs,
                pkg_libs,pkg_mods)

# Add makefile footer
makefile += makefile_footer(abinit_fallbacks)

# FIXME: add other files to distribute
makefile += "EXTRA_DIST ="
if ( len(fbk_dist_files) > 0 ):
  makefile += " \\\n\t" + " \\\n\t".join(fbk_dist_files)
if ( os.path.exists("patches") ):
  for f in os.listdir("patches"):
    if ( re.search("\.patch$",f) ):
      makefile += " \\\n\t%s" % (os.path.join("patches",f))
makefile += "\n\n"

# Write top makefile
mf = open("./Makefile.am","w")
mf.write(makefile)
mf.close()

# Write makefile in src
src_mf = """\
#                                                          -*- Automake -*-
# Makefile for the Abinit Fallbacks
# Generated by %s on %s

#
# IMPORTANT NOTE
#
# Any manual change to this file will systematically be overwritten.
# Please modify the %s script or its config file instead.
#

nodist_bin_SCRIPTS = abinit-fallbacks-config
""" % (my_name,now,my_name)
open("src/Makefile.am","w").write(src_mf)
