/*
 * Licensed Materials - Property of IBM* and/or HCL**
 * IBM UrbanCode Build
 * IBM UrbanCode Deploy
 * IBM UrbanCode Release
 * IBM AnthillPro
 * (c) Copyright IBM Corporation 2002, 2015. 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
 */
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import static java.lang.Integer.valueOf;

import org.apache.http.conn.scheme.Scheme;
import org.apache.http.conn.scheme.SchemeSocketFactory;
import org.apache.http.conn.ssl.SSLSocketFactory;
import org.apache.http.conn.ssl.TrustStrategy

import groovyx.net.http.RESTClient
import groovyx.net.http.AuthConfig
import groovyx.net.http.ContentType
import groovyx.net.http.HttpResponseException

import com.urbancode.air.AirPluginTool
import com.ibm.urbancode.zos.common.ZOSJOBS


try{
	def apTool = new AirPluginTool(this.args[0], this.args[1])
	final def workDir = new File('.').canonicalFile
	def props = apTool.getStepProperties();
	
	final def workflowKey = props['workflowKey']?.trim();
	final def stepName = props['stepName']?.trim();
	final def resolveConflictByUsing = props['resolveConflictByUsing']?.trim();
	final def performSubsequent = Boolean.valueOf(props['performSubsequent']?.trim()); //checkbox
	final def notificationUrl = props['notificationUrl']?.trim();
	final def waitForWorkflow = Boolean.valueOf(props['waitForWorkflow']?.trim());
	final def timeout = props['timeout']?.trim();
	final def printRESTLOG = Boolean.valueOf(props['printRESTLOG']?.trim()); //checkbox
	final def printJOBLOG = Boolean.valueOf(props['printJOBLOG']?.trim()); //checkbox
	final def outputJOBLOG = Boolean.valueOf(props['outputJOBLOG']?.trim()); //checkbox
	final def jobPrefix = props['jobPrefix']?.trim();
	final def jobOwner = props['jobOwner']?.trim();
	final def ddnameFilter = props['ddnameFilter']?.trim();
	final def cutOff = Integer.valueOf(props['cutOff']?.trim());
	//final def jobID = props['jobID']?.trim();   // doesn't seem useful in this use case
	jobID = ""
			
	//hidden
	final def baseurl = props['baseurl']?.trim();
	final def untrustedSSL = Boolean.valueOf(props['untrustedSSL']?.trim()); //checkbox
	final def userid = props['userid']?.trim();
	final def password = props['password']?.trim();
    final def useCertifactes = Boolean.valueOf(props['useCertifactes']?.trim()); //checkbox
    final def clientCertificatePath = props['clientCertificate']?.trim();
    final def certPassword = props['certPassword']?.trim();
	int timeoutValue=0;
	
	//validate required input
	if(workflowKey == null || workflowKey.length()==0){
		throw new Exception("Workflow Key must be set.");
	}

	if(baseurl == null || baseurl.length()==0){
		throw new Exception("zOSMF URL must be set.");
	}

    if (!useCertifactes) {
        if(userid == null || userid.length()==0){
            throw new Exception("User Name must be set Or Client certificate needs to be set.");
        }

        if(password == null || password.trim().length()==0){
            throw new Exception("Password must be set  Or Client certificate needs to be set.");
        }
    }

    if (useCertifactes) {
        if(clientCertificatePath == null || clientCertificatePath.length()==0){
            throw new Exception("Client certificate path needs to be set.");
        }
    }
    
	try{
		timeoutValue = valueOf(timeout as String);
	}catch(NumberFormatException e){
		throw new IllegalArgumentException("Time Out isn't a number.");
	}

	def JOBS, jobsBefore, jobsAfter, jobFilterMap = [:], ddnameFilterArray;
	if(printJOBLOG || outputJOBLOG){
		//verify job related input
		if(jobPrefix) jobFilterMap << [prefix : jobPrefix]
		if(jobOwner)  jobFilterMap << [owner : jobOwner]
		if(jobID)     jobFilterMap << [jobid : jobID]
		ddnameFilterArray = ddnameFilter.split("\n");
		
		//get the job list before running workflow
		JOBS= new ZOSJOBS(baseurl, userid, password,useCertifactes,clientCertificatePath,certPassword);
		jobsBefore = JOBS.listJobs(jobFilterMap);
	}
		
	def start_workflow_path="/zosmf/workflow/rest/1.0/workflows/${workflowKey}/operations/start"
	def get_workflow_property_path="/zosmf/workflow/rest/1.0/workflows/${workflowKey}"
	
	def client = new RESTClient(baseurl)
	client.contentType = ContentType.JSON  //zosmf API uses JSON
	client.encoder.setCharset("UTF-8")  //do not use default encoding
    if (!useCertifactes) {
        client.auth.basic userid, password
    } else {
        println "Using client certificate to authenticate"
        System.setProperty("javax.net.ssl.keyStoreType", "pkcs12");
        System.setProperty("javax.net.ssl.keyStore", clientCertificatePath);
        System.setProperty("javax.net.ssl.keyStorePassword", certPassword);
        //System.setProperty("javax.net.debug", "ssl");
    }
    client.headers['X-CSRF-ZOSMF-HEADER']="UCD";
    
	if(untrustedSSL){
		//allow untrustedSSL
		TrustStrategy trustStrat = new TrustStrategy(){
					public boolean isTrusted(X509Certificate[] chain, String authtype)
					throws CertificateException {
						return true;
					}
				};
		SchemeSocketFactory schemeSocketFactory = new SSLSocketFactory(trustStrat,SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
		Scheme scheme = new Scheme("https",  443, schemeSocketFactory);
		client.client.connectionManager.schemeRegistry.register(scheme);
	}


	//prepare JSON request
	def body = 	[:]

	//add optional fields
	if(stepName) 				body << [stepName : stepName]
	if(resolveConflictByUsing) 	body << [resolveConflictByUsing : resolveConflictByUsing]
	if(!performSubsequent)		body << [performSubsequent : performSubsequent]
	if(notificationUrl) 		body << [notificationUrl : notificationUrl]
	if(body.isEmpty()) body = null  //do not pass empty JSON body, otherwise the API will fail

	if(printRESTLOG) {
		println "Start workflow request URL:"
		println start_workflow_path
		println ""
		println "Start workflow request body:"
		println body? body : ""
		println ""
	}

	try{
		def resp  = client.put(
				path : start_workflow_path,
				body : body,
				requestContentType: ContentType.JSON
				)
		if(printRESTLOG) {
			println "Start workflow response status line:"
			println resp.statusLine
			println ""
			println "Start workflow response body:"
			println resp.data?resp.data : ""
			println ""
		}

		//Based on zOSMF Workflow REST API
		//On successful completion, HTTP status code 202 (Accepted) is returned.
		if(resp.status == 202){ //Accepted
			println "Workflow started. "
			if(printRESTLOG) {
				println "Start workflow response status line:"
				println resp.statusLine
				println ""
				println "Start workflow response body:"
				println resp.data?resp.data : ""
				println ""
			}

			//wait for completion
			if(waitForWorkflow){
				println "Waiting for workflow to complete."
				def elapseSeconds = 0;
				def stepSeconds = 4;
				def percentComplete = null;
				def statusName = null;
				def automationStatus = null;
				Thread.sleep(2000)
				while (elapseSeconds < timeoutValue) {
					def statusresp = client.get (path : get_workflow_property_path,
							query:[returnData:'steps'])
					if(statusresp.status == 200){
						/* Percentage of the workflow that is completed. z/OSMF calculates this value based on 
						   the number of steps in the workflow and the relative weighting value of each step.
						 */
						percentComplete = statusresp.data.percentComplete
						/*  Indicates the current workflow status, as follows:
							in-progress
							One or more steps in the workflow are started.
							complete
							Workflow is complete; all steps are marked complete or skipped.
							automation-in-progress
							Workflow contains an automated step that is running.
							canceled
							Workflow is canceled and cannot be resumed. You can, however, view its properties or delete it.
						 */
						statusName = statusresp.data.statusName
						automationStatus = statusresp.data.automationStatus
						
						if(statusName == "complete"){
							println ""
							println "Workflow completed. ${percentComplete}% completed. "
							storeAutomationStatus(apTool, automationStatus)
							processJobLog(printJOBLOG, outputJOBLOG, apTool, JOBS, jobsBefore, jobFilterMap, ddnameFilterArray, cutOff)
							System.exit(0);
						}else if(statusName == "canceled"){
							println "Workflow canceled."
							println "Automation status : " + automationStatus
							storeAutomationStatus(apTool, automationStatus)
							processJobLog(printJOBLOG, outputJOBLOG, apTool, JOBS, jobsBefore, jobFilterMap, ddnameFilterArray, cutOff)
							System.exit(1);
						}else if(statusName == "automation-in-progress"){
							print "."
						}else if(statusName == "in-progress"){
							println ""
							println "Workflow automation stopped."
							println "Percent completed : ${percentComplete}%"
							
							def automationSuccess = false;
							if(automationStatus){
								if(automationStatus.currentStepName == null){
									//Name of the step that is being processed automatically. Or, the name of the step that caused
									// automation to stop. If automation is stopped and the workflow status is complete, this 
									//property is set to null.
									
									automationSuccess = true 
								}else{
									println "Workflow stopped at step (${automationStatus.currentStepNumber}, ${automationStatus.currentStepName}, ${automationStatus.currentStepTitle})"
									println "MessageID: ${automationStatus.messageID}"
									println "messageText: ${automationStatus.messageText}"
								}
								println "Raw automation status: " + automationStatus
							}
							storeAutomationStatus(apTool, automationStatus)
							processJobLog(printJOBLOG, outputJOBLOG, apTool, JOBS, jobsBefore, jobFilterMap, ddnameFilterArray, cutOff)
							if(automationSuccess){
								System.exit(0);
							}else{
								System.exit(1);
							}
						}else{
							println "Workflow status unknown. "
							if(automationStatus){
								println "Automation status : " + automationStatus
							}
							storeAutomationStatus(apTool, automationStatus)
							processJobLog(printJOBLOG, outputJOBLOG, apTool, JOBS, jobsBefore, jobFilterMap, ddnameFilterArray, cutOff)
							System.exit(1);
						}
					}else{
						println "Workflow status check failed"
						System.exit(1);
					}
					
					Thread.sleep(stepSeconds * 1000)
					elapseSeconds = elapseSeconds + stepSeconds
					
				}
				println ""
				println "Timeout waiting for workflow to complete. Timeout=" + timeoutValue + " seconds."
				println "Workflow status : " + statusName
				println "Percent completed : ${percentComplete}%"
				if(automationStatus){
					println "Automation status : " + automationStatus
				}
				
				System.exit(1);
			}else{
				System.exit(0);
			}
		}else{
			println "Unknow response code. "
			println resp.statusLine
			System.exit(1)
		}

	}catch (HttpResponseException e){
		//any HTTP status code > 399 will end up here.
		if(printRESTLOG) {
			println "Start workflow response status line:"
			println e.response.statusLine
			println ""
			println "Start workflow response body:"
			println e.response.data? e.response.data : ""
			println ""
		}
		//HTTP 400 Bad request
		//HTTP 404 Not found
		//HTTP 409 Request conflict
		println "Failed to start workflow."
		e.response.data.each {
			println it.key + " : " + it.value
		}
		System.exit(1)
	}
	
}catch (Exception e) {
	println "Error starting workflow : ${e.message}";
	e.printStackTrace();
	System.exit(1);
}


def processJobLog(printJOBLOG, outputJOBLOG, apTool, JOBS, jobsBefore, jobFilterMap, ddnameFilterArray, cutOff){
	//def JOBS, jobsBefore, jobsAfter, jobFilterMap = [:], ddnameFilterArray;
	if(! (printJOBLOG || outputJOBLOG)){
		return
	}
	
	sleep(2000); // wait a little longer so the jobs can be retrieved. 
	def jobsAfter = JOBS.listJobs(jobFilterMap);
	def joblogs = "";
	def newjobs = jobsAfter.data.jobid - jobsBefore.data.jobid
	println "${newjobs.size()} jobs submitted after the workflow is started: " + newjobs
	if(newjobs.size() > 0) {
		jobsAfter.data.each{
			if(newjobs.contains(it.jobid)){
				joblogs += "===================== Output for ${it.jobid} ======================================\n"
				files = JOBS.getSpoolFiles(it.jobname, it.jobid, ddnameFilterArray, cutOff)
				joblogs += "Total ${files.size()} output data sets retrieved for job(${it.jobid}) with ddname matching ${ddnameFilterArray}\n"
				files.each {k,v->
				   joblogs += k
				   joblogs += "\n"
				   joblogs += v
				   joblogs += "\n\n"
				}
			}
		}
	}

	if(printJOBLOG){
		println joblogs
	}
	if(outputJOBLOG){
		apTool.setOutputProperty("zosmf.workflowJoblog", joblogs);
		apTool.storeOutputProperties();
	}
}


def storeAutomationStatus(apTool, automationStatus){
	if(automationStatus == null){
		apTool.setOutputProperty("zosmf.automationstatus", "");
	}else{
		apTool.setOutputProperty("zosmf.automationstatus", automationStatus.toString());
	}	
	apTool.storeOutputProperties();
}

