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

import com.urbancode.air.CommandHelper
import org.xml.sax.SAXParseException

public class SeleniumHelper {

    final File resourceHome = new File(System.getenv()['PLUGIN_HOME'])
    final def workDir = new File('.').canonicalFile
    def ch = new CommandHelper(workDir)
    final def out = System.out
    def seleniumJar
    def webBrowser
    def startingUrl
    def testSuite
    def testResults
    def portNumber
    def failurePercentThreshold = 100

    SeleniumHelper(def props) {
        seleniumJar = props['seleniumJar'].trim()
        if (!new File(seleniumJar).isFile()) {
            throw new RuntimeException("[Error] Could not find Selenium Jar file: ${seleniumJar}")
        }
        /**
         * Possibly supported browsers (from selenium-server-standalone-2.53.1.jar) include:
         * *firefoxchrome
         * *konqueror
         * *chrome
         * *safari
         * *piiexplore
         * *googlechrome
         * *firefox
         * *pifirefox
         * *iexploreproxy
         * *iehta
         * *firefoxproxy
         * *safariproxy
         * *iexplore
         * *mock
         * *webdriver
         * *custom
         * Note: The user must have the appropriate browser drivers set up locally.
         */
        webBrowser  = props['webBrowser'].trim()
        startingUrl = props['startingUrl'].trim()

        testSuite   = props['testSuite'].trim()
        if (!new File(testSuite).isFile()) {
            throw new RuntimeException("[Error] Could not find Selenium test suite file: ${testSuite}")
        }

        testResults = props['testResults'].trim()
        if (!testResults) {
            testResults = new File("testResults").getCanonicalPath()
        }

        portNumber = props['portNumber'].trim()
        if (portNumber && !portNumber.isInteger()){
            throw new IllegalStateException('Port must be an integer')
        }

        if (props['failurePercentThreshold']) {
            failurePercentThreshold = Integer.valueOf(props['failurePercentThreshold'].trim())
            if ((0 > failurePercentThreshold) || (failurePercentThreshold > 100)) {
                throw new IllegalStateException('[Error] Failure Percent Threshold must be between 0 and 100')
            }
        }
        println "[Info] The maximum percentage of Selenium tests allowed to fail: " + failurePercentThreshold + "%"

        ch.ignoreExitValue(true)
    }

    public def runTest() {
        try {
            // Construct and run selenium test command
            def args = []
            args = ['java', '-jar', seleniumJar, '-htmlSuite', webBrowser, startingUrl, testSuite, testResults]
            if (portNumber) {
                args << '-port'
                args << portNumber
            }

            def initialSize = 4098
            def errStream = new ByteArrayOutputStream(initialSize)
            def printStream = new PrintStream(errStream, true)

            def status = ch.runCommand("Running html test suite ${testSuite}", args) { proc ->
                proc.out.close()
                try {
                   proc.waitForProcessOutput(out, printStream)
                }
                finally {
                    out.flush()
                }
            }
            def errMessage = errStream
            println ""
            println "Output of ${seleniumJar}'s command: "
            println ""
            println errMessage
            println ""

            // Read and interpret status and errMessage
            if (status) {
                    if (errMessage =~ 'Tests failed, see result file for details'){
                         println("[Error] Selenium tests failed. See result file for complete details : ${testResults}.\n")
                    }
                    else if (errMessage =~ 'SystemRoot apparently not set!') {
                        println ('Error may be caused by browser: ' + webBrowser + ' not installed\n')
                        throw new Exception("[Error] Failed to run test server with exit code: ${status}")
                    }
                    else {
                        println ('Unknown Exception Occurred: ' + errMessage)
                        throw new Exception("[Error] Failed to run test server with exit code: ${status}")
                    }
            }
            else {
                println("[Info] Selenium tests ran successfully.")
            }
        }
        catch (Exception e) {
            println e
            System.exit(1)
        }
    }

