#!/usr/bin/python

# idgen: A tool to dynamically generate IDs and references thereon
# Copyright (C) 2015  Andreas Walz

# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.

# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.

# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.


import sys
import os
import argparse


#
# _____________________________________________________________________________
#
def main(argv):

    printWelcome()

    # define an argument parser and the arguments
    parser = argparse.ArgumentParser()
    parser.add_argument('-f', '--force', \
        action = 'store_true', \
        required = False, \
        default = False, \
        help = 'overwrite existing files')
    parser.add_argument('-m', '--manual', \
        action = 'store_true', \
        required = False, \
        default = False, \
        help = 'print manual')
    parser.add_argument('files', \
        nargs = '*', \
        metavar = 'input-file', \
        help = 'input file to process')
    # parse the arguments
    args = parser.parse_args()

    if args.manual:
        printManual()
        return

    if len(args.files) == 0:
        parser.print_help()
        return

    filesTmp = []        
    filesOut = []        
    filesIn = args.files    
    for file in filesIn:
        fileTmp = file + ".tmp"
        fileOut = file + ".out"
        print "Going to process: '" + file + "' -> '" + fileOut + "'"
        if os.path.isfile(fileTmp) and not args.force:
            print "  > Warning: intermediate file '" + fileTmp + \
                "' already exists. Won't overwrite ..."
            fileTmp = ""
        filesTmp += [fileTmp]
        if os.path.isfile(fileOut) and not args.force:
            print "  > Warning: output file '" + fileOut + \
                "' already exists. Won't overwrite ..."
            fileOut = ""
        filesOut += [fileOut]
    print ""

    list = []
    dict = {}
    # generate IDs
    for i in range(0, len(filesIn)):
        generateIDsInFile(filesIn[i], filesTmp[i], list);
    # build dictionary
    for i in range(0, len(filesTmp)):
        buildDictFromFile(filesTmp[i], dict);
    # replace place holders from dictionary
    for i in range(0, len(filesTmp)):
        replaceInFile(filesTmp[i], filesOut[i], dict);
    # remove intermediate temporary files
    for file in filesTmp:
        if os.path.isfile(file):
            os.remove(file)


#
# _____________________________________________________________________________
#
def printManual():
        print "usage: ./idgen.py <input-files>"
        print ""
        print "       idgen is a simple Python-based tool to dynamically generate IDs and"
        print "       resolving references thereon in text files. It basically consists of"
        print "       two processing steps: (1) dynamically generating IDs and (2) replacing"
        print "       references thereon."
        print "       "
        print "       Fields processed by idgen need to be enclosed in double curly brackets"
        print "       {{field}}."
        print "       "
        print "       (1) In the first processing step IDs are generated by replacing every"
        print "       single occurence of '%' within a field by a number. The number used to"
        print "       replace a specific '%' is taken from a global array where the position"
        print "       within the array is determined by the number of occurences of '%' within"
        print "       the current field that preceed the current occurence. That is, in a field"
        print "       {{ID/%.%.%}}, the '%' will be replaced by numbers taken from the array at"
        print "       positions 0, 1, and 2, respectively: {{ID/<0>.<1>.<2>}}. The numbers in"
        print "       the array may be influenced by prepending control characters to the '%',"
        print "       e.g. '%+%, '%0', or '%1'. '%+' increments the number by one before using"
        print "       it and resets all numbers at higher positions to zero. '%0' and '%1' set"
        print "       the number to zero or one, respectively, before using it. This allows to"
        print "       dynamically create a list of IDs, e.g.:"
        print "       "
        print "       {{ID/%1.%1}}  ->  {{ID/1.1}}"
        print "       {{ID/%.%+}}   ->  {{ID/1.2}}"
        print "       {{ID/%.%+}}   ->  {{ID/1.3}}"
        print "       {{ID/%.%+}}   ->  {{ID/1.4}}"
        print "       {{ID/%+.%1}}  ->  {{ID/2.1}}"
        print "       {{ID/%.%+}}   ->  {{ID/2.2}}"
        print "       {{ID/%.%+}}   ->  {{ID/2.3}}"
        print "       "
        print "       (2) In the second step fields are used to build a dictionary of references"
        print "       and references are resolved. For this, (dynamically generated) IDs can be"
        print "       assigned a static and unique identifier used for referring to the ID by"
        print "       prepending it to the ID, e.g. {{myIDa:ID/%.%+}}. Within the text, a reference"
        print "       to the ID is given by placing the corresponding static identifier within"
        print "       double curly brackets, e.g. {{myIDa}}. In either case, the field will finally"
        print "       by replaced by the ID, e.g.:"
        print "       "
        print "       {{myIDa:ID/1.2}}  ->  ID/1.2"
        print "       {{myIDa}}         ->  ID/1.2"
        print "       "
        print "       The tool is able to handle multiple files and resolve references that appear"
        print "       in the text before they are defined. For every input file it creates an output"
        print "       file by appending '.out' to the filename. An intermediate temporary set of"
        print "       file (by appending '.tmp' to the input filename) is used to hold the state"
        print "       between processing steps (1) and (2)."
        print "       "

