/*
* 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.
*/
import java.text.*

import org.apache.http.client.methods.HttpGet
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.commons.httpcomponentsutil.HttpClientBuilder
import com.urbancode.commons.util.IO


final def dateParser = new DateParser();
final def apTool = new AirPluginTool(this.args[0], this.args[1])

//------------------------------------------------------------------------------
// PARSE PROPERTIES
//------------------------------------------------------------------------------

final String PERL_FILE_PREFIX   = "clearquest-defect-details";
final String PERL_FILE_SUFFIX   = ".pl";
final String REPORT_FILE_PREFIX = "cq-output";
final String REPORT_FILE_SUFFIX = ".xml";

def webUrl = System.getenv('WEB_URL')
final def authToken = System.getenv('AUTH_TOKEN')
final def buildLifeId = System.getenv('BUILD_LIFE_ID')

final def stepProps = apTool.getStepProperties()
def workDir = new File('.').canonicalFile

// Automation
final String serverName = stepProps['automation']
final String username = stepProps['automation/username'] ?: ''
final String password = stepProps['automation/password'] ?: stepProps['automation/passwordScript'] ?: ''
final String dbName = stepProps['automation/dbName']
final String schema = stepProps['automation/schema']
final String issueUrl = stepProps['automation/issueUrl']
final String commandPath = stepProps['automation/commandPath'] ?: 'CQperl.exe'

def typeDefToTypeDefConfig = ['defect':['name':'headline', 'status':'state', 'description':'description']];

// Step
final def pattern = stepProps['defectIdPattern']

if (pattern == null) {
    throw new IllegalStateException('The defect id regular expression must be given');
}

def defectIds = []
def defectId2changeId = [:]

def sourceChangesUrl = webUrl + "/rest/buildlife/${buildLifeId}/sourcechanges"
HttpClientBuilder clientBuilder = new HttpClientBuilder();
clientBuilder.setTrustAllCerts(true);
DefaultHttpClient client = clientBuilder.buildClient();

HttpGet getMethod = new HttpGet(sourceChangesUrl);
try {
    if (authToken) {
        getMethod.setHeader("Authorization-Token", authToken)
        getMethod.setHeader("Content-Type", "application/xml")
    }
    
    def httpResponse = client.execute(getMethod)
    def responseCode = httpResponse.getStatusLine().getStatusCode()
    if (responseCode < 200 || responseCode >= 300) {
        throw new Exception("Getting source changes failed with response code: " + responseCode)
    }
    def changesXml = IO.readText(httpResponse.getEntity().getContent())
    
    new XmlSlurper().parseText(changesXml)."change-set".each { changeSetElem ->
        String comment = changeSetElem."comment"
        if (comment != null && !comment.empty) {
            def match = comment =~ pattern
            // match[0] is the entire matched substring, and match[1] should be the Issue ID
            String issueId
            if (match != null && match.size() != 0) {
                if (match.size() > 1) {
                    issueId = match[1]
                    defectIds.add(issueId.toString());
                    defectId2changeId[issueId.toString()] = changeSetElem."change-id"
                }
                else if (match.size() == 1) {
                    issueId = match[0]
                    defectIds.add(issueId.toString());
                    defectId2changeId[issueId.toString()] = changeSetElem."change-id"
                }
            }
        }
    }
}
finally {
    getMethod.releaseConnection()
}

File defectDetailsFile = File.createTempFile(REPORT_FILE_PREFIX, REPORT_FILE_SUFFIX);
File cmdFile = File.createTempFile(PERL_FILE_PREFIX, PERL_FILE_SUFFIX);

FileOutputStream fos = new FileOutputStream(cmdFile, true);
Writer writer = new BufferedWriter(new OutputStreamWriter(fos, "US-ASCII"));

