/*
 * Licensed Materials - Property of IBM* and/or HCL**
 * UrbanCode Deploy
 * (c) Copyright IBM Corporation 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
 */

package com.urbancode.air.plugins.accurev

import com.urbancode.air.CommandHelper
import com.urbancode.air.plugins.accurev.objects.Change
import com.urbancode.air.plugins.accurev.objects.Issue
import com.urbancode.air.plugins.accurev.objects.Stream
import com.urbancode.commons.util.IO
import groovy.util.XmlSlurper

public class AccuRevHelper {

    private static def tempDirs = new HashMap<String, File>()

    private String serverUrl
    private String username
    private String password
    private String depot
    private Stream stream
    private String accurev
    private CommandHelper ch = new CommandHelper(new File("."))

    private List<Stream> streams = new ArrayList<Stream>()
    private List<Stream> streamsMulti = new ArrayList<Stream>()

    public AccuRevHelper(String serverUrl, String username, String password, String depot,
            String streamStr, String accurev) {
        this.serverUrl = serverUrl
        this.username  = username
        this.password  = password
        this.depot     = depot

        if (accurev) {
            File accurevFile = new File(accurev.trim())
            if (accurevFile.isFile()) {
                this.accurev = accurevFile.getCanonicalPath()
            } else {
                throw new FileNotFoundException("[Error] AccuRev Executable Path is not valid. Please confirm value.")
            }
        } else {
            this.accurev = "accurev"
        }

        login()

        // Confirm given stream exists and it's either a Stream, Workspace, or Snapshot type
        streams = getStreams()
        stream  = getStream(streamStr)
        if (stream) {
            if (stream.isStream()) {
                println "[Info] Found ${stream.getName()} Stream."
            } else if (stream.isWorkspace()) {
                println "[Info] Found ${stream.getName()} Workspace."
            } else if (stream.isSnapshot()) {
                println "[Info] Found ${stream.getName()} Snapshot."
            } else {
                throw new RuntimeException("[Error] Unsupported '${streamStr}' stream type of '${stream.getType()}'.")
            }
        } else {
            throw new RuntimeException("[Error] The specified '${streamStr}' Stream does not exist!")
        }
    }

    private void login() {
        def args = [accurev, "login", "-H", serverUrl, username, password]
        ch.runCommand("[Action] Logging in...", args)
    }

    public void logout() {
        def args = [accurev, "logout", "-H", serverUrl]
        ch.runCommand("[Action] Logging out...", args)
    }

    // Retrieve all Streams/Workspaces/Snapshots within Depot
    private List<Stream> getStreams() {
        List<Stream> result = streams
        if (result.size() == 0) {
            def args = [accurev, "show", "-H", serverUrl, "streams", "-p", depot, "-fx"]

            String xmlOutput

            def getOutput = {
                it.out.close() // close stdin
                def out = new PrintStream(System.out, true)
                StringBuilder outputBuilder = new StringBuilder()
                try {
                    it.waitForProcessOutput(outputBuilder, out)
                }
                finally {
                    out.flush()
                }
                xmlOutput = outputBuilder.toString()
            }

            ch.runCommand("[Action] Gathering all Streams...", args, getOutput)

            def acResponse = new XmlSlurper().parseText(xmlOutput)
            acResponse?.stream.each { it ->
                result.add(new Stream(it))
            }

            println "[Info] Found ${result.size()} Stream(s) in Depot ${depot}."
        }
        return result
    }

    // Retrieve Stream
    private Stream getStream(String streamStr) {
        Stream result = null
        for (Stream s : getStreams()) {
            if (s.getName() == streamStr) {
                result = s
                break
            }
        }
        return result
    }

    // Get List<Stream> of all Snapshot Streams
    public List<Stream> getSnapshots() {
        println ""
        println "[Action] Gathering all direct child Snapshots of Stream ${stream.getName()}..."

        List<Stream> result = new ArrayList<Stream>()
        for (Stream s : streams) {
            if (s.getType() == "snapshot" && s.getBasis() == stream.getName()) {
                result.add(s)
            }
        }
        println "[Info] Found ${result.size()} Snapshot(s)."
        return result
    }

    // Retrieves a list of all Promote Transcation Numbers
    public def getPromoteTransactionNumbers() {
        def args = [accurev, "hist",
                "-H", serverUrl,
                "-s", stream.getName(),
                "-k", "promote",
                "-a", "-fx"]

        String xmlOutput

        def getOutput = {
            it.out.close() // close stdin
            def out = new PrintStream(System.out, true)
            StringBuilder outputBuilder = new StringBuilder()
            try {
                it.waitForProcessOutput(outputBuilder, out)
            }
            finally {
                out.flush()
            }
            xmlOutput = outputBuilder.toString()
        }

        ch.runCommand("[Action] Identifying Transaction Numbers...", args, getOutput)

        def xml = new XmlSlurper().parseText(xmlOutput)
        def transNums = [:]
        xml.transaction.each { transNums[it.@id.toString()] = Long.valueOf(it.@time.toString()) }

        return transNums
    }

