package com.urbancode.air.plugn.test.mstest
import org.xml.sax.Attributes;
import org.xml.sax.InputSource
import org.xml.sax.helpers.DefaultHandler;

public class MSTestHandler extends DefaultHandler {
    final SUCCESS = "success"
    final FAILURE = "failure"
    final ERROR = "error"

    final millisPerSecond = 1000
    final millisPerMinute = 60 * millisPerSecond
    final millisPerHour = 60 * millisPerMinute

    final resultMap = buildResultMap()

    def filename
    def onNewTest
    def testTable = [:]

    def inUnitTest
    def inUnitTestResult
    def inMessage

    def testName
    def testResult
    def testDuration
    def testId

    def unitTestId
    def unitTestClassName
    def unitTestName

    def messages = []
    def currentText = new StringBuilder()

    MSTestHandler(def filename) {
        this.filename = filename
    }

    void startElement(String ns, String localName, String qName, Attributes attrs) {
        switch (qName) {
            case 'UnitTest':
                inUnitTest = true
                unitTestId = attrs.getValue('id')
                break;

            case 'UnitTestResult':
                inUnitTestResult = true
                testId = attrs.getValue('testId')
                testName = attrs.getValue('testName')
                testResult = toTestResult(attrs.getValue('outcome'))
                testDuration = toTestDuration(attrs.getValue('duration'))
                break;

            case 'Message':
                if (inUnitTestResult) {
                    inMessage = true
                }
                break;

            case 'TestMethod':
                if (inUnitTest) {
                    unitTestClassName = getClassName(attrs.getValue('className'))
                    unitTestName = attrs.getValue('name')
                }
                break;
        }
    }

    void endElement(String ns, String localName, String qName) {
        switch (qName) {
            case 'UnitTest':
                testTable[unitTestId] = [className: unitTestClassName, name: unitTestName]
                inUnitTest = false
                break;

            case 'UnitTestResult':
                def className = testTable[testId].className
                onNewTest(testName, className, testResult, testDuration, messages)
                inUnitTestResult = false
                messages = []
                break;

            case 'Message':
                if (inUnitTestResult) {
                    messages << currentText.toString()
                    currentText = new StringBuilder()
                    inMessage = false;
                }
                break;
        }
    }
    
    void characters(char[] ch, int start, int length) {
        if (inMessage) {
            currentText.append(ch, start, length)
        }
    }
    
    def toTestResult(msTestOutcome) {
        return resultMap[msTestOutcome]
    }

    def toTestDuration(msTestDuration) {
        def matcher = msTestDuration =~ /(\d+):(\d+):(\d+(?:.\d+)?)/
        def hours = matcher[0][1]
        def minutes = matcher[0][2]
        def seconds = matcher[0][3]
        def duration = hours.toInteger() * millisPerHour +
                       minutes.toInteger() * millisPerMinute +
                       seconds.toBigDecimal() * millisPerSecond
        return duration.setScale(0, java.math.RoundingMode.HALF_UP)
    }

    def buildResultMap() {
        def map = [
            'Error': ERROR,
            'Failed': FAILURE,
            'Timeout': FAILURE,
            'Aborted': FAILURE,
            'Inconclusive': FAILURE,
            'PassedButRunAborted': SUCCESS,
            'NotRunnable': ERROR,
            'NotExecute': FAILURE,
            'Disconnected': ERROR,
            'Warning': SUCCESS,
            'Passed': SUCCESS,
            'Completed': SUCCESS,
            'InProgress': ERROR,
            'Pending': ERROR]
        return map.asImmutable()
    }

    def getClassName(classStrongName) {
        def i = classStrongName.indexOf(',')
        def className = classStrongName[0 ..< i].trim()
        className = className.replaceAll(/\+/, '.')
        className = className.replaceAll('\\\\\\\\', '\\\\')
        return className
    }

    def readTests(Closure closure) {
        onNewTest = closure
        def reader = SAXParserFactory.newInstance().newSAXParser().XMLReader
        def suiteFile = (filename instanceof File) ? filename : new File(filename)
        suiteFile.withInputStream { stream ->
            reader.setContentHandler(this)
            reader.parse(new InputSource(stream))
        }
    }
}