/*
* Copyright 2004-2005 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*      http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import org.tmatesoft.svn.core.io.SVNRepositoryFactory
import org.tmatesoft.svn.core.internal.io.dav.DAVRepositoryFactory
import org.tmatesoft.svn.core.internal.io.fs.FSRepositoryFactory
import org.tmatesoft.svn.core.io.*
import org.tmatesoft.svn.core.*
import org.tmatesoft.svn.core.auth.*
import org.tmatesoft.svn.core.wc.*

/**
 * Gant script that handles releasing plugins to a plugin repository.
 *
 * @author Graeme Rocher
 */

includeTargets << grailsScript("_GrailsPluginDev")
includeTargets << grailsScript("_GrailsDocs")

authManager = null
commitMessage = null
trunk = null
latestRelease = null
versionedRelease = null

target ('default': "A target for plug-in developers that uploads and commits the current plug-in as the latest revision. The command will prompt for your SVN login details.") {
    releasePlugin()
}

target(processAuth:"Prompts user for login details to create authentication manager") {
    if(!authManager) {
        ant.input(message:"Please enter your SVN username:", addproperty:"user.svn.username")
        ant.input(message:"Please enter your SVN password:", addproperty:"user.svn.password")

        def username = ant.antProject.properties."user.svn.username"
        def password = ant.antProject.properties."user.svn.password"

        authManager = SVNWCUtil.createDefaultAuthenticationManager( username , password )
    }
}
target(releasePlugin: "The implementation target") {
    depends(parseArguments, packagePlugin, processAuth, docs)

    if(argsMap.repository) {
      configureRepositoryForName(argsMap.repository, "distribution")
    }
    remoteLocation = "${pluginSVN}/grails-${pluginName}"
    trunk = SVNURL.parseURIDecoded("${remoteLocation}/trunk")
    latestRelease = "${remoteLocation}/tags/LATEST_RELEASE"
    versionedRelease = "${remoteLocation}/tags/RELEASE_${plugin.version.toString().replaceAll('\\.','_')}"


    FSRepositoryFactory.setup()
    DAVRepositoryFactory.setup()

    try {
        if(argsMap.pluginlist) {
            commitNewGlobalPluginList()
        }
        else {
            def statusClient = new SVNStatusClient((ISVNAuthenticationManager)authManager,null)

            try {
                // get status of base directory, if this fails exception will be thrown
                statusClient.doStatus(baseFile, true)
                updateAndCommitLatest()
            }
            catch(SVNException ex) {
                // error with status, not in repo, attempt import.
                if (ex.message.contains("is not a working copy")) {
                    // Now check whether the plugin is in the repository.
                    // If not, we ask the user whether they want to import
                    // it.
                    SVNRepository repos = SVNRepositoryFactory.create(SVNURL.parseURIDecoded(pluginSVN))
                    boolean notInRepository = true
                    try {
                        notInRepository = !repos.info("grails-$pluginName", -1)
                    }
                    catch (e) {
                        // ignore
                    }
                    if (notInRepository) {
                        importToSVN()
                    }
                    else {
                       def result = confirmInput("""
The current directory is not a working copy and your latest changes won't be committed. You need to checkout
a working copy and make your changes there. Alternatively, do you want to proceed and create a release from what is in SVN now?
                    """)
                        if(!result) exit(0)
                    }
                }
                else {
                    event('StatusFinal', ["Failed to stat working directory: ${ex.message}"])
                    exit(1)
                }
            }
            tagPluginRelease()
            commitNewGlobalPluginList()
            event('StatusFinal', ["Plug-in release successfully published"])
        }
    }
    catch(Exception e) {
        logErrorAndExit("Error occurred with release-plugin", e)
    }
}

target(commitNewGlobalPluginList:"updates the plugins.xml descriptor stored in the repo") {

   if(!commitMessage) askForMessage()
   ant.delete(file:pluginsListFile)
   println "Building plugin list for commit..."
   updatePluginsListManually()

    def pluginMetaDir = new File("${grailsSettings.grailsWorkDir}/${repositoryName}/.plugin-meta")
    def updateClient = new SVNUpdateClient((ISVNAuthenticationManager)authManager, null)
    def importClient = new SVNCommitClient((ISVNAuthenticationManager)authManager, null)
    def addClient = new SVNWCClient((ISVNAuthenticationManager)authManager, null)

    String remotePluginMetadata = "${pluginSVN}/.plugin-meta"
   if(!pluginMetaDir.exists()) {
       println "Checking out locally to '${pluginMetaDir}'."
       checkoutOrImportPluginMetadata(pluginMetaDir, remotePluginMetadata, updateClient, importClient)
   }
   else {
       try {
           updateClient.doUpdate(pluginMetaDir, SVNRevision.HEAD, true)
           commitNewestPluginList(pluginMetaDir, importClient)
       } catch (SVNException e) {
           println "Plugin meta directory corrupt, checking out again"
           checkoutOrImportPluginMetadata(pluginMetaDir, remotePluginMetadata, updateClient, importClient)
       }
   }
}

