/*
* Licensed Materials - Property of IBM Corp.
* IBM UrbanCode Build
* IBM UrbanCode Deploy
* IBM UrbanCode Release
* IBM AnthillPro
* (c) Copyright IBM Corporation 2002, 2013. 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.automation

import groovy.json.JsonBuilder
import groovy.json.JsonException
import groovy.json.JsonSlurper
import groovy.xml.MarkupBuilder

import org.apache.http.client.HttpClient
import org.apache.http.client.methods.HttpGet
import org.apache.http.client.methods.HttpPost
import org.apache.http.client.utils.URIBuilder
import org.apache.http.entity.ContentType
import org.apache.http.entity.StringEntity
import org.apache.http.HttpResponse
import org.apache.http.HttpStatus
import org.apache.http.NameValuePair
import org.xml.sax.SAXException
import org.xml.sax.SAXParseException

import com.urbancode.commons.httpcomponentsutil.HttpClientBuilder

public class RallyHelper {

    private static String host
    private static String version
    private static String apiKey
    private static HttpClient client

    /**
    *  @param apiKey The api key to use for token authentication with the Rally Server
    *  @param username The username to use for basic authentication with the Rally Server
    *  @param password The password associated with the username to use for basic authentication with the Rally Server
    *  @param proxyHost The hostname of the proxy server
    *  @param proxyPort The port to use on the proxy server
    *  @param proxyUser The username to authenticate with the proxy server
    *  @param proxyPass The password associated with the proxyUser to authenticate with the proxy server
    */
    public static void setupHttpClient(String apiKey, String username, String password, String proxyHost, String proxyPort,
                                       String proxyUser, String proxyPass) {
        HttpClientBuilder builder = new HttpClientBuilder()

        if (apiKey) {
            println ('[ok]  Using token based authentication.')
            this.apiKey = apiKey
        }
        else if (username) {
            println ('[warning]  Using preemptive basic authentication.')
            builder.setTrustAllCerts(true)
            builder.setPreemptiveAuthentication(true)
            builder.setUsername(username)
            builder.setPassword(password)
        }

        if (proxyHost) {
            builder.setProxyHost(proxyHost)
            builder.setProxyPort(Integer.valueOf(proxyPort))
            if (proxyUser && proxyPass) {
                builder.setProxyUsername(proxyUser)
                builder.setProxyPassword(proxyPass)
            }
        }
        this.client = builder.buildClient()
    }

    /**
    *  @param host The hostname of the Rally Server
    *  @param version The version of Rally on the Rally Server
    *  @param workspace The name of the workspace in Rally
    *  @return The URL reference for the workspace
    */
    public static String configureWorkspace(String host, String version, String workspace) {
        this.host = host
        if (version.startsWith('2.')) {
            this.version = 'v' + version
        }
        else {
            this.version = version
        }
        println ("\n[action]  Retrieving url reference for workspace ${workspace}...")
        String getBody
        try {
            getBody = executeHttpGet('workspace')
        }
        catch (IOException ioe) {
            println ('[error]  Could not contact Rally Server.')
            ioe.printStackTrace()
            System.exit(1)
        }

        def workspaceRef
        try {
            def workNodes = getElementArray(getBody, 'Results.Object', 'QueryResult.Results')
            if (!workNodes) {
                println ('[error]  Could not determine workspace reference.')
            }
            if (getBody.substring(0,1).equals('<')) {
                def workNode = workNodes.findAll{ it.'@refObjectName'.equals(workspace) }
                if (workNode.size() > 0) {
                    workspaceRef = workNode[0].'@ref'
                }
                else {
                    println ('[error]  Workspace ' + workspace + ' not found on Rally server.')
                    System.exit(1)
                }
            }
            else {
                def workNode = workNodes.findAll{ it.get('_refObjectName').equals(workspace) }
                if (workNode.size() > 0) {
                    workspaceRef = workNode[0].'_ref'
                }
                else {
                    println ("[error]  Workspace ${workspace} not found on Rally server.")
                    System.exit(1)
                }
            }
        }
        catch (IOException ioe) {
            println ('[error]  Returned response not well formed.')
            println ('Response: ' + getBody)
            ioe.printStackTrace()
            System.exit(1)
        }
        catch (SAXParseException spe) {
            println ('[error]  XML is not well-formed.')
            println ('XML: ' + getBody.replace('\n', ''))
            spe.printStackTrace()
            System.exit(1)
        }
        catch (JsonException jse) {
            println ('[error]  JSON is not well-formed.')
            println ('JSON: ' + getBody.replace('\n', ''))
            jse.printStackTrace()
            System.exit(1)
        }
        catch (MissingPropertyException mpe) {
            println ('[error]  Returned response not well formed.')
            println ('Response: ' + getBody)
            mpe.printStackTrace()
            System.exit(1)
        }
        catch (NullPointerException npe) {
            println ('[error]  Returned response not well formed.')
            println ('Response: ' + getBody)
            npe.printStackTrace()
            System.exit(1)
        }
        println ("[ok] Workspace reference found: ${workspaceRef}")
        return workspaceRef
    }

    /**
    *  @param getEndpoint The path to be added to the end of the URL host/slm/webservice/version
    *  @return The text of the body contained in the HTTP Response
    */
    public static def executeHttpGet(String getEndpoint) {
        return executeHttpGet(getEndpoint, null)
    }

    /**
    *  @param getEndpoint The path to be added to the end of the URL host/slm/webservice/version
    *  @param queryParams A NameValuePair list of the query parameters to set for the GET method call
    *  @return The text of the body contained in the HTTP Response or false for any failure
    */
    public static def executeHttpGet(String getEndpoint, List<NameValuePair> queryParams) {
        return executeHttpRequest(getEndpoint, queryParams, null)
    }

    /**
    *  @param postEndpoint The path to be added to the end of the URL host/slm/webservice/version
    *  @param postContent The content to be converted to a StringEntity and set as the POST entity
    *  @return The text of the body contained in the HTTP Response or false for any failure
    */
    public static def executeHttpPost(String postEndpoint, String postContent) {
        return executeHttpRequest(postEndpoint, null, postContent)
    }

    /**
    *  @param endPoint The path to be added to the end of the URL host/slm/webservice/version
    *  @param queryParams A NameValuePair list of the query parameters to set for a GET method call
    *  @param post The content to be converted to a StringEntity and set as a POST entity
    *  @return The text of the body contained in the HTTP Response or false for any failure
    */
    private static def executeHttpRequest(String endPoint, List<NameValuePair> queryParams, String post) {
        URIBuilder uri
        try {
            uri = new URIBuilder(host)
        }
        catch (URISyntaxException use) {
            println ("[error]  Invalid syntax for hostname: ${use.getMessage()}.")
            println ('[possible solution]  Please update the step configuration with a vaild hostname.')
            return false
        }
        uri.setPath('/slm/webservice/' + version + '/' + endPoint)
        if (queryParams) {
            uri.setParameters(queryParams)
        }
        def request
        try {
            if (post) {
                request = new HttpPost(uri.toString())
                ContentType postType
                if (version.substring(0,2).equals('1.')) {
                    postType = ContentType.APPLICATION_XML
                }
                else {
                    postType = ContentType.APPLICATION_JSON
                }
                request.setEntity(new StringEntity(post, postType))
            }
            else {
                request = new HttpGet(uri.toString())
            }
        }
        catch (IllegalArgumentException iae) {
            println ("[error]  Invalid uri: ${iae.getMessage()}.")
            println ("uri: ${uri.toString()}")
            return false
        }
        if (apiKey) {
            request.setHeader('zsessionid', apiKey)
        }
        HttpResponse response
        try {
            response = client.execute(request)
        }
        catch (IOException ioe) {
            println ("[error]  Problem executing HTTP request: ${ioe.getMessage()}.")
            println ("Response: ${response}")
            return false
        }
        if (!response.getStatusLine().getStatusCode().equals(HttpStatus.SC_OK)) {
            println ("[error]  Bad response code of ${response.getStatusLine().getStatusCode()} - ${response}.")
            return false
        }
        return response.entity?.content?.text
    }

    /**
    *  @param text The text to parse for erros and warnings
    */
    public static void printWarngingsAndErrors(String text, String resultType) {
        def errors = getElementArray(text, "Errors.${resultType}ResultError", "${resultType}Result.Errors")
        def warnings  = getElementArray(text, "Warnings.${resultType}ResultWarning", "${resultType}Result.Warnings")
        if (errors instanceof ArrayList && warnings instanceof ArrayList) {
            if (errors.size() > 0) {
                errors.each { println ("[error]  ${it}.") }
            }
            else if (warnings.size() > 0) {
                warnings.each { println ("[warning]  ${it}.") }
            }
            else {
                println ('[ok]  No errors or warnings from Rally.')
            }
        }
        else {
            println ('[error]  Could not get errors or warnings from response.')
        }
    }

    /**
    *  @param text The text to parse for the desired element valueOf
    *  @param xmlPath The path to the desired element if text is xmlPath
    *  @param jsonPath The path to the desired element if text is JSON
    *  @return The array containing all values at end of path
    */
    public static def getElementArray(String text, String xmlPath, String jsonPath) {
        try {
            if (text.startsWith('<')) {
                try {
                    return traversePath(new XmlParser().parseText(text), xmlPath)
                }
                catch (SAXException se) {
                    println ('[error]  XML is not well-formed.')
                    println ("XML: ${text.replace('\n', '')}")
                    return false
                }
            }
            else {
                try {
                    return traversePath(new JsonSlurper().parseText(text), jsonPath)
                }
                catch (JsonException jse) {
                    println ('[error]  JSON is not well-formed.')
                    println ("JSON: ${text.replace('\n', '')}")
                    return false
                }
            }
        }
        catch (IOException ioe) {
            println ('[error]  Response is not well-formed.')
            println ("Response: ${text.replace('\n', '')}")
            return false
        }
        catch (NullPointerException npe) {
            println ('[error]  Response is not well-formed.')
            println ("Response: ${text.replace('\n', '')}")
            return false
        }
    }

    /**
    *  @param text The text to parse for the desired element valueOf
    *  @param xmlPath The path to the desired element if text is xmlPath
    *  @param jsonPath The path to the desired element if text is JSON
    *  @return The value of the desired element
    */
    public static def getElement(String text, String xmlPath, String jsonPath) {
        def array = getElementArray(text, xmlPath, jsonPath)
        if (array) {
            if (text.startsWith('<')) {
                if (array instanceof NodeList) {
                    return array.text()
                }
                else {
                    return array[0].text()
                }
            }
            else {
                if (array instanceof ArrayList) {
                    return array[0]
                }
                else {
                    return array
                }
            }
        }
        else {
            return false
        }
    }

    /**
    *  @param parser The parser containing the object structure (XML or JSON)
    *  @param path The path in the object strucutre to the desired element
    *  @return The value of the last element in the path
    */
    private static def traversePath(def parser, String path) {
        try {
            path.split('\\.').each { element ->
                if (parser instanceof NodeList && parser.isEmpty()) {
                    return false
                }
                else {
                    parser = parser."${element}"
                }
            }
            return parser
        }
        catch (IOException ioe) {
            println ('[error]  Response is not well-formed.')
            println ("Response: ${parser.toString().replace('\n', '')}")
            return false
        }
        catch (SAXParseException spe) {
            println ('[error]  XML is not well-formed.')
            println ("XML: ${parser.toString().replace('\n', '')}")
            return false
        }
        catch (MissingPropertyException mpe) {
            println ('[error]  Response is not well-formed.')
            println ("Response: ${parser.toString().replace('\n', '')}")
            return false
        }
        catch (NullPointerException npe) {
            println ('[error]  Response is not well-formed.')
            println ("Response: ${parser.toString().replace('\n', '')}")
            return false
        }
    }

    /**
    *  @param items A comma separated list of items to put into a list
    *  @return A List of containing the items of the comma separated input
    */
    public static ArrayList<String> makeList(String items) {
        ArrayList<String> list = new ArrayList<String>()
        items.split(',').each { item ->
            if (item.size() > 0) {
                list.push(item.trim())
            }
        }
        return list
    }

    /**
    *  @param rootNode The name of the root node of the POST info
    *  @param childNodes The child nodes to append to the root node
    *  @return A string of the post data
    */
    public static String buildPost(String rootNode, HashMap<String, String> childNodes) {
        def postInfo
        if (version.substring(0,2).equals('1.')) {
            postInfo = new StringWriter()
            def builder = new MarkupBuilder(postInfo)
            addChildren(builder, rootNode, childNodes)
        }
        else {
            postInfo = new JsonBuilder()
            addChildren(postInfo, rootNode, childNodes)
        }
        return postInfo.toString()
    }

    /**
    *  @param builder The builder to interact with either XML or JSON
    *  @param rootNode The name of the root node of the POST info
    *  @param childNodes The child noes to append to the root node
    *  @return The builder with the nodes appended
    */
    private static def addChildren(def builder, String rootNode, HashMap<String, String> childNodes) {
        builder."${rootNode}"(){
            childNodes.each { element ->
                "${element.getKey()}"("${element.getValue()}")
            }
        }
    }
}
