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

# Makefile header
def makefile_header(name,stamp):

  return """#
# Makefile for ABINIT                                      -*- Automake -*-
# 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.
#

""" % (name,stamp,name)



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

#
# Main program
#

# Initial setup
my_name    = "make-makefiles-corelibs"
my_configs = ["config/specs/corelibs.conf", "config/specs/dependencies.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("src/98_main/abinit.F90") ):
  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(r"\.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_corelibs = cnf.sections()
abinit_corelibs.sort()
dep_cnf = MyConfigParser()
dep_cnf.read(my_configs[1])

# Hard-coded libraries for check programs
hard_chklibs = [
  "16_hideleave",
  "14_hidewrite",
  "12_hide_mpi",
  "11_memory_mpi",
  "10_defs",
  "02_clib",
  ]

# Prepare recognition of extensions
re_src = dict()
re_src["c"] = re.compile(r"\.c$")
re_src["cu"] = re.compile(r"\.cu$")
re_src["F90"] = re.compile(r"\.F90$")

# Process each library
for lib in abinit_corelibs:

  # Init
  lib_opt = cnf.get(lib,"optional")
  lib_rul = cnf.get(lib,"abirules")
  lib_par = cnf.get(lib,"parent")
  try:
    lib_deps = cnf.get(lib,"dependencies").split()
  except NoOptionError:
    lib_deps = None

  # Reset variables
  makefile = makefile_header(my_name,now)
  sources = list()
  optional_sources = list()
  modules = list()
  headers = list()
  cudas   = list()
  sources_specs = dict()
  checkers = None

  # Select parent dir
  if ( lib_par == "common" ):
    par_dir = os.path.join("shared", "common", "src", lib)
  elif ( lib_par == "libpaw" ):
    par_dir = os.path.join("shared", "libpaw", "src")
  else:
    par_dir = os.path.join("src", lib)

  # Import source configuration
  src_cnf = os.path.join(par_dir, "abinit.src")
  if ( os.path.exists(src_cnf) ):
    exec(compile(open(src_cnf).read(), src_cnf, 'exec'))
  else:
    print("%s: Could not find config file (%s)." % (my_name,src_cnf))
    print("%s: Aborting now." % my_name)
    sys.exit(3)

  # Import inter-directory dependency configuration
  dir_cnf = os.path.join(par_dir, "abinit.dir")
  if ( os.path.exists(dir_cnf) ):
    exec(compile(open(dir_cnf).read(), dir_cnf, 'exec'))
  else:
    include_dirs = []

  # FIXME: abisrc.py glitches
  if ( lib in ["57_iovars", "66_nonlocal", "70_gw"] ):
    include_dirs.append("33_xc_lowlevel")

  # Add special dirs to include list
  include_dirs += ["common", "core", "libpaw"]

  # Initialize internal variables
  dd_srcs = "lib%s_srcs =" % (lib)
  nd_srcs = "lib%s_srcs_built =" % (lib)
  ed_srcs = ""
  ob_srcs = ""

  cleans = ""
  distcleans = ""

  nds_print = False
  obs_print = False

  # Initialize build flags
  # FIXME: hard-coded TRIQS C++ flags
  makefile += "AM_CFLAGS = @ABI_CPPFLAGS@\n"
  makefile += "AM_CXXFLAGS = $(sd_triqs_cxxflags)\n"
  makefile += "AM_FCFLAGS = @FPPFLAGS@ @FCFLAGS_FREEFORM@ @FCFLAGS_MODDIR@ @sd_sys_fcflags@ @fcflags_opt_%s@\n\n" % (lib)

  # Initialize includes
  inc = ""
  for dep in include_dirs:
    inc += " \\\n\t@src_%s_fcflags@" % (dep)

  # Dependencies
  if ( cnf.has_option(lib,"dependencies") ):
    for dep in cnf.get(lib,"dependencies").split():
      if ( dep in dep_cnf.sections() ):
        if ( dep_cnf.get(dep, "detector") == "steredeg" ):
          dep_pfx = "sd"
        else:
          dep_pfx = "abi"
        dep_lngs = dep_cnf.get(dep, "languages").split()
        if ( ("c" in dep_lngs) or ("c++" in dep_lngs) ):
          inc += " \\\n\t@%s_%s_cppflags@" % (dep_pfx, dep)
        if ( "fortran" in dep_lngs ):
          inc += " \\\n\t@%s_%s_fcflags@" % (dep_pfx, dep)
      else:
        raise ValueError("invalid external dependency: '%s'" % dep)

  # Required basic Fortran includes (must be last!)
  inc += " \\\n\t@fc_mod_fcflags@"

  if ( inc != "" ):
    makefile += "AM_CPPFLAGS =%s\n\n" % (inc)

  # Generate lists of source files
  for src in sources:
    if ( src in sources_specs ):
      src_specs = sources_specs[src]
    else:
      src_specs = ABI_SRC_NIL

    # Check whether the file is built by the configure script
    if ( (src_specs & ABI_SRC_BLT) != 0 ):
      distcleans += " \\\n\t%s" % (src)
      nd_srcs += " \\\n\t%s" % (src)
      ed_srcs += " \\\n\t%s.in" % (src)
      nds_print = True
    else:
      dd_srcs += " \\\n\t%s" % (src)
      if ( not os.path.exists("%s/%s" % (par_dir,src)) ):
        sys.stderr.write("Error: No such file or directory: '%s/%s'\n" % (par_dir,src))
        sys.exit(4)

    # Check whether to clean the preprocessed source file
    for (ext,reg) in re_src.items():
      if ( reg.search(src) ):
        cleans += " \\\n\t%s" % (re.sub(r"\.%s" % (ext), \
          "_cpp.%s" % (ext.lower()),src))

    # Find CUDA source files
    if ( re_src["cu"].search(src) ):
      cudas.append(src)

  # Optional source files
  try:
    for cnd in optional_sources:
      ob_srcs += "if DO_BUILD_%s\n  lib%s_srcs += \\\n    %s\nendif" % \
        (cnd,lib," \\\n    ".join(optional_sources[cnd]))
    obs_print = True
  except NameError:
    pass

  dd_srcs += "\n\n"
  nd_srcs += "\n\n"
  ob_srcs += "\n\n"

  makefile += "# Regular source files\n"+dd_srcs
  if ( obs_print ):
    makefile += "# Source files depending on conditionals\n"+ob_srcs
  if ( nds_print ):
    makefile += "# Source files built by scripts\n"+nd_srcs

  # Library description
  makefile += "# Library description\n"
  makefile += "noinst_LIBRARIES = lib%s.a\n\n" % (lib)

  makefile += "lib%s_a_SOURCES= $(lib%s_srcs)\n" % (lib,lib)
  if ( nds_print ):
    makefile += "nodist_lib%s_a_SOURCES = $(lib%s_srcs_built)\n" % (lib,lib)

  # Write header list
  if ( len(headers) > 0 ):
    sep = " \\\n\t"
    makefile += "\nnoinst_HEADERS = \\\n\t%s\n" % (sep.join(headers))

  # Fix list of files to clean
  if ( len(modules) > 0 ):
    sep = ".mod \\\n\t"
    cleans += " \\\n\t%s.mod\n" % (sep.join(modules))
  elif ( cleans != "" ):
    cleans += "\n"

  # Write list of files to clean (must be set before adding abilint output)
  if ( cleans == "" ):
    cleans = "\n"
  makefile += "\nCLEANFILES ="+cleans

  # Write list of files to distclean
  if ( distcleans != "" ):
    makefile += "\nDISTCLEANFILES =%s\n" % (distcleans)

  # Add "abinit.src" to the list of dist files
  makefile += "\nEXTRA_DIST = abinit.src\n"

  # Write internal library dependencies
  dep = os.path.join(par_dir, "abinit.dep")
  if ( os.path.exists(dep) ):
    makefile += "\nEXTRA_DIST += abinit.dep\n"
    makefile += "\n"+ open(dep,"rt").read()

  # Write targets for checkers
  try:
    tmp_chk = "\ncheck_PROGRAMS ="
    tmp_chk_cln = "\nCLEANFILES +="
    for chk in checkers:
      tmp_chk += " \\\n\t%s" % (chk)
      tmp_chk_cln += " \\\n\t%s.log" % (chk)
      tmp_chk_cln += " \\\n\t%s.tmp" % (chk)
    makefile += tmp_chk + "\n"
    for chk in checkers:
      # FIXME: hard-coded for now
      makefile += "\n%s_SOURCES = %s.F90" % (chk,chk)
      makefile += "\n%s_CPPFLAGS = @sd_netcdf_cppflags@ @sd_linalg_cppflags@ -I$(top_srcdir)/shared/common/src/incs -I$(top_srcdir)/src/incs -I$(top_builddir)/shared/common/src/mods -I$(top_builddir)/src/mods" % \
       (chk)
      makefile += "\n%s_LDADD = \\\n\tlib%s.a" % (chk,lib)
      for elc in checkers[chk] + hard_chklibs:
        makefile += " \\\n\t../%s/lib%s.a" % (elc,elc)
      makefile += " \\\n\t@%s@\n" % \
        ("@ \\\n\t@".join(["sd_netcdf_libs","sd_linalg_libs"]))
    makefile += "\ncheck-local:\n"
    for chk in checkers:
      makefile += "\t./%s >%s.log 2>&1; grep '^TEST FAILED' %s.log && echo 'Unit test %s FAILED' || echo 'Unit test %s OK'\n" % \
        (chk,chk,chk,chk,chk)
    makefile += tmp_chk_cln + "\n"
  except:
    pass

  # Write CUDA rules
  if ( len(cudas) > 0 ):
    makefile += "\nSUFFIXES = .o .cu\n\n.cu.o:\n\t$(NVCC) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(NVCC_CPPFLAGS) $(NVCC_CFLAGS) -c $<\n"
    for src in cudas:
      makefile += "\n%s.$(OBJEXT): %s\n" % (re.sub(r"\.cu","",src),src)

  # Write optional source files
  # Note: useless because Automake already takes care of it
  #try:
  #  ob_xtra = "\nEXTRA_DIST +="
  #  for cnd in optional_sources:
  #    ob_xtra += " \\\n  " + " \\\n  ".join(optional_sources[cnd])
  #  ob_xtra += "\n"
  #  makefile += ob_xtra
  #except NameError:
  #  pass

  # Write source file templates
  if ( len(ed_srcs) > 0 ):
    makefile += "\n\nEXTRA_DIST +=" + ed_srcs + "\n"

  # Write additional hand-made information
  add = os.path.join(par_dir, "abinit.amf")
  if ( os.path.exists(add) ):
    makefile += "\nEXTRA_DIST += abinit.amf\n"
    makefile += "\n" + open(add, "rt").read()

  # Include RoboDOC header
  hdr = "%s/_%s_" % (par_dir,lib)
  if ( os.path.exists(hdr) ):
    makefile += "\n\nEXTRA_DIST += _%s_\n" % (lib)

  # Write additional cmake information
  cml = os.path.join(par_dir, "CMakeLists.txt")
  if ( os.path.exists(cml) ):
    makefile += "\nEXTRA_DIST += CMakeLists.txt\n"

  # Output to Makefile.am
  mf = open(os.path.join(par_dir, "Makefile.am"), "wt")
  mf.write(makefile)
  mf.close()
