#!/usr/bin/env python
#
# Copyright (C) 2005-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 ConfigParser import ConfigParser,NoOptionError
from time import gmtime,strftime

import commands
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"]

# 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("\.cf$",cnf_file) ):
      execfile(cnf_file)
  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()

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

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

# Process each library
for lib in abinit_corelibs:

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

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

  # Import source configuration
  src_cnf = "src/%s/abinit.src" % (lib)
  if ( os.path.exists(src_cnf) ):
    execfile(src_cnf)
  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 = "src/%s/abinit.dir" % (lib)
  if ( os.path.exists(dir_cnf) ):
    execfile(dir_cnf)
  else:
    include_dirs = []

  # FIXME: hard-coded include list
  include_dirs = ["incs"]

  # Import interfaces
  if ( os.path.exists("src/%s/interfaces_%s.F90" % (lib,lib)) ):
    sources.append("interfaces_%s.F90" % (lib))

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

  cleans = ""
  distcleans = ""

  nds_print = False

  # Initialize build flags
  makefile += "AM_CFLAGS = @ABI_CPPFLAGS@\n"
  makefile += "AM_FCFLAGS = @FPPFLAGS@ @FCFLAGS_FREEFORM@ @FCFLAGS_MODDIR@ @fc_mod_incs@ @fcflags_opt_%s@\n\n" % (lib)

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

  # Dependencies
  if ( cnf.has_option(lib,"dependencies") ):
    for dep in cnf.get(lib,"dependencies").split():
      inc += " \\\n\t@lib_%s_incs@" % (dep)

  # FIXME: hard-coded include for fallbacks
  inc += " \\\n\t@fallbacks_incs@"

  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)
      nds_print = True
    else:
      dd_srcs += " \\\n\t%s" % (src)
      if ( not os.path.exists("src/%s/%s" % (lib,src)) ):
        sys.stderr.write("Error: No such file or directory: 'src/%s/%s'\n" % (lib,src))
        sys.exit(4)

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

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

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

  makefile += "# Regular source files\n"+dd_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 = "src/%s/abinit.dep" % (lib)
  if ( os.path.exists(dep) ):
    makefile += "\nEXTRA_DIST += abinit.dep\n"
    makefile += "\n"+file(dep,"r").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)
    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 = @lib_netcdf_incs@ @lib_etsf_io_incs@ @lib_linalg_incs@ -I$(top_srcdir)/src/incs" % \
       (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(["lib_etsf_io_libs","lib_netcdf_libs","lib_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("\.cu","",src),src)

  # Write additional hand-made information
  add = "src/%s/abinit.amf" % (lib)
  if ( os.path.exists(add) ):
    makefile += "\nEXTRA_DIST += abinit.amf\n"
    makefile += "\n"+file(add,"r").read()

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

  # Output to Makefile.am
  mf = file("src/%s/Makefile.am" % (lib),"w")
  mf.write(makefile)
  mf.close()