StringBuilder sb = new StringBuilder();
try {
    sb.append("use strict;\n");
    sb.append("use CQPerlExt;\n");
    sb.append("use XML::Writer;\n");
    sb.append("use IO::File;\n");
    sb.append("\n");
    sb.append("use constant ID_ELEMENT => \"id\";\n");
    sb.append("use constant NAME_ELEMENT => \"name\";\n");
    sb.append("use constant TYPE_ELEMENT => \"type\";\n");
    sb.append("use constant STATUS_ELEMENT => \"status\";\n");
    sb.append("use constant DESCRIPTION_ELEMENT => \"description\";\n");
    sb.append("use constant URL_ELEMENT => \"url\";\n");
    sb.append("use constant ID_ATTRIBUTE => \"id\";\n");
    sb.append("use constant ISSUE_TRACKER_ATTRIBUTE => \"issue-tracker\";\n");
    sb.append("\n");
    sb.append("sub open_file_for_write {\n");
        sb.append("\tmy (\$file_name) = @_;\n");
        sb.append("\tnew IO::File(\$file_name);\n");
    sb.append("}\n");
    sb.append("\n");
    sb.append("sub create_document_writer() {\n");
        sb.append("\tmy (\$output) = @_;\n");
        sb.append("\tnew XML::Writer(OUTPUT => \$output);\n");
    sb.append("}\n");
    sb.append("\n");
    sb.append("sub write_document_begin {\n");
        sb.append("\tmy (\$writer) = @_;\n");
        sb.append("\n");
        sb.append("\t\$writer->xmlDecl(\"UTF-8\");\n");
        sb.append("\t\$writer->startTag(\"issues\");\n");
    sb.append("}\n");
    sb.append("\n");
    sb.append("sub write_document_end {\n");
        sb.append("\tmy (\$writer) = @_;\n");
        sb.append("\n");
        sb.append("\t\$writer->endTag(\"issues\");\n");
        sb.append("\t\$writer->end();\n");
    sb.append("}\n");
    sb.append("sub write_defect_element {\n");
        sb.append("\tmy (\$writer, \$id, \$name, \$type, \$status, \$description, \$changeId, \$url) = @_;\n");
        sb.append("\t\$writer->startTag(\"issue\", ID_ATTRIBUTE => \$id, ISSUE_TRACKER_ATTRIBUTE => 'ClearQuest');\n");
            sb.append("\t\$writer->cdataElement(NAME_ELEMENT, \$name);\n");
            sb.append("\t\$writer->cdataElement(TYPE_ELEMENT, \$type);\n");
            sb.append("\t\$writer->cdataElement(STATUS_ELEMENT, \$status);\n");
            sb.append("\t\$writer->cdataElement(DESCRIPTION_ELEMENT, \$description);\n");
            sb.append("\t\$writer->cdataElement(URL_ELEMENT, \$url);\n");
        sb.append("\t\$writer->endTag(\"issue\");\n");
    sb.append("}\n");
    sb.append("\n");
    sb.append("my @ids = (");
    for (int i = 0; i < defectIds.size(); i++) {
            sb.append("\"" + defectIds[i] + "\"");
            if (i != defectIds.size() - 1) {
                    sb.append(", ");
            }
    }
    sb.append(");\n");
    sb.append("\n");
    sb.append("my %typeDefConfigs = (");
    Iterator typeDefToTypeDefConfigItr = typeDefToTypeDefConfig.entrySet().iterator();
    int i = 0;
    while (typeDefToTypeDefConfigItr.hasNext()) {
        def entry = typeDefToTypeDefConfigItr.next();
        sb.append("\"" + entry.getKey() + "\" => {");
        Iterator fieldNameMappingItr = entry.getValue().entrySet().iterator();
        int j = 0;
        while (fieldNameMappingItr.hasNext()) {
            def innerEntry = fieldNameMappingItr.next();
            sb.append("\"" + innerEntry.getValue() + "\" => \"" + innerEntry.getKey() + "\"");
            if (j != entry.getValue().size() - 1) {
                sb.append(", ");
            }
            j++;
        }
        sb.append("}");
        if (i != typeDefToTypeDefConfig.size() - 1) {
            sb.append(", ");
        }
        i++;
    }
    sb.append(");\n");
    sb.append("my \$file_name = \">" + defectDetailsFile.getCanonicalPath().replaceAll("\\\\", "\\\\\\\\") + "\";\n");
    sb.append("\n");
    sb.append("my \$CQSession = CQPerlExt::CQSession_Build();\n");
    sb.append("\n");
    sb.append("my \$pass = \$ARGV[0];\n");
    sb.append("if (\$pass == undef) {\n");
        sb.append("\$pass = '';\n");
    sb.append("}\n");
    sb.append("\$CQSession->UserLogon(\'" + username + "\', \$pass, \'" + dbName + "\', \'" + schema + "\');\n");
    sb.append("\n");
    sb.append("my \$entityDefNames = \$CQSession->GetEntityDefNames();\n");
    sb.append("\n");
    sb.append("my \$output = &open_file_for_write(\$file_name);\n");
    sb.append("my \$writer = &create_document_writer(\$output);\n");
    sb.append("\n");
    sb.append("&write_document_begin(\$writer);\n");
    sb.append("\n");
    sb.append("my \$id;\n");
    sb.append("my \$name;\n");
    sb.append("my \$status;\n");
    sb.append("my \$description;\n");
    sb.append("my \$type;\n");
    sb.append("my \$url;\n");
    sb.append("\n");
    sb.append("foreach \$id (@ids) {\n");
        sb.append("\t\$name = undef;\n");
        sb.append("\t\$status = undef;\n");
        sb.append("\t\$description = undef;\n");
        sb.append("\t\$type = undef;\n");
        sb.append("\t\$url = \'" + issueUrl + "\';\n");
        sb.append("\t\$url =~ s/\\\$\\{issueId\\}/\$id/g;\n");
        sb.append("\n");
        sb.append("\tprint(\"Looking for \" . \$id . \"\\n\");\n");
        sb.append("\tmy \$entityDefName;\n");
        sb.append("\tforeach \$entityDefName (@\$entityDefNames) {\n");
            sb.append("\t\teval {\n");
                sb.append("\t\t\tif (\$CQSession->EntityExists(\$entityDefName, \$id)) {\n");
                    sb.append("\t\t\t\t\$type = \$entityDefName;\n");
                    sb.append("\t\t\t\tprint(\"Found \" . \$id . \" it is a \" . \$entityDefName . \"\\n\");\n");
                    sb.append("\t\t\t\tmy \$entity = \$CQSession->GetEntity(\$entityDefName, \$id);\n");
                    sb.append("\t\t\t\tmy \$fieldNameList = \$entity->GetFieldNames();\n");
                    sb.append("\t\t\t\tmy \$fieldName;\n");
                    sb.append("\n");
                    sb.append("\t\t\t\tforeach \$fieldName (@\$fieldNameList) {\n");
                        sb.append("\t\t\t\t\tif (exists \$typeDefConfigs{lc(\$entityDefName)}->{lc(\$fieldName)}) {\n");
                            sb.append("\t\t\t\t\t\tmy \$fieldElementName = \$typeDefConfigs{lc(\$entityDefName)}->{lc(\$fieldName)};\n");
                            sb.append("\t\t\t\t\t\tmy \$fieldValue = \$entity->GetFieldValue(\$fieldName)->GetValue();\n");
                            sb.append("\t\t\t\t\t\tprint(\"\\t\" . \$fieldName . \":\" . \$fieldValue . \"\\n\");\n");
                            sb.append("\t\t\t\t\t\tif (\$fieldElementName eq NAME_ELEMENT) {\n");
                                sb.append("\t\t\t\t\t\t\t\$name = \$fieldValue;\n");
                            sb.append("\t\t\t\t\t\t}\n");
                            sb.append("\t\t\t\t\t\telsif (\$fieldElementName eq STATUS_ELEMENT) {\n");
                                sb.append("\t\t\t\t\t\t\t\$status = \$fieldValue;\n");
                            sb.append("\t\t\t\t\t\t}\n");
                            sb.append("\t\t\t\t\t\telsif (\$fieldElementName eq DESCRIPTION_ELEMENT) {\n");
                                sb.append("\t\t\t\t\t\t\t\$description = \$fieldValue;\n");
                            sb.append("\t\t\t\t\t\t}\n");
                        sb.append("\t\t\t\t\t}\n");
                    sb.append("\t\t\t\t}\n");
                sb.append("\t\t\t}\n");
            sb.append("\t\t};\n");
            sb.append("\n");
            sb.append("\t\tif (\$@) {\n");
                sb.append("\t\t\tprint(\"Encountered the following error:\" . \$@);\n");
            sb.append("\t\t}\n");
        sb.append("\t}\n");
        sb.append("\tif (\$type) {\n");
            sb.append("\t\t&write_defect_element(\$writer, \$id, \$name, \$type, \$status, \$description, \$url);\n");
        sb.append("\t}\n");
        sb.append("\telse {\n");
            sb.append("\t\tprint(\$id . \" was not found\\n\");\n");
        sb.append("\t}\n");
    sb.append("}\n");
    sb.append("\n");
    sb.append("&write_document_end(\$writer);\n");
    sb.append("\n");
    sb.append("\$output->close();\n");
    sb.append("CQSession::Unbuild(\$CQSession);\n");
    writer.write(sb.toString());
}
finally {
    writer.close();
}