    // Downloads latest files
    public void populateFolder(String ver_spec, File downloadDir) {
        populateFolder(ver_spec, null, downloadDir)
    }

    // Downloads all files associated with Transaction Number
    public void populateFolder(String ver_spec, String transNum, File downloadDir) {
        populateFolder(ver_spec, transNum, downloadDir, ".")
    }

    // Downloads specified element associated with Transcation Number
    public void populateFolder(String ver_spec, String transNum, File downloadDir, String element) {
        while (element.startsWith("/")) {
            element = element.substring(1, element.length())
        }

        def args = [accurev, "pop", "-H", serverUrl, "-R", "-O"]
        if (transNum) {
            args << "-t"
            args << transNum
        }
        args << "-v"
        args << ver_spec
        args << "-L"
        args << downloadDir.getCanonicalPath()
        args << element

        ch.runCommand("[Action] Populating folder...", args)
    }

    // Downloads one file in an issue
    public void populateFileInIssue(String ver_spec, File downloadDir, String element) {
        while (element.startsWith("/")) {
            element = element.substring(1, element.length())
        }

        def args = [accurev, "pop", "-H", serverUrl, "-O"]
        args << "-v"
        args << ver_spec
        args << "-L"
        args << downloadDir.getCanonicalPath()
        args << element

        ch.runCommand("[Action] Populating folder...", args)
    }

    // Returns a List<Issue> container information on Depot's Issues
    public List<Issue> getDepotIssueList() {
        List<Issue> issues = new ArrayList<Issue>()

        String queryXML = """<?xml version="1.0" encoding="UTF-8"?>
<queryIssue issueDB="${depot}" expandUsers="true"></queryIssue>"""

        File queryFile = File.createTempFile("accurev-query", ".xml", new File(".").getCanonicalFile())
        queryFile.deleteOnExit()
        queryFile << queryXML

        def args = [accurev, "xml", "-H", serverUrl, "-l", queryFile]

        String xmlOutput
        def getOutput = {
            it.out.close() // close stdin
            def out = new PrintStream(System.out, true)
            StringBuilder outputBuilder = new StringBuilder()
            try {
                it.waitForProcessOutput(outputBuilder, out)
            }
            finally {
                out.flush()
            }
            xmlOutput = outputBuilder.toString()
        }

        ch.runCommand("[Action] Getting Depot Issue List...", args, getOutput)

        def acResponse = new XmlSlurper().parseText(xmlOutput)
        int i = 0
        acResponse?.issue?.each { it ->
            issues.add(new Issue(it))
        }
        println "[Info] Found ${issues.size()} issue(s) in ${depot}."

        return issues

    }

    // Returns a List<Issue> containing information on Stream's issues
    public List<Issue> getStreamIssueList() {
        List<Issue> issues = new ArrayList<Issue>()

        def args = [accurev, "issuelist",
                "-H", serverUrl,
                "-s", stream.getName(),
                "-fx"]

        String xmlOutput
        def getOutput = {
            it.out.close() // close stdin
            def out = new PrintStream(System.out, true)
            StringBuilder outputBuilder = new StringBuilder()
            try {
                it.waitForProcessOutput(outputBuilder, out)
            }
            finally {
                out.flush()
            }
            xmlOutput = outputBuilder.toString()
        }

        ch.runCommand("[Action] Getting Stream Issue List...", args, getOutput)

        def acResponse = new XmlSlurper().parseText(xmlOutput)
        acResponse?.issues?.issue.each { it ->
            // Confirm if we should only find issues with ancestry="direct"
            issues.add(new Issue(it))
        }

        println "[Info] Found ${issues.size()} issue(s) in ${stream.getName()}."
        return issues
    }

    // Retrieve every Change/File associated with Issue
    public List<Change> getChanges(Issue issue) {
        List<Change> changes = new ArrayList<Change>()
        def args = [accurev, "cpkdescribe",
                "-H", serverUrl,
                "-I", issue.getIssueNum(),
                "-p", depot,
                "-fx"]

        String xmlOutput
        def getOutput = {
            it.out.close() // close stdin
            def out = new PrintStream(System.out, true)
            StringBuilder outputBuilder = new StringBuilder()
            try {
                it.waitForProcessOutput(outputBuilder, out)
            }
            finally {
                out.flush()
            }
            xmlOutput = outputBuilder.toString()
        }

        ch.runCommand("[Action] Getting Changes for Issue #${issue.getIssueNum()}...", args, getOutput)

        def acResponse = new XmlSlurper().parseText(xmlOutput)
        acResponse?.issues?.issue?.elements?.element.each{ it ->
            Change change = new Change(it)
            changes.add(change)
        }

        return changes
    }

    // Generates or retrieves temporary directory associated with version name
    def getTempDir = { version ->
        File result = tempDirs.get(version)
        if (result == null) {
            result = new File(".", UUID.randomUUID().toString())
            IO.mkdirs(result)
            tempDirs.put(version, result)
        }
        return result
    }

}
