#!/usr/bin/env python3

"""
Automatically updates parameterlist.txt by searching all *.hh files
for usage of getParam or getParamFromGroup.
"""

import os

# find the content of the given string between the first matching pair of opening/closing keys
def getEnclosedContent(string, openKey, closeKey):

    # cut off everything before the first occurence of openKey
    string = openKey + string.partition(openKey)[2]

    # get content between mathing pair
    rest = string.partition(closeKey)
    result, rest = rest[0] + closeKey, rest[2]
    while result.count(openKey) != result.count(closeKey):
        rest = rest.partition(closeKey)
        if rest[1] == '': raise IOError('Could not get content between "{}" and "{}" in given string "{}"'.format(openKey, closeKey, string))
        result, rest = result + rest[0] + closeKey, rest[2]

    return result.partition(openKey)[2].rpartition(closeKey)[0]

# extract a parameter from a given line
def extractParamName(line):

    # split occurrences of getParam<T>(CALLARGS) or getParamFromGroup<T>(CALLARGS)
    # into the template arguments T and the function arguments CALLARGS
    if 'getParamFromGroup<' in line:
        line = line.split('getParamFromGroup')[1]
        hasGroup = True
    elif 'getParam<' in line:
        line = line.split('getParam')[1]
        hasGroup = False
    else:
        return {}

    # TODO: Support this also
    if line.count('getParam') > 1:
        raise IOError('Cannot process multiple occurrences of "getParam" in one line')

    # remove trailing spaces and cut off everything behind semicolon
    line = line.strip('\n').strip(' ').split(';')[0]

    # extract template arg between '<' and '>'
    paramType = getEnclosedContent(line, '<', '>')

    # extract function arguments
    functionArgs = line.partition('<' + paramType + '>')[2]
    functionArgs = getEnclosedContent(functionArgs, '(', ')')

    if hasGroup: functionArgs = functionArgs.partition(',')[2]
    functionArgs = functionArgs.partition(',')
    paramName = functionArgs[0]
    defaultValue = None if not functionArgs[2] else functionArgs[2]

    paramType = paramType.strip(' ')
    paramName = paramName.strip(' ')
    if (defaultValue): defaultValue = defaultValue.strip(' ')

    # if interior spaces occur in the parameter name, we can't identify it
    if paramName[0] != '"' or paramName[-1] != '"' or ' ' in paramName:
        raise IOError("Could not correctly process parameter name")

    return {'paramType': paramType, 'paramName': paramName.strip('"'), 'defaultValue': defaultValue}

# extract all parameters from a given file
def getParamsFromFile(file):
    parameters = []
    errors = {}
    with open(file) as f:
        for lineIdx, line in enumerate(f):
            try:
                param = extractParamName(line);
                if param: parameters.append(param);
            except IOError as e:
                errors[lineIdx] = {'line': line.strip(), 'message': e}

    # print encountered errors
    if errors:
        print('\n\n{} paramter{} in file {} could not be retrieved automatically. Please check them yourself:'.format(len(errors), 's' if len(errors) > 1 else '', file))
        for lineIdx in errors:
            print("\n\t-> line {}: {}".format(lineIdx, errors[lineIdx]['line']))
            print("\t\t-> error message: {}".format(errors[lineIdx]['message']))

    return parameters

# search all *.hh files for parameters
# TODO: allow runtime args with extensions and folder(s) to be checked
parameters = []
rootDir = os.path.dirname(os.path.abspath(__file__)) + "/../../dumux"
for root, _, files in os.walk(rootDir):
    for file in files:
        if os.path.splitext(file)[1] == ".hh" and os.path.splitext(file)[0] != 'parameters':
            parameters.extend(getParamsFromFile(os.path.join(root, file)))

# make sorted dictionary of the entries
# treat duplicates (could have differing default values or type names - e.g. via aliases)
parameterDict = {}
for params in parameters:
    key = params['paramName']
    if key in parameterDict:
        parameterDict[key]['defaultValue'].append(params['defaultValue'])
        parameterDict[key]['paramType'].append(params['paramType'])
    else:
        parameterDict[key] = params
        parameterDict[key]['defaultValue'] = [params['defaultValue']]
        parameterDict[key]['paramType'] = [params['paramType']]
sortedParameterDict = {key: value for key, value in sorted(parameterDict.items())}

tableEntriesWithGroup = []
tableEntriesWithoutGroup = []
previousGroupEntry = None

for key in sortedParameterDict:

    entry = sortedParameterDict[key]
    hasGroup = True if entry['paramName'].count('.') != 0 else False
    groupEntry = '-' if not hasGroup else entry['paramName'].split('.')[0]
    paramName = entry['paramName'] if not hasGroup else entry['paramName'].partition('.')[2]

    # TODO: selection scheme in case of multiple occurrences? For now we use the first one
    paramType = entry['paramType'][0]
    defaultValue = entry['defaultValue'][0] if entry['defaultValue'][0] != None else ''

    if groupEntry != previousGroupEntry:
        previousGroupEntry = groupEntry
        if hasGroup: groupEntry = '\\b ' + groupEntry

    tableEntry = ' * | {} | {} | {} | {} | TODO: explanation |'.format(groupEntry, paramName , paramType , defaultValue)

    if hasGroup: tableEntriesWithGroup.append(tableEntry)
    else: tableEntriesWithoutGroup.append(tableEntry)

# combine entries
tableEntries = tableEntriesWithoutGroup + tableEntriesWithGroup

# make a backup of the old parameterlist.txt file
copyfile(rootDir + '/../doc/doxygen/extradoc/parameterlist.txt', rootDir + '/../doc/doxygen/extradoc/parameterlist_old.txt')

header = """/*!
 *\\file
 *\ingroup Parameter
 *
 *\\brief List of currently useable run-time parameters
 *
 * The listed run-time parameters are available in general,
 * but we point out that a certain model might not be able
 * to use every parameter!
 *
 * | Group       | Parameter    | Type       | Default Value     | Explanation |
 * | :-          | :-           | :-         | :-                | :-          |
 * | -           | ParameterFile | std::string| executable.input  | name of the parameter file |
"""

# overwrite the old parameterlist.txt file
with open(rootDir + '/../doc/doxygen/extradoc/parameterlist.txt', "w") as outputfile:
    outputfile.write(header)
    for e in tableEntries:
        outputfile.write(e + '\n')
    outputfile.write(' */\n')