#!/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
except ImportError:
    from ConfigParser import ConfigParser,NoOptionError
from time import gmtime,strftime

import subprocess
import os
import re
import sys

if ( sys.version_info.major == 3 ):
    from subprocess import getoutput
else:
    from commands import getoutput

class MyConfigParser(ConfigParser):

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

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

#
# Subroutines
#

# Macro header
def macro_header(name,stamp):

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

#
# Command-line options 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)



# Define macro header
def macro_define_header():

  return """


# AFB_OPTIONS_DEFINE()
# --------------------
#
# Declares command-line arguments for the "configure" script.
#
AC_DEFUN([AFB_OPTIONS_DEFINE],[
"""



# Define macro footer
def macro_define_footer():

  return "]) # AFB_OPTIONS_DEFINE\n"



# Init macro header
def macro_setup_header():

  return """


# AFB_OPTIONS_SETUP()
# -------------------
#
# Sets the default values of command-line arguments.
#
AC_DEFUN([AFB_OPTIONS_SETUP],[
"""



# Init macro footer
def macro_setup_footer():

  return "]) # AFB_OPTIONS_SETUP\n"



# Backup macro header
def macro_backup_header():

  return """


# AFB_OPTIONS_BACKUP()
# --------------------
#
# Saves all command-line arguments.
#
AC_DEFUN([AFB_OPTIONS_BACKUP],[
"""



# Backup macro footer
def macro_backup_footer():

  return "]) # AFB_OPTIONS_BACKUP\n"



# Restore macro header
def macro_recall_header():

  return """


# AFB_OPTIONS_RECALL()
# --------------------
#
# Restores all previously-saved command-line arguments.
#
AC_DEFUN([AFB_OPTIONS_RECALL],[
"""



# Restore macro footer
def macro_recall_footer():

  return "]) # AFB_OPTIONS_RECALL\n"



# Changed options macro
def macro_changed_template():

  return """


dnl AFB_OPTIONS_CHANGED()
dnl ---------------------
dnl
dnl Display changes of user interface between versions and warns about
dnl obsolete uses.
dnl
AC_DEFUN([AFB_OPTIONS_CHANGED],[
  AC_MSG_NOTICE([reporting user interface changes:])
@MACRO@
]) dnl AFB_OPTIONS_CHANGED
"""



# Parsing macro
def macro_parse_template():

  return """


dnl AFB_OPTIONS_PARSE()
dnl -------------------
dnl
dnl Parses command-line arguments.
dnl
AC_DEFUN([AFB_OPTIONS_PARSE],[
  AC_REQUIRE([AC_PROG_EGREP])
  AC_MSG_NOTICE([parsing command-line options])
@MACRO@
]) dnl AFB_OPTIONS_PARSE
"""



# Defines and conditionals
def macro_triggers_template():

  return """


dnl AFB_OPTIONS_TRIGGERS()
dnl ----------------------
dnl
dnl Set triggers associated to 'enable_*' options (AC_DEFINE and
dnl AM_CONDTIONAL).
dnl
AC_DEFUN([AFB_OPTIONS_TRIGGERS],[
  AC_MSG_NOTICE([setting triggers associated to command-line options])
@MACRO@
]) dnl AFB_OPTIONS_TRIGGERS
"""



