/*
* Licensed Materials - Property of IBM* and/or HCL**
* UrbanCode Deploy
* (c) Copyright IBM Corporation 2013, 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
*/
import org.apache.http.client.methods.CloseableHttpResponse
import org.apache.http.client.methods.HttpUriRequest
import com.urbancode.commons.httpcomponentsutil.CloseableHttpClientBuilder
import org.apache.http.impl.client.CloseableHttpClient
import org.apache.http.HttpEntity
import org.apache.http.HttpResponse
import org.apache.http.client.methods.RequestBuilder
import org.apache.http.entity.StringEntity
import org.apache.http.entity.FileEntity
import groovy.transform.Field
import com.urbancode.air.AirPluginTool

@Field
AirPluginTool apTool = new AirPluginTool(this.args[0], this.args[1])
@Field
Properties inProps = apTool.getStepProperties()
@Field
Properties outProps = new Properties()

//Input properties
@Field
def inputUntrustedSSL = Boolean.valueOf(inProps['untrustedSSL'])
@Field
def inputUsername = inProps['username']
@Field
def inputPassword = inProps['password']
@Field
def inputUrl = inProps['url']
@Field
def inputContentType = inProps['contentType']
@Field
def inputAcceptType = inProps['acceptType']
@Field
def inputHeaders = inProps['headers']
@Field
def inputDataFile = inProps['dataFile']
@Field
def inputData = inProps['data']
@Field
def inputMethod = inProps['method']
@Field
def inputOutFile = inProps['outFile']


try {
    executeHTTP()
} catch (Exception e) {
    println "Error occurred: ${e.message}"
    System.exit(1)
} finally {
    if (outProps) {
        Enumeration e = outProps.propertyNames()
        while (e.hasMoreElements()) {
            String key = (String) e.nextElement()
            apTool.setOutputProperty(key, outProps.getProperty(key))
        }
        apTool.setOutputProperties()
    }
}

// Main Method
void executeHTTP () {

    println "[action] Building and validating request."

    CloseableHttpClient client = buildHTTPClient(inputPassword, inputUntrustedSSL, inputUsername)

    // validates and encodes the url
    String url = validateURL(inputUrl)

    // Parses inputted headers
    Map headerMap = ["Content-Type":inputContentType,"accept":inputAcceptType]
    headerMap = textAreaToHashMap(inputHeaders, headerMap)

    // Get body data from text or file
    String data = inputData?.trim()
    String dataFile = inputDataFile?.trim()
    HttpUriRequest httpRequest

    if(inputContentType == "application/zip") {
        File infile = new File(dataFile)
        if (infile.exists() && !infile.isDirectory()) {
            // Formulates the complete HTTP request
            httpRequest = createHTTPRequestForZip(url, headerMap, inputMethod, infile)
        }
        else {
            throw new Exception ('A valid file name must be provided for request content type ZIP archive. ')
        }
    }
    else {
        if(dataFile) {
            File infile = new File(dataFile)
            if (infile.exists() && !infile.isDirectory()) {
                println "[action] Reading body from file. "
                data = infile.getText()
            }
            else {
                throw new Exception ('A valid file name must be provided for Data File. ')
            }
            // Formulates the complete HTTP request
        } else if (data) {
            println "[action] Reading body from text box."
        }
        else {
            println "[info] Message body is blank."
        }
        httpRequest = createHTTPRequest(url, headerMap, inputMethod, data)
    }

    CloseableHttpResponse response
    try {
        println "[action] Executing request. " + httpRequest
        // Send Request
        response = client.execute(httpRequest)

        // Process response
        File outFile = null
        if (inputOutFile) {
            File workDir = new File(".").getCanonicalFile()
            outFile = new File(workDir, inputOutFile)
            if(outFile.exists()) {
                outFile.delete()
            }
            println "[action] Response body written to file ${outFile.getAbsolutePath()}"
        }

        println "[action] Processing response."
        httpResponseProcessing(response, outFile)

        Integer status = response.getStatusLine().getStatusCode()
        boolean statusCheck = status > 199 && status < 300
        if (!statusCheck) {
            throw new Exception('The server responded with error code: ' + response.getStatusLine().getStatusCode() + " and message: " + response.getStatusLine())
        }
    } catch (ConnectException e) {
        throw e
    } finally {
        if(response) response.close()
    }
}

/**
 * A method that builds an apache httpclient using our internal library
 *
 * @param   password                        Password String for authentication
 * @param   trustAllCerts                   Boolean true if all certificates should be trusted
 * @param   username                        String username for endpoint
 * @return                                  An apache http components closable httpclient
 */
static CloseableHttpClient buildHTTPClient (
        String password = "",
        boolean trustAllCerts = true,
        String username = "") {

    CloseableHttpClientBuilder clientBuilder = new CloseableHttpClientBuilder()
    clientBuilder.setTrustAllCerts(trustAllCerts)
    if (username && password) {
        clientBuilder.setUsername(username)
        clientBuilder.setPassword(password)
        clientBuilder.setPreemptiveAuthentication(true)
    }
    return clientBuilder.buildClient()
}

/**
 * Encodes a url
 *
 * @param   url     Accepts any URL as defined in RFC 2396 and RFC 2732.
 * @return          A String object with components encoded according to the escaping mechanism defined in RFC 2396
 */
