/*
* Licensed Materials - Property of IBM* and/or HCL**
* UrbanCode Deploy
* UrbanCode Build
* UrbanCode Release
* AnthillPro
* (c) Copyright IBM Corporation 2011, 2017. All Rights Reserved.
* (c) Copyright HCL Technologies Ltd. 2018. All Rights Reserved.
*
* U.S. Government Users Restricted Rights - Use, duplication or disclosure restricted by
* GSA ADP Schedule Contract with IBM Corp.
*
* * Trademark of International Business Machines
* ** Trademark of HCL Technologies Limited
*/
import com.urbancode.air.AirPluginTool
import com.urbancode.air.FileSet
import javax.xml.parsers.DocumentBuilder
import javax.xml.parsers.DocumentBuilderFactory

def workDir = new File('.').canonicalFile
def airTool = new AirPluginTool(args[0], args[1])
def props = airTool.getStepProperties()

enum FailMode {
    WARN_ONLY,
    FAIL_FAST,
    FAIL_ON_ANY_FAILURE
}

def srcDirOffset = props['srcDirOffset']
def destDirOffset = props['destDirOffset']
def failMode = FailMode.valueOf(props['failMode'])
def includes = props['includes']?.replaceAll(',', '\n')
def excludes = props['excludes']?.replaceAll(',', '\n')
def replaceText = props['replaceText']
def setAttr = props['setAttr']
def removeList = props['remove']
def insertList = props['insert']
def failWithoutMatch = props['failWithoutMatch']
def ignoreDTD = Boolean.valueOf(props['ignoreDTD'])
def prettyPrint = Boolean.valueOf(props['prettyPrint'])

def ant = new AntBuilder()
ant.taskdef(name:'xmltask', classname:'com.oopsconsultancy.xmltask.ant.XmlTask')

if (!srcDirOffset) {
    srcDirOffset = '.'
}

if (!destDirOffset) {
    destDirOffset = '.'
}

println "Working Directory: ${workDir}"
println "Source Directory Offset: ${srcDirOffset}"
println "Destination Directory Offset: ${destDirOffset}"
println "File Includes: ${includes}"
println "File Excludes: ${excludes}"
println "-----------------------------"

/*
 * Returns a String representing the path to "file", relative to "dir".
 */
Closure<String> getRelativePath = { File dir, File file ->
    file.canonicalPath - (dir.canonicalPath + File.separator)
}

FileSet fs = new FileSet(new File(workDir, srcDirOffset))
fs.include(includes)
fs.exclude(excludes)

List<File> matchedFiles = fs.files()
matchedFiles = matchedFiles.collect {new File(getRelativePath(workDir, it))}

if (ignoreDTD) {
    println("[Warning] Temporarily commenting out all DOCTYPE declarations so they may be ignored.")

    // comment out all DOCTYPE declarations
    for (File file in matchedFiles) {
        fileText = file.text
        file.withWriter{ w -> w << fileText.replaceAll("(<!DOCTYPE.*>)", "<!--\$1-->")}
    }
}

List<String> wellformedFiles = []
List<String> malformedFiles = []

DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance()
DocumentBuilder db = dbf.newDocumentBuilder()

for (File xml in matchedFiles) {
    try {
        db.parse(xml)
        wellformedFiles.add(xml.toString())
    }
    catch (Exception e) {
        malformedFiles.add(xml.toString())

        if (failMode == FailMode.FAIL_FAST) {
            println "Fail Fast: xml malformed ${xml}"
            throw new IllegalStateException("Xml malformed.", e)
        }
        println e.getMessage()
    }
}

println "Wellformed xmls: ${wellformedFiles}"
println "Malformed xmls:  ${malformedFiles}"

/*
 * If there are no well-formed XML files to update, do nothing and exit
 * successfully. Typically this might be implemented as a new Fail Mode option,
 * such as "fail if no updates", but we've already released a version of the
 * plugin that behaves incorrectly in this scenario - it ends up trying to
 * update ALL xml files recursively in the working directory. This check simply
 * prevents that unintended side effect.
 */
if (!wellformedFiles) {
    println 'No well-formed files found to update. Exiting'
    System.exit(0)
}

try {
    ant.xmltask(expandEntityReferences:'false',
            report:'false',
            failWithoutMatch:failWithoutMatch,
            outputter: prettyPrint?'simple':'default',
            toDir: new File(workDir, destDirOffset)) {
        fileset(dir:srcDirOffset) {
            wellformedFiles.each {
                if (it && it.trim().length() > 0) {
                    include(name:it.trim())
                }
            }
        }

        if (replaceText) {
            replaceText.split('\n').each {
                if (it && it.trim().length() > 0) {
                    def index = it.indexOf('->')
                    if ( index > 0) {
                        println "Replace ${it[0..index - 1].trim()} with ${it[index + 2..-1]}"
                        replace(path:it[0..index - 1].trim(), withText:it[index + 2..-1], expandProperties:'false')
                    }
                    else {
                        println "Invalid replace text rule found: $it"
                        System.exit 1
                    }
                }
            }
        }

        if (insertList) {
            insertList.split('\n').each {
                if (it && it.trim().length() > 0) {
                    def index = it.indexOf('->')
                    if ( index > 0) {
                        println "Inserting\n${it[index + 2..-1]}\nunder ${it[0..index - 1].trim()}"
                        insert(
                                path:it[0..index - 1].trim(),
                                position:'under',
                                expandProperties:'false',
                                xml:it[index + 2..-1]) {
                        }
                    }
                    else {
                        println "Invalid insert rule found: $it"
                        System.exit 1
                    }
                }
            }
        }

        if (setAttr) {
            setAttr.split('\n').each {
                if (it && it.trim().length() > 0) {
                    def separatorIndex = it.indexOf('->')
                    def attributeIndex = it.indexOf('/@')
                    if ( separatorIndex > 0 && attributeIndex > 0) {
                        def element = it.substring(0, attributeIndex)
                        def attribute = it.substring(attributeIndex + 2, separatorIndex)
                        println "Set attribute $attribute on element $element to ${it[separatorIndex + 2..-1]}"
                        attr(path:element.trim(), attr: attribute.trim(), value:it[separatorIndex + 2..-1])
                    }
                    else {
                        println "Invalid replace text rule found: $it"
                        System.exit 1
                    }
                }
            }
        }

        if (removeList) {
            removeList.split('\n').each {
                if (it && it.trim().length() > 0) {
                    println "Removing ${it.trim()}"
                    remove(path:"${it.trim()}")
                }
            }
        }
    }
}
catch (Exception e) {
    e.printStackTrace()
    System.exit 1
}
finally {
    if (ignoreDTD) {
        println("[Action] Removing temporary doctype comments.")

        for (File file in matchedFiles) {
            def fileText = file.text
            file.withWriter{ w -> w << fileText.replaceAll("<!--(<!DOCTYPE.*>)-->", "\n\$1\n")}
        }
    }
}

//------------------------------------------------------------------------------
// Check post conditions
//------------------------------------------------------------------------------

if (failMode == FailMode.FAIL_ON_ANY_FAILURE && malformedFiles) {
    throw new IllegalStateException("Fail On Any Failure: Got ${malformedFiles.size()} failures!")
}

System.exit(0)