    /**
     * Identify and retrieve results from the test results file.
     */
    public def getResults() {

        // Read testResults file
        def outputFile = new File(testResults)

        // If testResults file is a directory, we are running v3, and we need to identify the results
        // file based on the browser name.
        if (outputFile.isDirectory()) {
            String newTestResults = webBrowser + ".results.html"
            if (newTestResults.startsWith("*")) {
                newTestResults = newTestResults.substring(1)
            }
            outputFile = new File(testResults + File.separator + newTestResults)
            this.testResults = outputFile.getCanonicalPath()
        }

        // If we can't identify the test results file, fail
        if (!outputFile.isFile()) {
            throw new RuntimeException("[Error] Unable to find Test Results file: ${testResults}")
        }

        def replaced = outputFile.text.replaceAll("&nbsp", " ")
        if (replaced.indexOf("&nbsp") > 0) {
            throw new Exception("Non-breaking spaces in test results file not all replaced!")
        }

        // Convert the test results file into XmlSlurper
        def slurper = new XmlSlurper()
        def htmlParser
        try {
            htmlParser = slurper.parseText(replaced)
        } catch (SAXParseException ex) {
            throw new SAXParseException("[Error] Unable to parse results file: ${testResults}\n" +
                "[Possible Solution] View results file and confirm tests can be run manually.")
        }

        // Retrieve all test results summary results.
        String result
        int totalTime
        int numTestTotal
        int numTestPasses
        int numTestFailures
        int numCommandPasses
        int numCommandFailures
        int numCommandErrors
        String seleniumVersion
        String seleniumRevision
        htmlParser.body.table.tr.each{
            try {
                if (it.td[0] == "result:"){
                    result = it.td[1].toString().trim()
                }
                else if (it.td[0] == "totalTime:"){
                    totalTime = Integer.valueOf(it.td[1].toString().trim())
                }
                else if (it.td[0] == "numTestTotal:"){
                    numTestTotal = Integer.valueOf(it.td[1].toString().trim())
                }
                else if (it.td[0] == "numTestPasses:"){
                    numTestPasses = Integer.valueOf(it.td[1].toString().trim())
                }
                else if (it.td[0] == "numTestFailures:"){
                    numTestFailures = Integer.valueOf(it.td[1].toString().trim())
                }
                else if (it.td[0] == "numCommandPasses:"){
                    numCommandPasses = Integer.valueOf(it.td[1].toString().trim())
                }
                else if (it.td[0] == "numCommandFailures:"){
                    numCommandFailures = Integer.valueOf(it.td[1].toString().trim())
                }
                else if (it.td[0] == "numCommandErrors:"){
                    numCommandErrors = Integer.valueOf(it.td[1].toString().trim())
                }
                else if (it.td[0] == "Selenium Version:"){
                    seleniumVersion = it.td[1].toString().trim()
                }
                else if (it.td[0] == "Selenium Revision:"){
                    seleniumRevision = it.td[1].toString().trim()
                }
            } catch (Exception ex) {
                ex.printStackTrace()
                println "[Warn] Unable to identify the key and value of `${it.td}`."
            }
        }

        return [result, totalTime, numTestTotal, numTestPasses, numTestFailures, numCommandPasses,
                numCommandFailures, numCommandErrors, seleniumVersion, seleniumRevision ]
    }

    /**
     * param numTestPasses Total number of successful tests
     * param numTestFailures Total number of failed tests
     * return True if threshold is less than identified failure percentage, otherwise fail
     */
    public boolean checkResults(int numTestPasses, int numTestFailures) {
        boolean result = false

        // If numTestPasses and numTestFailures values are empty, ignore test and return false
        if (numTestPasses != null && numTestFailures != null) {

            int failurePercent = numTestFailures / (numTestFailures + numTestPasses) * 100

            if (failurePercent > failurePercentThreshold) {
                println "[Info] Failure: The calculated failure percentage (${failurePercent}%) is greater" +
                        " than the specified failure percentage limit (${failurePercentThreshold}%)."
            } else {
                println "[Info] Success: The calculated failure percentage (${failurePercent}%) is less than or" +
                        " equal to the specified failure percentage limit (${failurePercentThreshold}%)."
                result = true
            }
        } else {
            println "[Warn] Unable to identify the total number of passed and failed tests."
        }
        return result
    }

    /**
     * Prints complete testResults file to output
     */
    public void printFile(String file) {
        File outputFile = new File(file)
        if (outputFile.isFile()) {
            println ""
            println "File Location: ${file}"
            println ""
            println "****************************************************************************************************"
            println "****************************************************************************************************"
            println "****************************************************************************************************"
            println ""
            println ""
            println ""

            //print the results html here
            outputFile.eachLine { line ->
               println line
            }

            println ""
            println ""
            println ""
            println "****************************************************************************************************"
            println "****************************************************************************************************"
            println "****************************************************************************************************"
        }
        else {
            println "[Error] File ${file} cannot be found."
        }
    }

    /**
     * Prints the summary results in an easily digestible format
     */
    public void printSummary(String result, int totalTime, int numTestTotal, int numTestPasses,
            int numTestFailures, int numCommandPasses, int numCommandFailures, int numCommandErrors,
            String seleniumVersion, String seleniumRevision) {
        println ""
        println "Test Suite Results Summary"
        println "Result: " + result
        println "Total Time: " + totalTime
        println "Number of Total Tests: " + numTestTotal
        println "Number of Test Passes: " + numTestPasses
        println "Number of Test Failures: " + numTestFailures
        println "Number of Command Passes: " + numCommandPasses
        println "Number of Command Failures: " + numCommandFailures
        println "Number of Command Errors: " + numCommandErrors
        println "Selenium Version: " + seleniumVersion
        println "Selenium Revision: " + seleniumRevision
        println ""
    }
}