# Option parser generator
def parse_opt(opt,values):

  ret = "  dnl Parse %s\n" % (opt)

  if ( len(values.split()) > 1 ):
    ret += """  if test "${%s}" != ""; then
    for v in `echo "${%s}" | sed -e 's/+/ /g'`; do
      opt_ok="no"
      for r in %s; do
        if test "${v}" = "${r}";then
          opt_ok="yes"
          break
        fi
      done
      test "${opt_ok}" = "no" && break
    done
    if test "${opt_ok}" = "no"; then
      AC_MSG_WARN([%s = (%s)])
      AC_MSG_ERROR([invalid option: %s = ${v}])
    fi
  fi
""" % (opt,opt,values,opt,"|".join(values.split()),opt)

  else:

    if ( values == "@float" ):
      ret += """ if test "${%s}" != ""; then
    opt_ok="no"
    test "`echo "${%s}" | \\
      ${EGREP} -e '^-?[[0-9]]+\.[[0-9]]+$'`" != "" && opt_ok="yes"
    test "`echo "${%s}" | \\
      ${EGREP} -e '^-?[[0-9]]+\.[[0-9]]+[[Ee]]-?[[0-9]]+$'`" != "" && opt_ok="yes"
    if test "${opt_ok}" = "no"; then
      AC_MSG_ERROR([invalid float: %s = ${%s}])
    fi
  fi
""" % (opt,opt,opt,opt,opt)

    elif ( values == "@includes" ):
      ret += """ if test "${%s}" != ""; then
    opt_ok="yes"
    for v in ${%s}; do
      if test "`echo "${v}" | grep '^-I'`" = ""; then
        opt_ok="no"
        break
      fi
    done
    if test "${opt_ok}" = "no"; then
      AC_MSG_ERROR([invalid include statement in %s: ${v}])
    fi
  fi
""" % (opt,opt,opt)

    elif ( values == "@integer" ):
      ret += """ if test "${%s}" != ""; then
    opt_ok="yes"
    test "`echo "${%s}" | ${EGREP} -e '^-?[[0-9]]+$'`" = "" && opt_ok="no"
    if test "${opt_ok}" = "no"; then
      AC_MSG_ERROR([invalid integer: %s = ${%s}])
    fi
  fi
""" % (opt,opt,opt,opt)

    elif ( values == "@libs" ):
      ret += """ if test "${%s}" != ""; then
    opt_ok="yes"
    for v in ${%s}; do
      if test "`echo "${v}" | grep '^-[[LloW]]'`" = ""; then
        opt_ok="no"
        break
      fi
    done
    if test "${opt_ok}" = "no"; then
      AC_MSG_ERROR([invalid library statement in %s: ${v}])
    fi
  fi
""" % (opt,opt,opt)

    else:

      ret += "  dnl FIXME: NOT IMPLEMENTED!\n"

  return ret



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

#
# Main program
#

# Initial setup
my_name    = "make-macros-options"
my_configs = ["config/specs/options.conf"]
my_output  = "config/m4/auto-options.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 open(s)
cnf = MyConfigParser()
for cnf_file in my_configs:
  if ( not os.path.exists(cnf_file) ):
    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.read(my_configs[0])
re_en = re.compile("enable_")
re_wi = re.compile("with_")
all_args = cnf.sections()
all_args.sort()
ac_args = { "enable":list(), "with":list() }
for arg in all_args:
  arg_stat = cnf.get(arg,"status")
  if ( (arg_stat != "removed") and (arg_stat != "dropped") ):
    if ( re_en.match(arg) ):
      ac_args["enable"].append(arg)
    if ( re_wi.match(arg) ):
      ac_args["with"].append(arg)
ac_args["enable"].sort()
ac_args["with"].sort()

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

# Start writing define macro
m4.write(macro_define_header())

