import org.json.*;
import groovy.json.JsonSlurper


//The GlossaryHandler reads all files from a given folder path url.
//It expects the files are JSON formatted glossary exports from EA.
//There is one single glossary file per EA project expected.
//All entries not being part of the glossaryTypes list will be removed from the glossaries.
//If the glossaryTypes list is empty, all entries will be used.
//Each glossary is sorted by terms in alphabetical order.
//Using the outputFormat the json formatted file is overwritten with
//the sorted and filtered list to be included in asciidoc files.
//An additional file is written to the folder containing the entries of all
//single glossaries in an alphabetical sorted order.
//This global glossary is stored as glossary.ad.
class GlossaryHandler
{
    private List<List<Object>> globalGlossaryList = []

    //Collect all files inside the glossary folder (jsonFilePath)
    //Each file is processed individually,
    //finally a merged glossary of all single glossaries is written
    void execute(jsonFilePath, outputFormat, glossaryTypes) {
        def count = 0
        def glossaryFile
        new File(jsonFilePath).eachFileRecurse { file ->
            if (file.isFile()) {
                glossaryFile = file.canonicalPath
                reformatJsonFile(glossaryFile, outputFormat, glossaryTypes)
                count = count+1
            }
        }
        if (!globalGlossaryList.isEmpty()) {
            if (count == 1) {
                //use the already sorted and stored file, but rename it
                new File(glossaryFile).renameTo(new File(jsonFilePath+"/glossary.ad"))
            }
            else {
                //sort and store the global glossary (summary of all single glossaries)
                sortAndWriteToFile(jsonFilePath+"/glossary.ad", globalGlossaryList, outputFormat)
            }
        }
    }

    //Reads the json file if available, other terminates silently.
    //the glossary list read is filtered by the glossaryTypes list and
    //sorted by term in alphabetical order.
    //The resulting glossary list is handed over to a
    //file handler to be sorted and written to file system
    void reformatJsonFile(jsonFilePath, outputFormat, glossaryTypes) {
        if (new File(jsonFilePath).isFile()) {
            def jsonSlurper = new JsonSlurper()
            def glossaryList = jsonSlurper.parse(new File(jsonFilePath))
            if (glossaryTypes && !glossaryTypes.isEmpty()) {
                glossaryList.retainAll { glossaryTypes.contains(it['type']) }
            }
            globalGlossaryList.addAll(glossaryList)
            sortAndWriteToFile(jsonFilePath, glossaryList, outputFormat)
        }
    }

    //Sorts the given glossaryList in alphabetical order and calls the output formatter
    //to create the new output format and write the file.
    void sortAndWriteToFile(jsonFilePath, glossaryList, outputFormat) {
        glossaryList.sort{a, b -> a['term'].compareTo(b['term'])}
        writeFormattedGlossaryToFile(jsonFilePath, glossaryList, outputFormat)
    }

    //Filehandler to write a glossary list to a new file.
    //An output format is used to format each glossary entry in a certain output format.
    //Following placeholder are defined: ID, TERM, MEANING, TYPE. One or many of these placeholder can
    //be used by the output format. A valid output format: "TERM:: MEANING" to include the glossary
    //as a flat list. Other formats can be used to include it as a table row.
    void writeFormattedGlossaryToFile(jsonFilePath, glossaryList, outputFormat) {
        new File(jsonFilePath).withWriter('utf-8') { writer ->
            glossaryList.each { key ->
                def out = outputFormat.replace("ID" , key['termID'])
                out = out.replace("TERM" , key['term'])
                out = out.replace("MEANING" , key['meaning'])
                out = out.replace("TYPE" , key['type'])
                writer.writeLine(out)
            }
        }
    }
}

//tag::streamingExecute[]
task streamingExecute(
        dependsOn: [],
        description: 'extends the String class with a better .executeCmd'
) {
    doLast {
        //I need a streaming execute in order to export from EA
        String.metaClass.executeCmd = { silent ->
            //make sure that all paramters are interpreted through the cmd-shell
            //TODO: make this also work with *nix
            def command = delegate.value
            if (command.toString().startsWith('[')) {
                command = new String(delegate.value)
            }
            def p
            try {
                String commandUTF8 = new String(command.toString().getBytes(), 'utf-8');
                p = "cmd.exe /c ${commandUTF8}".execute()
            } catch (Exception e) {
                throw new Exception ("""
sorry, this task currently needs needs windows as operation system
if you started this task from WSL, please switch to powershell
""" + e.getMessage())

            }
            def result = [std: '', err: '']
            def ready = false
            Thread.start {
                def reader = new BufferedReader(new InputStreamReader(p.in))
                def line = ""
                while ((line = reader.readLine()) != null) {
                    if (silent != false) {
                        println "" + line
                    }
                    result.std += line + "\n"
                }
                ready = true
                reader.close()
            }
            p.waitForOrKill(30000)
            def error = p.err.text

            if (error.isEmpty()) {
                return result
            } else {
                throw new RuntimeException("\n" + error)
            }
        }
    }
}
//end::streamingExecute[]

