/*
* 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 org.apache.http.client.HttpClient
import org.apache.http.client.methods.HttpPost
import org.apache.http.entity.StringEntity
import org.apache.http.impl.client.DefaultHttpClient

import com.urbancode.air.*
import com.urbancode.air.plugin.scm.changelog.*
import com.urbancode.commons.httpcomponentsutil.HttpClientBuilder
import com.urbancode.commons.util.IO


public class SCMChangelog extends SCMStep {

    //**************************************************************************
    // CLASS
    //**************************************************************************

    //**************************************************************************
    // INSTANCE
    //**************************************************************************

    // changelog boundaries
    Date startDate = null
    Date endDate = null
    String startRevision = null
    String endRevision = null
    String changesUrl = null
    String depotName = null
    boolean traverseParentStreams = false

    // filters for changeset
    final ChangeSetFilter changeSetFilter = new ChangeSetFilter()
    List<ChangeSet> changeSets

    /**
     *
     * @return xml document of changes
     */
    public def execute() {
        boolean error = false

        this.login()

        try {
            // Get Change Log
            def logOutput = runLogCommand(streamName)

            // Construct xml message and send to server
            if (changeSets) {
                ChangeSetXMLHelper xmlHelper = new ChangeSetXMLHelper()
                xmlHelper.repoType = REPO_TYPE
                String xml = xmlHelper.toXML(changeSets)
                sendPostRequest(xml)
            }
            else {
                println "No changes detected"
            }
        }
        catch (ExitCodeException e) {
            println e.getMessage()
            error = true
        }
        finally {
            this.logout()

            if (error) {
                System.exit(1)
            }
        }
    }

    /**
     * Parse an input of accurev xml hist command into a list of AHP ChangeSets
     * @param logOutput output from a `accurev hist -fx` command
     */
    public void parseLogIntoChangeSets(String logOutput) {

        if (!changeSets) {
            changeSets = []
        }

        groovy.util.slurpersupport.GPathResult log = new XmlSlurper().parseText(logOutput);

        log.transaction.each{ trans ->
            ChangeSet changeSet = new ChangeSet();
            String changeId = trans.@id.text()
            String depotChangeId = depotName ? "${changeId}-${depotName}" : changeId
            changeSet.id = depotChangeId
            changeSet.user = trans.@user.text();
            changeSet.date = new Date((trans.@time.text() as Long) * 1000);
            changeSet.message = trans.comment.text();

            def changeSetType = trans.@type.text()
            def workItems = new LinkedHashSet();

            trans.version.each{ ver ->
                def path = ver.@path.text() ;

                // Accurev seems to prefix all of their file paths with
                // /./ so I am removing it here for consistency with the rest
                // of our file paths.
                path = path.replace('^/\\./', '');

                ChangeSetFileEntry entry = new ChangeSetFileEntry();
                entry.revision = changeId;
                entry.type = changeSetType;
                entry.path = path;
                changeSet.fileSet << entry;

                // find workItem meta-data elements
                ver.issueNum.each{ workItems << it.text() }
            }

            if (workItems) {
                changeSet.properties['workItems'] = workItems.join(',');
            }
            if (depotName) {
                changeSet.properties['depotName'] = depotName
            }

            boolean allowAuthor = changeSet.hasAllowedAuthor(changeSetFilter);
            boolean allowAnyPaths = changeSet.hasAllowedPath(changeSetFilter);
            if (allowAuthor && allowAnyPaths) {
                changeSets << changeSet
            }
            else {
                def message = new StringBuilder("Changeset ${changeSet.id} skipped because ")
                if (!allowAuthor) {
                    message << "it has excluded author ${changeSet.user}"
                }
                if (!allowAuthor && !allowAnyPaths) {
                    message << " and "
                }
                if (!allowAnyPaths) {
                    message << "it contains only excluded file paths (${changeSet.fileSet.collect{it.path}})"
                }
                println message
            }
        }
    }

    protected String calculateRange() {

        // Should range use to startBuildlifeId["workspace.revision.$srcName"] ||
        //       blProps["workspace.revision.$srcName"] if present ?

        final def range;
        if (startRevision && endRevision) {
            range = "$endRevision-$startRevision"
        }
        else {
            range = scmHelper.formatDateRange(startDate, endDate)
        }
        return range;
    }

    private void sendPostRequest(String xml) {
        // construct the URL with property replacements
        String url = changesUrl
        def authToken = System.getenv("AUTH_TOKEN")

        println "Sending request to $url"

        // Debug/testing stub
        if (url.startsWith("file://")) {
            File xmlOut = new File(directory, "xmlOut.xml")
            xmlOut << xml
        }
        else {
            HttpClientBuilder clientBuilder = new HttpClientBuilder();
            clientBuilder.setTrustAllCerts(true);
            DefaultHttpClient client = clientBuilder.buildClient();

            HttpPost postMethod = new HttpPost(url);
            if (authToken) {
                postMethod.setHeader("Authorization-Token", authToken)
                postMethod.setHeader("Content-Type", "application/xml")
            }

            println "Sending ${changeSets.size()} changes"
            postMethod.setEntity(new StringEntity(xml));

            def httpResponse = client.execute(postMethod)
            def responseCode = httpResponse.getStatusLine().getStatusCode()
            if (isGoodResponseCode(responseCode)) {
                IO.copy(postMethod.getEntity().getContent(), System.out)
                println ""
            }
            else {
                IO.copy(postMethod.getEntity().getContent(), System.err)
                throw new RuntimeException("Failed to upload source changes. StatusCode: ${responseCode}")
            }
        }
    }

    private boolean isGoodResponseCode(int responseCode) {
        return responseCode >= 200 && responseCode < 300;
    }

    private def findParentStream(def stream) {
        def output
        def showCommand = [scmCommand, "show", "-fx", "-s", stream, "streams"]

        cmdHelper.runCommand("Getting stream information for stream " + stream, showCommand) {
            Process proc ->
            proc.out.close()
            proc.consumeProcessErrorStream(System.out)
            output = proc.text
        }

        def parentStreamName = output.find(~/basis=\"(.*)\"/){match, parent -> return parent.trim()}

        if (parentStreamName) {
            println "Stream $stream has backing stream $parentStreamName\n"
        }
        else {
            println "Stream $stream did not have a backing stream\n"
        }

        return parentStreamName
    }

    protected String runLogCommand(def stream) {
        def logOutput
        while (stream) {
            def logCommand = [this.scmCommand, "hist"]
            if (host) {
                logCommand << '-H' << host
            }

            logCommand << '-a' << '-s' << stream << '-fx' << '-t'
            if (revision) {
                logCommand << revision
            }
            else {
                def range = calculateRange()
                logCommand << range
            }

            cmdHelper.runCommand("Getting Changelog for stream " + stream, logCommand) { Process proc ->
                proc.out.close()
                proc.consumeProcessErrorStream(System.out)
                logOutput = proc.text
                println logOutput
                if (!(logOutput?.trim())) {
                    println("No Changes Found");
                }
            }

            // the result change set data
            if (logOutput) {
                parseLogIntoChangeSets(logOutput);
            }

            stream = traverseParentStreams ? findParentStream(stream) : null
        }
        return logOutput
    }
}