private checkoutOrImportPluginMetadata (File pluginMetaDir, String remotePluginMetadata, SVNUpdateClient updateClient, SVNCommitClient importClient) {
    def svnURL = SVNURL.parseURIDecoded (remotePluginMetadata)
    try {
        updateClient.doCheckout(svnURL, pluginMetaDir, SVNRevision.HEAD, SVNRevision.HEAD, true)

        try {
            addClient.doAdd(new File("$pluginMetaDir/plugins-list.xml"), false, false, false, false)
        }
        catch (e) {
            // ignore
        }
        commitNewestPluginList(pluginMetaDir, importClient)
        
    } catch (SVNException e) {
        println "Importing plugin meta data to ${remotePluginMetadata}. Please wait..."

        ant.mkdir(dir: pluginMetaDir)
        ant.copy(file: pluginsListFile, tofile: "$pluginMetaDir/plugins-list.xml")

        def commit = importClient.doImport(pluginMetaDir, svnURL, commitMessage, true)
        println "Committed revision ${commit.newRevision} of plugins-list.xml."
        ant.delete(dir: pluginMetaDir)
        updateClient.doCheckout(svnURL, pluginMetaDir, SVNRevision.HEAD, SVNRevision.HEAD, true)

    }
}

private def commitNewestPluginList(File pluginMetaDir, SVNCommitClient importClient) {
    ant.copy(file: pluginsListFile, tofile: "$pluginMetaDir/plugins-list.xml", overwrite: true)

    def commit = importClient.doCommit([pluginMetaDir] as File[], false, commitMessage, true, true)

    println "Committed revision ${commit.newRevision} of plugins-list.xml."
}


target(checkInPluginZip:"Checks in the plug-in zip if it has not been checked in already") {
    def statusClient = new SVNStatusClient((ISVNAuthenticationManager)authManager,null)
    def wcClient = new SVNWCClient((ISVNAuthenticationManager)authManager,null)
    def pluginFile = new File(pluginZip)
    def addPluginFile = false
    try {
        def status = statusClient.doStatus(pluginFile, true)
        if(status.kind == SVNNodeKind.NONE || status.kind == SVNNodeKind.UNKNOWN) addPluginFile = true
    }
    catch(SVNException) {
        // not checked in add and commit
        addPluginFile = true
    }
    if(addPluginFile) wcClient.doAdd(pluginFile,true,false,false,false)
    def pluginXml = new File("${basedir}/plugin.xml")
    addPluginFile = false
    try {
        def status = statusClient.doStatus(pluginXml, true)
        if(status.kind == SVNNodeKind.NONE || status.kind == SVNNodeKind.UNKNOWN) addPluginFile = true
    }
    catch(SVNException e) {
        addPluginFile = true
    }
    if(addPluginFile) wcClient.doAdd(pluginXml, true, false,false,false)
}
target(updateAndCommitLatest:"Commits the latest revision of the Plug-in") {
   def result = confirmInput("""
This command will perform the following steps to release your plug-in to Grails' SVN repository:
* Update your sources to the HEAD revision
* Commit any changes you've made to SVN
* Tag the release

NOTE: This command will not add new resources for you, if you have additional sources to add please run 'svn add' before running this command.
NOTE: Make sure you have updated the version number in your *GrailsPlugin.groovy descriptor.

Are you sure you wish to proceed?
""") 
    if(!result) exit(0)

        checkInPluginZip()


    updateClient = new SVNUpdateClient((ISVNAuthenticationManager)authManager, null)

    println "Updating from SVN '${remoteLocation}'"
    long r = updateClient.doUpdate(baseFile, SVNRevision.HEAD, true)
    println "Updated to revision ${r}. Committing local, please wait..."

    commitClient = new SVNCommitClient((ISVNAuthenticationManager)authManager, null)

    if(!commitMessage) askForMessage()

    println "Committing code. Please wait..."

    def commit = commitClient.doCommit([baseFile] as File[],false,commitMessage,true,true)

    println "Committed revision ${commit.newRevision}."
}  

