/*
* Licensed Materials - Property of IBM Corp.
* IBM UrbanCode Build
* (c) Copyright IBM Corporation 2015. 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.scm

import java.text.SimpleDateFormat
import java.util.regex.Matcher
import java.util.regex.Pattern

import org.apache.http.client.HttpClient
import org.apache.http.client.methods.HttpPost
import org.apache.http.entity.StringEntity
import org.apache.http.impl.client.DefaultHttpClient

import com.urbancode.air.CommandHelper
import com.urbancode.air.plugin.scm.changelog.*
import com.urbancode.commons.httpcomponentsutil.HttpClientBuilder
import com.urbancode.commons.util.IO

public class SCMChangelog extends SCMStep {

    //**************************************************************************
    // CLASS
    //**************************************************************************

    //**************************************************************************
    // INSTANCE
    //**************************************************************************

    // Changeset Variables
    final def CHANGE_DATE_FORMAT = new SimpleDateFormat("MM-dd-yyyy;hh:mm:ss")
    final def HARVEST_DATE_FORMAT = new SimpleDateFormat("MM/dd/yyyy")
    final def REVISION_START_DELIMITER = "--------------"
    final def SPACE_SEPARATOR_PATTERN = Pattern.compile("(     )")
    final def TAB_SEPARATOR_PATTERN = Pattern.compile("(\t)")
    final def changeSetFilter = new ChangeSetFilter()
    def changeSets
    def startDate
    def endDate
    def label
    def changesUrl


    def execute() {
        CHANGE_DATE_FORMAT.timeZone = TimeZone.getTimeZone("UTC")

        def logOutput = runLogCommand()
        def changeSets = parseChangesets(logOutput)

        if (changeSets) {
            ChangeSetXMLHelper xmlHelper = new ChangeSetXMLHelper()
            xmlHelper.repoType = REPO_TYPE
            def xml = xmlHelper.toXML(changeSets)
            sendPostRequest(xml)
        }
        else {
            println "No changes detected"
            return null
        }
    }

    def parseChangesets(String logOutput) {
        def changeSetMap = [:]
        changeSets = []
        boolean isReadingChanges = false
        String previousLine = null
        int fileNameIndex = 0
        int viewPathIndex = 0
        int packageIndex = 0
        int versionIndex = 0
        int tagIndex = 0
        int userIndex = 0
        int dateIndex = 0

        logOutput.eachLine { line ->
            if (isReadingChanges && line.trim().length() > 0) {
                final Matcher matcher = getMatcherForLine(line)
                int index = 0
                int count = 0
                boolean isCompleteChange = false
                String fileName = null
                String packageName = null
                String viewPath = null
                String version = null
                String tag = null
                String user = null
                String date = null
                while (matcher.find() && !isCompleteChange) {
                    String value = line.substring(index, matcher.start()).trim()
                    index = matcher.end()
                    count++
                    if (count == fileNameIndex) {
                        fileName = value
                    }
                    else if (count == viewPathIndex) {
                        viewPath = value
                    }
                    else if (count == versionIndex) {
                        version = value
                    }
                    else if (count == tagIndex) {
                        tag = value
                    }
                    else if (count == packageIndex) {
                        packageName = value
                    }
                    else if (count == userIndex) {
                        user = value
                    }
                    else if (count == dateIndex) {
                        date = value
                        isCompleteChange = true
                    }
                }

                if (isCompleteChange) {
                    ChangeSet changeSet = new ChangeSet()
                    def existingChange = changeSetMap.get(packageName)

                    if (existingChange) {
                        changeSet = existingChange
                    }

                    changeSet.id = packageName
                    changeSet.user = user
                    changeSet.date = CHANGE_DATE_FORMAT.parse(date)

                    ChangeSetFileEntry fileEntry = new ChangeSetFileEntry()
                    fileEntry.revision = version
                    fileEntry.type = tag
                    fileEntry.path = getCompletePath(viewPath, fileName)
                    changeSet.fileSet << fileEntry
                    changeSetMap << [packageName : changeSet]
                }
            }
            else if (line.startsWith(REVISION_START_DELIMITER)) {
                isReadingChanges = true
                // now we need to figure out which data is in which column
                if (previousLine == null) {
                    throw new IllegalStateException("Did not find column names!")
                }
                else {
                    final Matcher matcher = getMatcherForLine(previousLine)
                    int index = 0
                    int count = 0
                    while (matcher.find()) {
                        String value = previousLine.substring(index, matcher.start()).trim()
                        index = matcher.end()
                        count++
                        if (value.trim().toLowerCase().equals("name")) {
                            fileNameIndex = count
                        }
                        else if (value.trim().toLowerCase().equals("path")) {
                            viewPathIndex = count
                        }
                        else if (value.trim().toLowerCase().equals("version")) {
                            versionIndex = count
                        }
                        else if (value.trim().toLowerCase().equals("tag")) {
                            tagIndex = count
                        }
                        else if (value.trim().toLowerCase().equals("package")) {
                            packageIndex = count
                        }
                        else if (value.trim().toLowerCase().equals("modifier")) {
                            userIndex = count
                        }
                        else if (value.trim().toLowerCase().equals("modified")) {
                            dateIndex = count
                        }
                    }
                }
            }
            else if (line.trim().length() == 0) {
                isReadingChanges = false
                previousLine = null
            }
            else {
                previousLine = line
            }
        }

        changeSets = changeSetMap.values()

        //filter results
        changeSets.retainAll{ ChangeSet changeSet ->
            boolean allowAuthor = changeSet.hasAllowedAuthor(changeSetFilter);
            boolean allowAnyPaths = changeSet.hasAllowedPath(changeSetFilter);
            if (allowAuthor && allowAnyPaths) {
                // retain changeset
                return true;
            }
            else {
                def message = new StringBuilder("Changeset ${changeSet.id} skipped because ")
                if (!allowAuthor) {
                    message << "it has excluded author ${changeSet.user}"
                }
                if (!allowAuthor && !allowAnyPaths) {
                    message << " and "
                }
                if (!allowAnyPaths) {
                    message << "it contains only excluded file paths (${changeSet.fileSet.collect{it.path}})"
                }
                println message
                // exclude changeset
                return false;
            }
        }
        return changeSets
    }

    String runLogCommand() {
        def logFile = File.createTempFile("hsv-", ".log", directory)
        logFile.deleteOnExit()
        def cmdArgs = commandHelperBase()
        def parsedStart = formatDate(startDate, false)
        // need to add a day to the end date in order to get any revisions for that day
        // because harvest does not accept time on the command line and only looks
        // until the begining of that day.
        def parsedEnd = formatDate(endDate, true)
        ["-iv", "av", "-id",  "sd", parsedStart, parsedEnd, "-o", logFile].each {
            cmdArgs << it
        }

        cmdHelper.runCommand('Getting Changelog', cmdArgs)

        return logFile.getText('UTF-8')
    }

    private void sendPostRequest(String xml) {
        // construct the URL with property replacements
        String url = changesUrl
        def authToken = System.getenv("AUTH_TOKEN")

        println "Sending request to $url"

        // Debug/testing stub
        if (url.startsWith("file://")) {
            File xmlOut = new File(workDir, "xmlOut.xml")
            xmlOut << xml
        }
        else {
            HttpClientBuilder clientBuilder = new HttpClientBuilder();
            clientBuilder.setTrustAllCerts(true);
            DefaultHttpClient client = clientBuilder.buildClient();

            HttpPost postMethod = new HttpPost(url)
            if (authToken) {
                postMethod.addHeader("Authorization-Token", authToken)
                postMethod.addHeader("Content-Type", "application/xml")
            }

            println "Sending ${changeSets.size()} changes"
            postMethod.setEntity(new StringEntity(xml))

            def response = client.execute(postMethod)
            def responseCode = response.statusLine.statusCode
            InputStream responseStream = response.entity.content
            if (isGoodResponseCode(responseCode)) {
                IO.copy(responseStream, System.out)
                println ""
            }
            else {
                IO.copy(responseStream, System.err)
                throw new RuntimeException("Failed to upload source changes. StatusCode: ${responseCode}")
            }
        }
    }

    protected boolean isGoodResponseCode(int responseCode) {
        return responseCode >= 200 && responseCode < 300;
    }

    protected def formatDate(def date, boolean addADay) {
        if (addADay) {
            Calendar c = Calendar.getInstance()
            c.setTime(date)
            c.add(Calendar.DAY_OF_YEAR, 1)
            date = c.getTime()
        }
        return HARVEST_DATE_FORMAT.format(date)
    }

    protected Matcher getMatcherForLine(String line) {
        final Matcher matcher
        if (line.contains("\t")) {
            matcher = TAB_SEPARATOR_PATTERN.matcher(line)
        }
        else {
            matcher = SPACE_SEPARATOR_PATTERN.matcher(line)
        }
        return matcher
    }

    protected String getCompletePath(String viewPath, String fileName) {
        String result = "";
        if (viewPath != null && fileName != null) {
            String separator = "\\";
            if (viewPath.indexOf('/') != -1) {
                separator = "/";
            }
            result = viewPath + separator + fileName;
        }

        return result;
    }
}