//DEBUG
//println "Executing Script:"
//println sb.toString()


//------------------------------------------------------------------------------
// PREPARE COMMAND LINE
//------------------------------------------------------------------------------

String[] commandLine = new String[3];
commandLine[0] = commandPath
commandLine[1] = cmdFile.getCanonicalPath()
commandLine[2] = password

//------------------------------------------------------------------------------
// EXECUTE CQ UPDATE
//------------------------------------------------------------------------------

if (!workDir.exists()) {
  workDir.mkdirs();
}

CommandHelper cmdHelper = new CommandHelper(workDir);

println "ClearQuest command line: " + commandPath + " " + cmdFile.getCanonicalPath()
println "ClearQuest command working Directory: " + workDir.getCanonicalPath()
println "ClearQuest command environment: "

cmdHelper.runCommand('', commandLine);

//------------------------------------------------------------------------------
// UPLOAD ISSUES
//------------------------------------------------------------------------------
// Put in changeId for each issue
issues = new XmlSlurper().parse(defectDetailsFile)
issues.issue.each { issue ->
  issue.@'change-id' = defectId2changeId[issue.@id]
}

// Upload xml
String issuesXml = issues.text

webUrl += webUrl.endsWith("/") ? "" : "/"

String postUrl = webUrl + "rest/buildlife/${buildLifeId}/issues"

println "Uploading Issues xml"
println issuesXml

HttpPost postMethod = new HttpPost(postUrl)
try {
    if (authToken) {
        postMethod.setHeader("Authorization-Token", authToken)
        postMethod.setHeader("Content-Type", "application/xml")
    }
    
    postMethod.setEntity(new StringEntity(issuesXml));
    
    def postResponse = client.execute(postMethod)
    def postResponseCode = postResponse.getStatusLine().getStatusCode()
    if (postResponseCode >= 200 && postResponseCode < 300) {
        IO.copy(postMethod.getEntity().getContent(), System.out)
        println ""
    }
    else {
        IO.copy(postMethod.getEntity().getContent(), System.err)
        throw new RuntimeException("ClearQuest results upload to server failed. StatusCode: ${postResponseCode}")
    }
}
finally {
    postMethod.releaseConnection()
}