# Process arguments
defaults = ""
parse = ""
for arg in ("enable","with"):
  m4.write("\n  dnl\n  dnl --%s arguments\n  dnl\n" % (arg))
  defaults += "\n  dnl\n  dnl --%s arguments\n  dnl\n\n" % (arg)
  for opt in ac_args[arg]:
    var = re.sub(arg+"_","",opt)
    opt_name = re.sub("_","-",var)
    opt_desc = cnf.get(opt,"description")
    opt_stat = cnf.get(opt,"status")
    try:
      opt_dflt = cnf.get(opt,"default")
    except:
      opt_dflt = None
    try:
      opt_nega = cnf.get(opt,"negative")
    except:
      opt_nega = ""
    try:
      opt_vals = cnf.get(opt,"values")
    except:
      opt_vals = None
    if ( (opt_stat == "hidden") and (not opt_dflt is None) ):
      parse += """
  dnl Hidden option: %s
  dnl ac_configure_args="${ac_configure_args} --%s=\\\"${%s}\\\""
  %s="%s"
""" % \
        (opt,re.sub("_","-",opt),opt,opt,opt_dflt)
    else:
      m4.write("\n  AC_ARG_%s(%s,\n" % (arg.upper(),opt_name) \
        + "    AC_HELP_STRING([--%s-%s],\n      [%s (default: %s)]))\n" % \
          (arg,opt_name,opt_desc,opt_dflt))

      if ( opt_dflt is not None ):
        defaults += "  if test \"${%s_%s}\" = \"\"; then\n    %s_%s=\"%s\"\n  fi\n" % \
          (arg,var,arg,var,opt_dflt)

      if ( arg == "with" ):
        defaults += "  if test \"${%s_%s}\" = \"no\"; then\n    %s_%s=\"%s\"\n  fi\n" % \
          (arg,var,arg,var,opt_nega)

      if ( opt_vals is not None ):
        parse += "\n"+parse_opt(opt,opt_vals)
      elif ( arg == "enable" ):
        parse += "\n"+parse_opt(opt,"no yes")

      m4.write("  AC_SUBST(%s_%s)\n" % (arg,var))

# Finish writing define macro
m4.write(macro_define_footer())

# Start writing setup macro
m4.write(macro_setup_header())

# Process arguments
m4.write(defaults)

# Finish writing setup macro
m4.write(macro_setup_footer())

# Start writing backup macro
m4.write(macro_backup_header())

# Process arguments
for arg in ("enable","with"):
  m4.write("\n  dnl\n  dnl --%s arguments\n  dnl\n" % (arg))
  for opt in ac_args[arg]:
    var = re.sub(arg+"_","",opt)
    m4.write("  cmd_%s_%s=\"${%s_%s}\"\n" % (arg,var,arg,var))

# Do not forget "prefix"
m4.write("""
  dnl
  dnl Prefix
  dnl
  if test "${prefix}" != "NONE"; then
    cmd_prefix="${prefix}"
  fi
""")

# Finish writing backup macro
m4.write(macro_backup_footer())

# Start writing recall macro
m4.write(macro_recall_header())

# Process arguments
for arg in ("enable","with"):
  m4.write("\n  dnl\n  dnl --%s arguments\n  dnl\n" % (arg))
  for opt in ac_args[arg]:
    var = re.sub(arg+"_","",opt)
    m4.write("\n  if test \"${cmd_%s_%s}\" != \"\"; then\n    %s_%s=\"${cmd_%s_%s}\"\n  fi\n" % \
      (arg,var,arg,var,arg,var))

# Do not forget "prefix"
m4.write("""
  dnl
  dnl Prefix
  dnl
  if test "${cmd_prefix}" != ""; then
    prefix="${cmd_prefix}"
  fi
""")

# Finish writing recall macro
m4.write(macro_recall_footer())

# Build changed macro
changed = ""