static String validateURL(url) {
    URI aURI
    URL aURL

    // This validates the URL
    try {
        aURL = new java.net.URL(url)
        // See rfc7230 section 2.7.1
        if ((aURL.getProtocol().equalsIgnoreCase('http') || aURL.getProtocol().equalsIgnoreCase('https')) && !aURL.getHost()) {
            throw new MalformedURLException('No hostname was found. Hostname is required for the http or https protocals. See rfc7230 section 2.7.1.')
        }
        if (aURL.getUserInfo()) {
            throw new MalformedURLException('It must not include a userinfo option as part of its authority. See rfc7230 section 2.7.1.')
        }
    } catch (MalformedURLException e) {
        print ("The inputted URL ${url}, is incorrectly formed.")
        if (e.getLocalizedMessage()) {
            print (e.getLocalizedMessage())
        }
        else {
            print ('Either no legal protocol (Example: http, https, file, and jar) could be found in a specification string or the string could not be parsed.')
        }
        println ('Example of correctly formed URL: <protocol>://<authority><path>?<query>#<fragment> or <scheme>://<authority>')
        throw e
    }

    // URI construtor quotes, or encodes, URL to FRC 2396 for each segement.
    try {
        aURI = new java.net.URI(aURL.getProtocol(), aURL.getUserInfo(), aURL.getHost(), aURL.getPort(), aURL.getPath(), aURL.getQuery(), aURL.getRef())
    } catch (URISyntaxException e) {
        println ("Inputted URL, ${url}, violates RFC 2396. Reason: " + e.getReason())
        throw e
    }

    // This returns the validated and encoded URL into a string
    try {
        String result = aURI.toURL().toString()
        return result
    } catch (MalformedURLException e) {
        println ('The inputted URL is incorrectly formed: ' + e.getMessage())
        throw e
    }
}

/**
 * [WSP] *CHAR [WSP] ':' / '=' [WSP] *CHAR [WSP] parsed onto blank or given hashmap
 *
 * @param   textArea     Accepts any [WSP] *CHAR [WSP] ':' / '=' [WSP] *CHAR [WSP]
 * @param   resultMap    Accepts any linked hashmap
 * @return               A linked hashmap built on top of the linked hashmap happy from parsing the textArea
 */
def static textAreaToHashMap(textArea, def resultMap = [:]) {

    if(textArea)
        textArea.eachLine { text ->
            def splitText = text.split('=|:', 2)
            if (splitText != null && splitText[0].trim()) {
                resultMap[splitText[0].trim().toLowerCase()] = splitText[1].trim()
            }
        }
    return resultMap
}

/**
 * Defines the default response property
 *
 * @param   response    Accepts any response entity.
 * @param   outFile     The java File object to use for the output.
 * @param   outputProps Output properties hashmap.
 * @return              A hashmap of the outputs.
 */
Properties httpResponseProcessing(HttpResponse response, File outFile) {

    outProps['responseStatus'] = response.getStatusLine().getStatusCode().toString()
    println("[response] The status code is: " + response.getStatusLine().getStatusCode().toString())
    response.getAllHeaders().each { header ->
        outProps[header.name] = header.value.toString()
    }
    HttpEntity entity = response.getEntity()
    if(entity) {
        File entityTempFile = File.createTempFile('httpresponse', '', new File('.'))
        entityTempFile.deleteOnExit()
        FileOutputStream entityTempFileIOStream = new FileOutputStream(entityTempFile)
        entity.writeTo(entityTempFileIOStream)

        //TODO: Set this output property if the response entity is a readable content
        outProps["responseBody"] = entityTempFile.getText()
        entityTempFileIOStream.close()
        if(outFile) {
            entityTempFile.renameTo(outFile)
        }
    }
    else {
        outProps["responseBody"] = ""
    }
    return outProps
}

/**
 * Creates an HTTP request object which represent a HTTP request message.
 *
 * @param   url         Accepts any URL as defined in RFC 2396 and RFC 2732.
 * @param   headers     Accepts any list.
 * @param   method      Accepts any String.
 * @param   body        Accepts any String.
 * @return              A HttpRequest
 */
static HttpUriRequest createHTTPRequest (String url, Map headers, String method, String body) {

    RequestBuilder httpRequest = new RequestBuilder().create(method)
    httpRequest.setUri(url)
    for (header in headers) {
        String key = header.getKey().toString()
        String value = header.getValue().toString()
        httpRequest.setHeader(key, value)
    }
    if(body != null) {
        httpRequest.setEntity(new StringEntity(body))
    }
    return httpRequest.build()
}

/**
 * Creates an HTTP request object which represent a HTTP request with a zip file in the content
 *
 * @param   url         Accepts any URL as defined in RFC 2396 and RFC 2732.
 * @param   headers     Accepts any list.
 * @param   method      Accepts any String.
 * @param   file        Accepts a File
 * @return              A HttpRequestEntity
 */
static HttpUriRequest createHTTPRequestForZip (String url, Map headers, String method, File file) {

    RequestBuilder httpRequestEntity = new RequestBuilder().create(method)
    httpRequestEntity.setUri(url)
    for (header in headers) {
        String key = header.getKey().toString()
        String value = header.getValue().toString()
        httpRequestEntity.setHeader(key, value)
    }
    httpRequestEntity.setEntity(new FileEntity(file,"application/zip"))

    return httpRequestEntity.build()
}