target(importToSVN:"Imports a plug-in project to Grails' remote SVN repository") {
    checkOutDir = new File("${baseFile.parentFile.absolutePath}/checkout/${baseFile.name}")

    def result = confirmInput("""
This plug-in project is not currently in the repository, this command will now:
* Perform an SVN import into the repository
* Checkout the imported version of the project from SVN to '${checkOutDir}'
* Tag the plug-in project as the LATEST_RELEASE
Are you sure you wish to proceed?
    """)
    if(!result) exit(0)

    ant.unzip(src:pluginZip, dest:"${basedir}/unzipped")
    ant.copy(file:pluginZip, todir:"${basedir}/unzipped")
    ant.mkdir(dir:"${basedir}/unzipped/grails-app")


    importClient = new SVNCommitClient((ISVNAuthenticationManager)authManager, null)
    askForMessage()

    println "Importing project to ${remoteLocation}. Please wait..."               

    def svnURL = SVNURL.parseURIDecoded("${remoteLocation}/trunk")
    importClient.doImport(new File("${basedir}/unzipped"),svnURL,commitMessage,true)
    println "Plug-in project imported to SVN at location '${remoteLocation}/trunk'"

    ant.delete(dir:"${basedir}/unzipped")

    checkOutDir.parentFile.mkdirs()

    updateClient = new SVNUpdateClient((ISVNAuthenticationManager)authManager, null)
    println "Checking out locally to '${checkOutDir}'."
    updateClient.doCheckout(svnURL, checkOutDir, SVNRevision.HEAD,SVNRevision.HEAD, true)

    event('StatusFinal', ["""
Completed SVN project import. If you are in terminal navigate to imported project with:
cd ${checkOutDir}

Future changes should be made to the SVN controlled sources!"""])
}

target(tagPluginRelease:"Tags a plugin-in with the LATEST_RELEASE tag and version tag within the /tags area of SVN") {
    println "Preparing to publish the release..."

    def copyClient = new SVNCopyClient((ISVNAuthenticationManager) authManager, null)
    def commitClient = new SVNCommitClient((ISVNAuthenticationManager) authManager, null)

    if (!commitMessage) askForMessage()

    tags = SVNURL.parseURIDecoded("${remoteLocation}/tags")
    latest = SVNURL.parseURIDecoded(latestRelease)
    release = SVNURL.parseURIDecoded(versionedRelease)

    try { commitClient.doMkDir([tags] as SVNURL[], commitMessage) }
    catch (SVNException e) {
        // ok - already exists
    }
    try { commitClient.doDelete([latest] as SVNURL[], commitMessage) }
    catch (SVNException e) {
        // ok - the tag doesn't exist yet
    }
    try { commitClient.doDelete([release] as SVNURL[], commitMessage) }
    catch (SVNException e) {
        // ok - the tag doesn't exist yet
    }

    // Get remote URL for this working copy.
    def wcClient = new SVNWCClient((ISVNAuthenticationManager) authManager, null)
    def copyFromUrl = trunk
    if (new File(grailsSettings.baseDir, ".svn").exists()) {
        copyFromUrl = wcClient.doInfo(new File("."), SVNRevision.WORKING).URL
    }

    // First tag this release with the version number.
    try {
        println "Tagging version release, please wait..."
        def copySource = new SVNCopySource(SVNRevision.HEAD, SVNRevision.HEAD, copyFromUrl)
        def commit = copyClient.doCopy([copySource] as SVNCopySource[], release, false, false, true, commitMessage, new SVNProperties())
        println "Copied trunk to ${versionedRelease} with revision ${commit.newRevision} on ${commit.date}"

        // And now make it the latest release.
        println "Tagging latest release, please wait..."
        copySource = new SVNCopySource(SVNRevision.HEAD, SVNRevision.HEAD, release)
        commit = copyClient.doCopy([copySource] as SVNCopySource[], latest, false, false, true, commitMessage, new SVNProperties())
        println "Copied trunk to ${latestRelease} with revision ${commit.newRevision} on ${commit.date}"

    }
    catch (SVNException e) {
        logErrorAndExit("Error tagging release", e)
    }


}

target(askForMessage:"Asks for the users commit message") {
    ant.input(message:"Enter a SVN commit message:", addproperty:"commit.message")
    commitMessage = ant.antProject.properties."commit.message"
}