for arg in all_args:

  # Init
  arg_stat = cnf.get(arg,"status")
  try:
    arg_repl = cnf.get(arg,"use_instead")
  except:
    arg_repl = ""

  # Removed options
  if ( (arg_stat == "removed") or (arg_stat == "dropped") ):
    arg1 = re.sub("_","-",arg)
    changed += "\n  dnl Removed --%s option" % (arg1)
    if ( arg_stat == "removed" ):
      changed += "\n  AC_MSG_NOTICE([ * --%s removed])" % (arg1)
      if ( arg_repl != "" ):
        changed += "\n  AC_MSG_NOTICE([   === please use --%s instead ===])" % \
          (re.sub("_","-",arg_repl))
    changed += """
  if test "${%s}" != ""; then
    AC_MSG_ERROR([removed option --%s has been used])
  fi
""" % (re.sub("-","_",arg1),arg1)

  # New options
  elif ( arg_stat == "new" ):
    arg2 = re.sub("_","-",arg)
    changed += "\n  dnl New --%s option\n" % (arg2)
    changed += "  AC_MSG_NOTICE([ * new option --%s is available])\n" % (arg2)

  # Changed UIs
  elif ( re.match("changed",arg_stat) ):
    arg1 = re.sub("_","-",arg)
    changed += """
  dnl UI change for --%s option
  AC_MSG_NOTICE([ * --%s has been modified])
  AC_MSG_NOTICE([   (%s)])
  AC_MSG_NOTICE([   please check that --%s="${%s}" is OK for you])
""" % (arg1,arg1,arg_stat,arg1,re.sub("-","_",arg1))

  # Renamed options
  elif ( re.match("renamed",arg_stat) ):
    arg1 = re.sub("_","-",arg_stat.split()[1])
    arg2 = re.sub("_","-",arg)
    changed += """
  dnl --%s > --%s
  AC_MSG_NOTICE([ * --%s renamed --%s])
  if test "${%s}" != ""; then
    AC_MSG_ERROR([renamed option --%s has been used])
  fi
""" % (arg1,arg2,arg1,arg2,re.sub("-","_",arg1),arg1)

# Write changed macro
m4.write(re.sub("@MACRO@",changed,macro_changed_template()))

# Write parse macro
m4.write(re.sub("@MACRO@",parse,macro_parse_template()))

# Build triggers macro
triggers = ""

for opt in ac_args["enable"]:
  opt_desc = cnf.get(opt,"description")
  opt_desc = opt_desc[0].lower() + opt_desc[1:]
  try:
    opt_cnds = cnf.get(opt,"conditionals").split()
  except:
    opt_cnds = list()
  try:
    opt_defs = cnf.get(opt,"defines").split()
  except:
    opt_defs = list()

  if ( len(opt_cnds) + len(opt_defs) > 0 ):
    triggers += """

  dnl Triggers for %s
  AC_MSG_CHECKING([whether to %s])
  AC_MSG_RESULT([${%s}])
""" % (opt,opt_desc,opt)

  if ( len(opt_cnds) > 0 ):
    for cnd in opt_cnds:
      if ( cnd[0] == "!" ):
        val = "no"
        cnd = cnd[1:]
      else:
        val = "yes"
      triggers += """
  if test "${%s}" = "%s"; then
    AC_MSG_NOTICE([triggering the '%s' conditional])
  fi
  AM_CONDITIONAL([%s],[test "${%s}" = "%s"])""" % (opt,val,cnd,cnd,opt,val)

  if ( len(opt_defs) > 0 ):
    for cpp in opt_defs:
      if ( cpp[0] == "!" ):
        val = "no"
        cpp = cpp[1:]
      else:
        val = "yes"
      triggers += """
  if test "${%s}" = "%s"; then
    AC_MSG_NOTICE([defining the '%s' preprocessing macro])
    AC_DEFINE([%s],1,
      [Define to 1 if you want to %s.])
  fi""" % (opt,val,cpp,cpp,opt_desc)

# Write triggers macro
m4.write(re.sub("@MACRO@",triggers,macro_triggers_template()))

# Finish
m4.close()

tmp = getoutput("./config/scripts/add-header-typed Autoconf %s" % (my_output))
if ( tmp != "" ):
  print(tmp)

# Write option dumper (for debugging)
dumper = open("fallbacks.dump.in","a")
dumper.write("\n                    # ----------------------------------- #\n")
dumper.write("\n# Command-line options (script: %s)\n" % (my_name))
for arg in all_args:
  arg_stat = cnf.get(arg,"status")
  if ( (arg_stat != "hidden") and \
       (arg_stat != "removed") and \
       (arg_stat != "dropped") and \
       (not re.match("group", arg)) ):
    var = re.sub("-","_",opt_name)
    dumper.write("%s=\"@%s@\"\n" % (arg,arg))
dumper.write("\n")
dumper.close()