//tag::exportEA[]
task exportEA(
        dependsOn: [streamingExecute],
        description: 'exports all diagrams and some texts from EA files',
        group: 'docToolchain'
) {
    doFirst {
    }
    doLast {
        logger.info("docToolchain > exportEA: " + docDir)
        logger.info("docToolchain > exportEA: " + mainConfigFile)
        def configFile = new File(docDir, mainConfigFile)
        def config = new ConfigSlurper().parse(configFile.text)
        def scriptParameterString = ""
        def exportPath = ""
        def searchPath = ""
        def glossaryPath = ""
        def imageFormat = ""
        def diagramFilter = ""
        def readme = """This folder contains exported diagrams or notes from Enterprise Architect.

Please note that these are generated files but reside in the `src`-folder in order to be versioned.

This is to make sure that they can be used from environments other than windows.

# Warning!

**The contents of this folder	will be overwritten with each re-export!**

use `gradle exportEA` to re-export files
"""

        if (!config.exportEA.connection.isEmpty()) {
            logger.info("docToolchain > exportEA: found " + config.exportEA.connection)
            scriptParameterString = scriptParameterString + "-c \"${config.exportEA.connection}\""
        }
        if (!config.exportEA.packageFilter.isEmpty()) {
            def packageFilterToCreate = config.exportEA.packageFilter as List
            logger.info("docToolchain > exportEA: package filter list size: " + packageFilterToCreate.size())
            packageFilterToCreate.each { packageFilter ->
                scriptParameterString = scriptParameterString + " -p \"${packageFilter}\""
            }
        }
        if (!config.exportEA.diagramFilter.isEmpty()) {
            logger.info("docToolchain > exportEA: diagram filter: " + config.exportEA.diagramFilter)
            scriptParameterString = scriptParameterString + "-df \"${config.exportEA.diagramFilter}\""
        }
        if (!config.exportEA.exportPath.isEmpty()) {
            exportPath = new File(docDir, config.exportEA.exportPath).getAbsolutePath()
        } else {
            exportPath = new File(docDir, 'src/docs').getAbsolutePath()
        }
        if (!config.exportEA.searchPath.isEmpty()) {
            searchPath = new File(docDir, config.exportEA.searchPath).getAbsolutePath()
        }
        else if (!config.exportEA.absoluteSearchPath.isEmpty()) {
            searchPath = new File(config.exportEA.absoluteSearchPath).getAbsolutePath()
        }
        else {
            searchPath = new File(docDir, 'src').getAbsolutePath()
        }
        if (config.exportEA.imageFormat.isEmpty()) {
            imageFormat = ".png"
        } else if(!config.exportEA.imageFormat.startsWith(".")) {
            imageFormat = "." + config.exportEA.imageFormat
        } else {
            imageFormat = config.exportEA.imageFormat
        }
        if (!config.exportEA.verboseLevel.isEmpty()) {
            logger.info("docToolchain > exportEA: verboseLevel: " + config.exportEA.verboseLevel)
            scriptParameterString = scriptParameterString + "-v ${config.exportEA.verboseLevel}"
        }
        scriptParameterString = scriptParameterString + " -d \"$exportPath\""
        scriptParameterString = scriptParameterString + " -s \"$searchPath\""
        scriptParameterString = scriptParameterString + " -f \"$imageFormat\""
        logger.info("docToolchain > exportEA: exportPath: " + exportPath)

        //remove old glossary files/folder if exist
        new File(exportPath, 'glossary').deleteDir()
        //set the glossary file path in case an output format is configured, other no glossary is written
        if (!config.exportEA.glossaryAsciiDocFormat.isEmpty()) {
            //create folder to store glossaries
            new File(exportPath, 'glossary/.').mkdirs()
            glossaryPath = new File(exportPath, 'glossary').getAbsolutePath()
            scriptParameterString = scriptParameterString + " -g \"$glossaryPath\""
        }
        //configure additional diagram attributes to be exported
        if (!config.exportEA.diagramAttributes.isEmpty()) {
            scriptParameterString = scriptParameterString + " -da \"$config.exportEA.diagramAttributes\""
        }
        //configure additional diagram attributes to be exported
        if (!config.exportEA.additionalOptions.isEmpty()) {
            scriptParameterString = scriptParameterString + " -ao \"$config.exportEA.additionalOptions\""
        }
        //make sure path for notes exists
        //and remove old notes
        new File(exportPath, 'ea').deleteDir()
        //also remove old diagrams
        new File(exportPath, 'images/ea').deleteDir()

        //create a readme to clarify things
        new File(exportPath, 'images/ea/.').mkdirs()
        new File(exportPath, 'images/ea/readme.ad').write(readme)
        new File(exportPath, 'ea/.').mkdirs()
        new File(exportPath, 'ea/readme.ad').write(readme)

        //execute through cscript in order to make sure that we get WScript.echo right
        logger.info("docToolchain > exportEA: parameters: " + scriptParameterString)
        "%SystemRoot%\\System32\\cscript.exe //nologo ${projectDir}/scripts/exportEAP.vbs ${scriptParameterString}".executeCmd()
        //the VB Script is only capable of writing iso-8859-1-Files.
        //we now have to convert them to UTF-8
        new File(exportPath, 'ea/.').eachFileRecurse { file ->
            if (file.isFile()) {
                println "exported notes " + file.canonicalPath
                file.write(file.getText('iso-8859-1'), 'utf-8')
            }
        }

        //sort, filter and reformat a glossary if an output format is configured
        if (!config.exportEA.glossaryAsciiDocFormat.isEmpty()) {
            def glossaryTypes

            if (!config.exportEA.glossaryTypes.isEmpty()) {
                glossaryTypes = config.exportEA.glossaryTypes as List
            }
            new GlossaryHandler().execute(glossaryPath, config.exportEA.glossaryAsciiDocFormat, glossaryTypes);
        }
    }
}
//end::exportEA[]
