import org.tmatesoft.svn.core.wc.*

//includeTargets << grailsScript("Init")

/**
* Check and update the app.vcsRevision property in application.properties file and metadata.
* May be run directly by `grails update-rev` and normally run by _Events.groovy and eventCompileStart.
* The revision in the properties file is checked against the version control system (VCS) revision
* and updated if required.
* The VCS revision is written to the properties file so that it is available in the compiled war.
* The compile is intentionally allowed to go ahead if a handled exception occurs.
* VCS currently supported: subversion.
*/
target(updateVcsRevision: "Check and update the app.vcsRevision property in application.properties file and metadata.") {

    def result = [:]

    // Properties file.
    def url = basedir + "/application.properties"
    def propertiesFile = new File(url)

    def fail = { Map m ->
        updateInMemoryMetadata('Unknown')
        def writeResult = writePropertiesFileRevision(propertiesFile, 'Unknown')
        if(writeResult.error) {
            m.code= writeResult.error.code
            m.args= writeResult.error.args
        }

        result.error = [ code: m.code, args: m.args ]
        println "Error: UpdateRev script: " + result.error
        return result
    }

    // Get propertiesFile revision.
    def properitesFileResult = getPropertiesFileRevision(propertiesFile)
    if(properitesFileResult.error)
        return fail(code: properitesFileResult.error.code, args: properitesFileResult.error.args)

    // Get VCS revision.
    def vcsResult = getVcsRevision()
    if(vcsResult.error)
        return fail(code: vcsResult.error.code, args: vcsResult.error.args)

    // Compare and update.
    if(properitesFileResult.revision != vcsResult.revision) {

        println "app.vcsRevision = "+properitesFileResult.revision +', VCS Revision = '+vcsResult.revision

        updateInMemoryMetadata(vcsResult.revision)

        // Update application.properties file.
        def writeResult = writePropertiesFileRevision(propertiesFile, vcsResult.revision)
        if(writeResult.error)
            return fail(code: writeResult.error.code, args: writeResult.error.args)

    } // if(rev != rev)
    else {
        println "VCS Revisions match: app.vcsRevision = ${properitesFileResult.revision}, VCS Revision = ${vcsResult.revision}."
    }

    // Success.
    return result

} // updateVcsRevision()

/**
* Get the app.vcsRevision property from properties file.
* @retuns A map containing revision and lineNumber otherwise an error map.
*/
def getPropertiesFileRevision(propertiesFile) {
    def result = [:]

    def fail = { Map m ->
        result.error = [ code: m.code, args: m.args ]
        return result
    }

    if(!propertiesFile.isFile())
        return fail(code:"application.properties.file.not.found", args:[propertiesFile.getAbsoluteFile()])

    propertiesFile.eachLine { line, lineNumber ->
        // app.vcsRevision=$Rev: NUM $
        if ( line =~ '^app.vcsRevision.' ) {
            if(line.size() > 23) {
                result.revision = line[22..-3]
                result.lineNumber = lineNumber
            }
        }
    }

    if(!result.revision || !result.lineNumber)
        return fail(code:"app.vcsRevision.not.found")

    // Success.
    return result

} // getAppRevision()

/**
* Get the working copy's base revision from SVN.
* @retuns A map containing revision otherwise an error map.
*/
def getVcsRevision() {
    def result = [:]

    def fail = { Map m ->
        result.error = [ code: m.code, args: m.args ]
        return result
    }

    def wc = new File(basedir)
        if(!wc.isDirectory())
            return fail(code:"vcs.working.copy.not.found", args:[basedir])

    // Use svnkit to get the base revision.
    def clientManager = SVNClientManager.newInstance()
    def wcClient = clientManager.getWCClient()
    try {
        result.revision = wcClient.doInfo(wc, SVNRevision.BASE).getRevision().toString()
    }
    catch(org.tmatesoft.svn.core.SVNException e) {
        fail(code:"vcs.exception", args:[e])
    }

    // Success.
    return result
} // getVcsRevision()

/** 
* Update the in memory metadata if already loaded.
* Available vars: binding.variables.each { println it.key }
*/
def updateInMemoryMetadata(revision) {
    if(binding.variables.containsKey('metadata')) {
        def metadata = binding.variables['metadata']
        metadata['app.vcsRevision'] = '$Rev: '+revision+' $'
    }
} // updateInMemoryMetadata()

/**
* Write revision to properties file.
* @retuns An error map if any errors.
*/
def writePropertiesFileRevision(propertiesFile, revision) {
    def result = [:]

    def fail = { Map m ->
        result.error = [ code: m.code, args: m.args ]
        return result
    }

    if(!propertiesFile.isFile())
        return fail(code:"application.properties.file.not.found", args:[propertiesFile.getAbsoluteFile()])

    def revisionString = 'app.vcsRevision=\\$Rev: '+revision+' \\$'
    println "Updating application.properties file with: ${revisionString}"

    def processFileInplace = { file, Closure processText ->
        def text = file.text
        file.write(processText(text))
    }

    processFileInplace(propertiesFile) { text ->
        text.replaceAll('app.vcsRevision.*', revisionString)
    }

    // Success.
    return result

} // writePropertiesFileRevision()
