/*
* Licensed Materials - Property of IBM Corp.
* IBM UrbanCode Build
* (c) Copyright IBM Corporation 2012, 2014. All Rights Reserved.
*
* U.S. Government Users Restricted Rights - Use, duplication or disclosure restricted by
* GSA ADP Schedule Contract with IBM Corp.
*/
package com.urbancode.air.plugin.scm

import com.urbancode.air.*
import org.apache.commons.io.output.TeeOutputStream

public class SCMStep {

    static protected final String REPO_TYPE = 'RTC'
    static protected final int SLEEP_SECONDS = 5
    static protected final int DEFAULT_RETRIES = 5
    
    String command = "scm"
    String serverUrl
    String username
    String password
    String workspace
    String stream
    String baseline
    String snapshot
    String components
    String buildSnapshot
    String buildSnapshotDescription
    boolean isIncludeRoot = false
    boolean isForce = false
    def listSnapshotDateFormat
    File directory
    CommandHelper cmdHelper

    protected String error

    protected CommandHelper getCmdHelper() {
        if (!cmdHelper) {
            cmdHelper = new CommandHelper(directory)
        }

        return cmdHelper
    }

    public void cleanup(dir) {
        if (dir == null) {
            dir = directory.canonicalFile
        }
        println "Removing everything in ${dir}..."
        dir.listFiles().each {file ->
            boolean success = false;
            if (file.isFile()) {
                success = file.delete()
            }
            else if (file.isDirectory()) {
                success = file.deleteDir()
            }
            if (!success) {
                println("Failed to delete $file")
            }
        }
        println "Done deleting directory contents for ${dir}"
    }

    public void undo(dir) {
        if (dir == null) {
            dir = directory.canonicalFile
        }
        def statusText
        runCommand('Get workspace status', [command, '--non-interactive', 'status', '-u', username, '-P', password]) { statusText = it }
        println statusText
        def isChange = false
        def isWorkspaceMatch = false
        def undoCommand = []
        undoCommand << command << '--non-interactive' << 'undo' << '-u' << username << '-P' << password
        statusText?.eachLine {
            def line = it.trim()
            if (line.startsWith('Workspace')) {
                line.find('Workspace: \\((\\d+)\\) "(.*)" <->.*') {match, workspaceAlias, workspaceName ->
                    if (workspaceAlias.equalsIgnoreCase(workspace) || workspaceName.equalsIgnoreCase(workspace)) {
                        isWorkspaceMatch = true
                    }
                    else {
                        println "Local workspace ${workspaceAlias}:${workspaceName} does not match required remote workspace ${workspace}!"
                    }
                }
            }
            else if (line.startsWith('Unresolved')) {
                isChange = true
            }
            else if (isChange && (line.startsWith('a-') || line.startsWith('d-') || line.startsWith('m-') || line.startsWith('-c'))) {
                undoCommand << line.substring(4)
            }
            else if (isChange) {
                isChange = false
            }
        }

        //now run the undo command on all local changes unless the workspaces don't match
        if (!isWorkspaceMatch) {
            cleanup(dir)
        }
        else if (undoCommand.size() > 7) {
            runCommand('Undo local changes', undoCommand)
        }
        else {
            println 'No local changes found, nothing to cleanup!'
        }
    }

    public void repair() {
        runCommand('Attempt local workspace repair',
                [command, '--non-interactive', 'repair',
                '-r', serverUrl,
                '-u', username,
                '-P', password])
    }

    public Map getWorkspaceInfo() {
        def componentText
        def result = [:]
        def getComponentsCommand = [command, '-a', 'n', '--non-interactive', 'list', 'components', workspace,
                                    '-r', serverUrl, '-u', username, '-P', password]
        result.componentList = []

        CommandHelper cmdHelper = getCmdHelper()
        cmdHelper.ignoreExitValue = true
        result.exitCode = runCommand('Get workspace components', getComponentsCommand) {
            println it
            componentText = it
        }
        cmdHelper.ignoreExitValue = false
        if (result.exitCode == 0) {
            componentText?.eachLine {
                def line = it.trim()
                if (line.startsWith('Workspace')) {
                    line.find('.*<-> (.*)') { match, stream ->
                        result.workspaceStreamName = stripQuotes(stream)
                    }
                }
                else if (line.startsWith('Component') && line[-1] != ')') {
                    line.find('Component: (.*)') { match, component ->
                        result.componentList << stripQuotes(component)
                    }
                }
            }
        }
        return result
    }

    protected String stripQuotes(String value) {
        return value.replaceAll(/"/, '').trim()
    }

    protected int runCommand(def message, def command) {
        return runCommand(message, command, null)
    }

    protected int runCommand(def message, def command, Closure closure) {
        return runCommand(message, command, DEFAULT_RETRIES, closure)
    }

    protected int runCommand(def message, def command, def retries, Closure closure) {
        return runCommand(message, command, retries, processOutput, closure)
    }

    protected int runCommand(def message, def command, def retries, Closure preProcess, Closure postProcess) {
        int exitCode
        boolean done = false

        // Always reset the error output so we don't accidentally see errors from another command if the processOutput
        // closure isn't called as part of a runCommand
        error = new ByteArrayOutputStream()
        CommandHelper cmdHelper = getCmdHelper()
        boolean ignoreExitValue = cmdHelper.ignoreExitValue
        cmdHelper.ignoreExitValue = true
        try {
            def text
            for (int i = 0; i <= retries && !done; i++) {
                exitCode = cmdHelper.runCommand(message, command) { proc ->
                    text = preProcess(proc)

                    if (error) {
                        println error
                    }

                    if (error.find(~"Monicker for .* already exists") ||
                            error.find(~"Could not secure [.]jazz-scm")) {
                        println "Failed to run the RTC command."
                        if (i < retries) {
                            println "Retrying in ${SLEEP_SECONDS} seconds."
                            sleep SLEEP_SECONDS * 1000
                        }
                    }
                    else {
                        done = true
                    }
                }
            }

            // Guarantee that the output will be printed even if the command failed during each retry
            if (postProcess) {
                postProcess(text)
            }
            else {
                println text
            }

            // We always want an exit code of 0 from the last run of the command
            if (!ignoreExitValue && exitCode) {
                throw new ExitCodeException("Command failed with exit code: " + exitCode)
            }
        }
        finally {
            cmdHelper.ignoreExitValue = ignoreExitValue
        }
        return exitCode
    }

    protected def processOutput = { proc ->
        def out = new ByteArrayOutputStream()
        def err = new ByteArrayOutputStream()

        proc.outputStream.close()
        proc.waitForProcessOutput(out, err)
        error = err.toString()

        return out.toString()
    }
}