#
# _____________________________________________________________________________
#
def printWelcome():
        print "idgen  Copyright (C) 2015  Andreas Walz"
        print "This program comes with ABSOLUTELY NO WARRANTY."
        print "This is free software, and you are welcome to redistribute it"
        print "under certain conditions."
        print ""


#
# _____________________________________________________________________________
#
def generateIDsInFile(filenameIn, filenameOut, list):
    if filenameIn != "" and filenameOut != "":
        try:
            fi = open(filenameIn, 'r')
            fo = open(filenameOut, 'w')
            print "Generating IDs: '" + filenameIn + "' -> '" + filenameOut + "'"
            for line in fi:
                fo.write(generateIDsInLine(line, list));
            fi.close();
            fo.close();
        except:
            print "  > Error: Could not open file '" + filenameIn + \
                "' or '" + filenameOut + "'"


#
# _____________________________________________________________________________
#
def generateIDsInLine(str, list):
    pos = 0
    while pos != -1:
        pos1 = str.find("{{", pos)
        pos2 = str.find("}}", pos1)
        if pos1 != -1 and pos2 != -1:
            newstr = str[: pos1 + 2] + generateID(str[pos1 + 2 : pos2], list)
            pos = len(newstr) + 2
            str = newstr + str[pos2 :]
        else:
            pos = -1
    return str


#
# _____________________________________________________________________________
#
def generateID(str, list):
    level = 0
    pos = 0
    while pos >= 0:
        pos = str.find("%")
        if pos >= 0:
            # expand id level list
            while level >= len(list):
                list += [0];
            pat = "%"
            if str.find("%+", pos) == pos:
                pat = "%+"
                list[level] = list[level] + 1
                for i in range(level + 1, len(list)):                    
                    list[i] = 0
            elif str.find("%0", pos) == pos:
                pat = "%0"
                list[level] = 0
            elif str.find("%1", pos) == pos:
                pat = "%1"
                list[level] = 1
            str = str.replace(pat, "{0:d}".format(list[level]), 1);
            level += 1
    return str;


#
# _____________________________________________________________________________
#
def buildDictFromFile(filename, dict):
    if filename != "":
        try:
            f = open(filename, 'r')
            print "Building dictionary: from file '" + filename + "'"
            for line in f:
                buildDictFromLine(line, dict)
            f.close();
        except:
            print "Error opening file", filename


#
# _____________________________________________________________________________
#
def buildDictFromLine(str, dict):
    pos = 0
    while pos != -1:
        pos1 = str.find("{{", pos)
        pos2 = str.find("}}", pos1)
        pos3 = str.find(":", pos1)
        if pos1 != -1 and pos2 != -1:
            if pos3 > pos1 and pos3 < pos2:
                dict[str[pos1 + 2 : pos3]] = str[pos3 + 1 : pos2]
            pos = pos2 + 2
        else:
            pos = -1


#
# _____________________________________________________________________________
#
def replaceInFile(filenameIn, filenameOut, dict):
    if filenameIn != "" and filenameOut != "":
        try:
            fi = open(filenameIn, 'r')
            fo = open(filenameOut, 'w')
            print "Resolving references: '" + filenameIn + "' -> '" + filenameOut + "'"            
            for line in fi:
                fo.write(replaceInLine(line, dict));
            fi.close();
            fo.close();
        except:
            print "  > Error: Could not open file '" + filenameIn + \
                "' or '" + filenameOut + "'"


#
# _____________________________________________________________________________
#
def replaceInLine(str, dict):
    pos = 0
    while pos != -1:
        pos1 = str.find("{{", pos)
        pos2 = str.find("}}", pos1)
        pos3 = str.find(":", pos1)
        if pos1 != -1 and pos2 != -1:
            # extract key from {{key}} or {{key:value}}
            if pos3 > pos1 and pos3 < pos2:
                key = str[pos1 + 2 : pos3]
            else:
                key = str[pos1 + 2 : pos2]
            # get value either from dictionary or from text
            if key in dict:
                value = dict[key]
            else:
                value = str[pos1 + 2 : pos2]
            str = str[:pos1] + value + str[pos2 + 2:]
            pos = pos1 + len(value)
        else:
            pos = -1
    return str


#
# _____________________________________________________________________________
#
if __name__ == "__main__":
    main(sys.argv[1:]);

