package com.urbancode.air.plugin.clearcasebasesnapshot

import groovy.xml.StreamingMarkupBuilder

import java.text.SimpleDateFormat

import org.apache.http.HttpResponse
import org.apache.http.client.HttpClient
import org.apache.http.client.methods.HttpPost
import org.apache.http.entity.StringEntity

import com.urbancode.air.*
import com.urbancode.commons.httpcomponentsutil.HttpClientBuilder
import com.urbancode.commons.util.IO

public class ClearCaseHelper {

    /*--------------------------------------------------------------------------------
     *  Class
     --------------------------------------------------------------------------------*/
    static protected final String REPO_TYPE = 'ClearCase'
    static final String NO_VERSION = "no version selected in configuration specification"
    static final def GMT = TimeZone.getTimeZone("GMT")
    static final def CC_DATE_FORMAT = new SimpleDateFormat("dd-MMM-yy.HH:mm:ss")
    static final def CC_DATE = new SimpleDateFormat("yyyyMMdd.HHmmss")
    static final def AIR_DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.S Z")
    static {
        CC_DATE_FORMAT.timeZone = GMT
        AIR_DATE_FORMAT.timeZone = GMT
    }

    /**
     * Converts an Ant-style wild-mat string into an equivalent java.util.regex.Pattern
     *   ? is a single char wildcard
     *   * is a wildcard matches any string (but does not cross directories)
     *   ** is a wildcard which matches any string and can cross any number of directories
     */
    static public def convertToPattern( def antWildMat) {
        // trim leading / character from pattern
        def pattern = antWildMat.startsWith('/') ? antWildMat : '/'+antWildMat

        // deal with special regex-characters that should be interpreted as literals
        '\\.+[]^${}|()'.toCharArray().each{ c ->
            pattern = pattern.replace(''+c, '\\'+c)
        }
        pattern = pattern.replace('?', '.') // ? is a single-char wildcard

        // deal with ant-style wildcards
        StringBuffer result = new StringBuffer()
        def m = (pattern =~ '\\*\\*/|\\*\\*|\\*')
        while (m) {
            def token = m.group()
            def replacement
            if (token == '**/') {
                replacement = '.*(?<=/)'
            }
            else if (token == '**') {
                replacement = '.*'
            }
            else {
                replacement = '[^/]*'
            }
            m.appendReplacement(result, java.util.regex.Matcher.quoteReplacement(replacement))
        }
        m.appendTail(result)
        return ~(result.toString())
    }

    /*--------------------------------------------------------------------------------
     * Instance
     --------------------------------------------------------------------------------*/
    def commandPath
    def viewPath
    File workDirFile = null
    Date startDate = null
    Date endDate = null
    def vobTagRoot
    def loadRuleList = new ArrayList<String[]>()
    def ch
    def userExcludes
    def fileFilters
    def label
    def message
    def globalLabels
    def labelVobNames = []
    def includeFiles = []
    def excludeFiles = []
    def apTool
    def changeSets = [] //list of arrays [id,user,message,date,fileSet[]]
    def useNco
    def qpUseNco
    def strategy
    def viewName
    def useTags
    def hostname
    def globStorPath
    def hostStorPath
    def checkNewConfigSpec
    Date date = null
    def configSpec
    def failIfNoVersion
    String tempConfigSpec
    def ptime
    def tmode
    def changesUrl
    def srcName

    public ClearCaseHelper(AirPluginTool apTool, Map<String,String> props) {
        this.apTool = apTool
        srcName = props['source']
        commandPath = props['source/repo/commandPath']
        viewPath = props['source/storageDir']
        strategy = props['source/strategy']
        viewName = props['source/viewName']
        failIfNoVersion = Boolean.valueOf(props['source/failIfNoVersion'])

        if ("alreadyExists".equals(strategy)) {
            workDirFile = new File(viewPath)
            //workDir
            /* if (!workDirFile.exists()) {
             throw new IllegalStateException("View Path doe not exist: " + viewPath)
             }*/
        }
        else {
            workDirFile = new File(viewName).getAbsoluteFile()
        }

        DateParser dateParser = new DateParser()
        startDate = dateParser.parseDate(props['startDate'])
        endDate = dateParser.parseDate(props['endDate'])

        vobTagRoot = props['source/tagRoot']?:""

        //load rules
        String[] loadRuleTemp = props['source/loadRules'].split('\n')
        loadRuleList = new ArrayList<String[]>()

        loadRuleTemp.each { loadRule ->
            int i = 0
            String [] tempLoadRuleArray = loadRule.split(':')
            String [] loadRuleArray = new String[4]
            for (i = 0; i< tempLoadRuleArray.length && i < 4; i++) {
                loadRuleArray[i] = tempLoadRuleArray[i]
            }
            for (i; i < 4; i++ ) {
                loadRuleArray[i] = null
            }
            loadRuleList << loadRuleArray
        }

        //view path
        if (viewPath == null || viewPath.trim().length() == 0) {
            throw new IllegalStateException("View path is not configured.")
        }

        //commandpath
        if (commandPath == null || commandPath.trim().length() == 0) {
            commandPath = "cleartool"
        }

        ch = new CommandHelper(workDirFile)

        userExcludes = props['source/excludeUsers']
        if (userExcludes) {
            userExcludes = userExclude.readLines().collect{it.trim()}.findAll{it}
        }

        //--------------------------------------------------------------------------------
        //file excludes
        //--------------------------------------------------------------------------------
        fileFilters = props['fileFilters']
        if (fileFilters) {
            fileFilters.readLines().collect{it.trim()}.findAll{it}.each{filter ->
                def pattern = convertToPattern(filter.substring(1))
                if (filter.startsWith('+')) {
                    includeFiles << pattern
                }
                else if (filter.startsWith('-')) {
                    excludeFiles << pattern
                }
                else {
                    // not a valid expression
                }
            }
        }

        label = props['label']
        message = props['message']
        labelVobNames = props['labelVobs']?.readLines()
        globalLabels = Boolean.valueOf(props['globalLabels'])
        useNco = Boolean.valueOf(props['source/useNco'])
        changesUrl = props['changesUrl']
        useTags = Boolean.valueOf(props['source/useTags'])
        hostname = props['source/hostname']
        globStorPath = props['source/globStorPath']
        hostStorPath = props['source/hostStorPath']
        checkNewConfigSpec = Boolean.valueOf(props['source/checkNewConfigSpec'])
        configSpec = props['source/configSpec']
        date = dateParser.parseDate(props['date'])
        ptime = Boolean.valueOf(props['source/ptime'])
        tmode = props['source/tmode']
    }

    //--------------------------------------------------------------------------------
    //determine if it is an acceptable revision closure not testing user or file excludes
    //--------------------------------------------------------------------------------

    public boolean isRevisionAcceptable(def changeSet) {
        boolean result = false
        String fileName = changeSet['file']
        String labels = changeSet['label']
        String event = changeSet['event']
        Date revisionDate = changeSet['date']
        String action = changeSet['action']


        // find the rule for this revision
        println("Checking if revision is acceptable")
        if (changeSet['file'] != null) {
            int i = 0
            loadRuleList.each { loadRule ->
                // check and make sure the revision is in the date interval we need
                if (endDate == null || (revisionDate == null || endDate.after(revisionDate))) {
                    // now make sure that revision belongs to one of the paths specified in
                    // the list of changelog rules
                    if (fileName.indexOf(loadRule[0]) != -1 && fileName.indexOf(loadRule[1]) != -1) {
                        // make sure that the revision branch matches the changelog branch for that path
                        if (loadRule[2] == null || fileName.indexOf(loadRule[2]) != -1) {
                            // check if we have a mkbranch action that does not affect code
                            if ("mkbranch".equals(action) && loadRule[2] != null &&
                                    (fileName().endsWith(loadRule[2]) || fileName().endsWith(loadRule[2] + "\\0") || fileName().endsWith(loadRule[2] + "/0") )) {
                                println "Found a maintenance mkbranch action"
                            }
                            // make sure that label specified in the changelog rule
                            // is either in the labels or event field of the revision
                            // and it needs to be in the event field for mklabel and rmlabel events
                            else if (loadRule[3] == null) {
                                println("No label specified")
                                result = true
                            }
                            else if ("mklabel".equals(action) || "rmlabel".equals(action)) {
                                println("Label was made or removed")
                                if (event != null && (event.indexOf("\"" + loadRule[3] + "\"") != -1)) {
                                    println("A matching label was made or removed")
                                    result = true
                                }
                                else {
                                    println("Label did not match the changelog label for this path")
                                    println("loadRule[3]:" + loadRule[3])
                                }
                            }
                            else if (labels != null && labels.indexOf(loadRule[3]) != -1) {
                                println("Found a change that matched our label")
                                result = true
                            }
                            else {
                                println("A label existed but did not match our load rules")
                                println("loadRule[3]:" + loadRule[3])
                                if (labels != null) {
                                    println("labels:" + labels)
                                }
                            }
                        }
                        else {
                            println("revision branch does not match the changelog branch for this path")
                            println("loadRule[2]:" + loadRule[2])
                        }
                    }
                    else {
                        println("revision does not belong to one of the paths specified in the list of changelog rules")
                        println("fileName:" + fileName)
                        println("loadRule[0]:" + loadRule[0])
                        println("loadRule[1]:" + loadRule[1])
                    }
                }
                else {
                    println("Revision is not in our interval")
                    if (endDate != null) {
                        println("endDate:" + endDate)
                    }
                    if (revisionDate != null) {
                        println("revisionDate:" + revisionDate)
                    }
                }
            }
        }
        else {
            println("File name is null")
        }

        println("Is revision acceptable " + result)
        return result
    }

    //--------------------------------------------------------------------------------
    //determine if it is an acceptable user
    //--------------------------------------------------------------------------------

    public boolean isUserAcceptable(def changeSet) {
        boolean result = true
        String fileName = changeSet['file']
        String labels = changeSet['label']
        String event = changeSet['event']
        Date revisionDate = changeSet['date']
        String action = changeSet['action']
        String user = changeSet['user']

        if (userExcludes) {
            result = !userExcludes.contains(user)
        }
        println("Is user acceptable : " + result)

        return result
    }

    //--------------------------------------------------------------------------------
    //determine if it is an acceptable user
    //--------------------------------------------------------------------------------

    public boolean isFileAcceptable(def changeSet) {
        boolean result = true
        String fileName = changeSet['file']
        String labels = changeSet['label']
        String event = changeSet['event']
        Date revisionDate = changeSet['date']
        String action = changeSet['action']

        if (excludeFiles) {
            result = excludeFiles.find{fileName ==~ it} != null
        }

        println("Is file acceptable : " + result)
        return result
    }

    //--------------------------------------------------------------------------------
    // get the changesets from the output
    //--------------------------------------------------------------------------------
    /* example changelog output
     Action:checkin
     Date:20070816.205608
     User:svc-bld
     Object:/vobs/dsm_proto///build.xml@@/main/dsm_eolian-dev/40
     Labels:
     Event:create version
     Comment:
     --urbancodesplit-->
     Action:checkin
     Date:20070816.205610
     User:svc-ble
     Object:/vobs/dsm_proto///build.xml@@/main/dsm_eolian-dev/41
     Labels:label
     Event:create version
     Comment:kensie
     --urbancodesplit-->

    */
    public void parseOutputStream(def out) {

        //first we should split on --------->\n<---------
        println("Output")
        println("--------------------------------------------------------------------")
        println(out.toString())
        println("--------------------------------------------------------------------")

        def revsTemp = out.toString().split("--urbancodesplit-->")
        def revs = []
        //remove trailing newline
        for (int j = 0; j < revsTemp.length-1; j++) {
            revs << revsTemp[j]
        }
        revs.each { revString ->
            //next we should split on \n
            def rev = [:]
            rev['comment'] = ''

            if (revString.readLines().size() < 6) {
                //we have to have 6 lines here to get any useful information out of a revision
                //if not we should skip it
                println("Skipping because not enough fields are present")
            }
            else {
                revString.eachLine { line ->
                    def index = line.indexOf(':')
                    def type = index > 0 ? line.substring(0, index) : line
                    def data = index > 0 ? line.substring(index + 1) : line
                    if (data) {
                        switch (type) {
                            case "Action":
                                rev['action'] = data
                                break
                            case "Date":
                                rev['date'] = CC_DATE.parse(data)
                                break
                            case "User":
                                rev['user'] = data
                                break
                            case "Object":
                                rev['file'] = data
                                break
                            case "Labels":
                                rev['label'] = data
                                break
                            case "Event":
                                rev['event'] = data
                                break
                            case "Defect":
                            case "Project":
                                break
                            case "Comment":
                            default:
                                rev['comment'] += '\n' + data
                                break
                        }
                    }
                }

                //only add to the rev list if we determine it is acceptable
                if (isRevisionAcceptable(rev) && isUserAcceptable(rev) && isFileAcceptable(rev)) {
                    changeSets.add(rev)
                }
            }
        }
    }

    //--------------------------------------------------------------------------------
    //Upload the changesets
    //--------------------------------------------------------------------------------
    public void uploadChangeSets() {
        if (changeSets) {
            def xmlBuilder = new StreamingMarkupBuilder()
            xmlBuilder.encoding = "UTF-8"
            def xml = xmlBuilder.bind {
                mkp.xmlDeclaration()
                'change-log'{
                    changeSets.each { changeSet ->
                        'change-set'() {
                            'user'(stripInvalidXMLChars(changeSet['user']))
                            'date'(AIR_DATE_FORMAT.format(changeSet['date']))
                            if (changeSet['comment']) {
                                'comment'(stripInvalidXMLChars(changeSet['comment']))
                            }
                            if (changeSet['label']) {
                                'module'(changeSet['label'])
                            }
                            'repository-type'(stripInvalidXMLChars(REPO_TYPE))
                            'repository-id'(System.getenv("REPOSITORY_ID"))
                            'source-config-id'(System.getenv("SOURCE_CONFIG_ID"))
                            'file-set' {
                                'file'(
                                    'change-type':stripInvalidXMLChars(changeSet['action']),
                                    stripInvalidXMLChars(changeSet['file']))
                            }
                        }
                    }
                }
            }

            sendPostRequest(xml.toString())
        }
        else {
            println "No changes detected"
        }
    }

    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(directory, "xmlOut.xml")
           xmlOut << xml
       }
       else {
           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))

           HttpClientBuilder builder = new HttpClientBuilder()
           builder.setTrustAllCerts(true)
           HttpClient client = builder.buildClient()

           HttpResponse 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}")
           }
       }
    }

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

    /**
     * Strip any invalid xml characters (such as backspace) from the value.
     * @param value the value to strip (can be null)
     * @return the sanitized value (null if and only if value was null)
     */
    protected String stripInvalidXMLChars(String value) {

        // legal xml chars: #x9 | #xA | #xD | #x20-#xD7FF | #xE000-#xFFFD | #x10000-#x10FFFF
        def legalChars = ['\\u0009', '\\u000A', '\\u000D', '\\u0020-\\uD7FF', '\\uE000-\\uFFFD', '\\u10000-\\u10FFFF']
        def illegalChars = "[^${legalChars.join()}]"
        return !value ? value : value.replaceAll(illegalChars, '?')
    }

    //--------------------------------------------------------------------------------
    //Execute the changelog function for clearcase for each loadRule
    //--------------------------------------------------------------------------------
    public void getChangeLogStep(def isQp) {
        loadRuleList.each { loadRule ->
            String fileName = loadRule[0]
            if (loadRule[1]) {
                fileName += File.separator + loadRule[1]
            }
            if (apTool.isWindows && vobTagRoot) {
                fileName = vobTagRoot + File.separator + fileName
            }
            while (fileName.startsWith("\\") || fileName.startsWith("/")) {
                fileName = fileName.substring(1)
            }

            String branchName = loadRule[2]
            String labelName = loadRule[3]

            println("Getting changelog for " + fileName + " in working directory " +
                    workDirFile.getCanonicalPath())
            File file = new File(workDirFile, fileName)
            ByteArrayOutputStream out = new ByteArrayOutputStream()

            if (file.exists()) {
                // get the lshistory just for the directory
                def cmdArgs = [commandPath, 'lshistory']

                if (useNco || (isQp && qpUseNco)) {
                    cmdArgs << '-nco'
                }

                cmdArgs << '-r'

                if (labelName) {
                    cmdArgs << "-minor"
                }

                cmdArgs << '-branch'

                if (branchName) {
                    cmdArgs << branchName
                }
                else {
                    cmdArgs << 'main'
                }

                cmdArgs << '-since'
                cmdArgs << CC_DATE_FORMAT.format(startDate)+"UTC+00:00"
                cmdArgs << '-fmt'
                cmdArgs << "Action:%o\\nDate:%Nd\\nUser:%u\\nObject:%n\\nLabels:%l\\nEvent:%e\\nComment:%Nc\\n--urbancodesplit-->\\n"
                cmdArgs << fileName

                ch.runCommand("Getting history for directory (recursive)", cmdArgs) { proc ->
                    proc.out.close()
                    proc.waitForProcessOutput(out, System.out)
                }

                //parse the output adding changesets as appropriate to the changeSets array
                parseOutputStream(out)
            }
            else {
                println("Did not find file for changelog at " + file.getAbsolutePath())
            }
        }
    }

    //--------------------------------------------------------------------------------
    // Upload last updated time to the server
    //--------------------------------------------------------------------------------
    public void uploadLastUpdatedTime() {
        if (!changeSets) {
            println("No changes to use for last time!")
        }
        else {
            Date lastUpdatedTime = null
            changeSets.each { changeSet ->
                if (lastUpdatedTime == null || changeSet['date'].compareTo(lastUpdatedTime) > 0) {
                    lastUpdatedTime = changeSet['date']
                }
            }

            apTool.setOutputProperty("job/latest.change.time.${srcName}", String.valueOf(lastUpdatedTime.time))
            apTool.storeOutputProperties()
            println "Latest Revision Date: " + lastUpdatedTime
        }
    }

    //--------------------------------------------------------------------------------
    // Create label type
    //--------------------------------------------------------------------------------
    public void makeLabelTypeCommand(String vobName) {

        def cmdArgs = [commandPath, 'mklbtype']

        if (message == null) {
            cmdArgs << "-nc"
        }
        else {
            cmdArgs << "-c"
            cmdArgs << message
        }

        if (globalLabels) {
            cmdArgs << "-global"
            cmdArgs << "-acquire"
        }
        else {
            cmdArgs << "-ordinary"
        }

        cmdArgs << label + "@" + vobName

        ch.runCommand("Creating label type", cmdArgs)
    }

    //--------------------------------------------------------------------------------
    //  Get the vob name list for labeling
    //--------------------------------------------------------------------------------
    public List getVobNameList() {
        List vobNameList = new ArrayList()
        if (labelVobNames == null || labelVobNames.length == 0) {
            loadRuleList.each { loadRule ->
                String vobName = getVobFromPath(loadRule[0])
                if (vobName != null && vobName.trim().length() > 0 && !vobNameList.contains(vobName.trim())) {
                    vobNameList.add(vobName.trim())
                }
            }
        }
        else {
            labelVobNames.each { labelVobName ->
                if (labelVobName != null && labelVobName.trim().length() > 0 && !vobNameList.contains(labelVobName.trim())) {
                    vobNameList.add(labelVobName.trim())
                }
            }
        }
        return vobNameList
    }

    //--------------------------------------------------------------------------------
    //  Get the vob from path
    //--------------------------------------------------------------------------------
    private String getVobFromPath(String path) {
        String result = null

        // strip any / or \ at the beginning of the path
        while (path.startsWith("\\") || path.startsWith("/")) {
            path = path.substring(1)
        }
        if (path != null && path.length() > 0) {
            // now look for the first / or \ and strip anything after to get the name
            // of the vob.
            int index = path.indexOf('\\')
            if (index != -1) {
                result = path.substring(0, index)
            }
            else if ((index = path.indexOf('/')) != -1) {
                result = path.substring(0, index)
            }
            else {
                result = path
            }
        }
        return result
    }

    //--------------------------------------------------------------------------------
    //  Apply Labels
    //--------------------------------------------------------------------------------
    public void applyLabels(List vobNameList) {
        loadRuleList.each { loadRule ->
            String vobName = loadRule[0]
            String dirName = loadRule[1]
            if (vobName != null && vobName.trim().length() > 0) {
                println "Trying to label " + dirName
                makeLabelCommand(dirName.trim(), true)
                //next the label needs to be applied to the vob
                applyLabelToVob(vobName)
            }
        }
    }

    //--------------------------------------------------------------------------------
    //  apply label command
    //--------------------------------------------------------------------------------
    void makeLabelCommand(String dirToLabel, boolean isRecurse) {

        def cmdArgs = [commandPath, 'mklabel', '-replace']


        if (isRecurse) {
            cmdArgs << '-recurse'
        }

        if (message == null) {
            cmdArgs << '-nc'
        }
        else {
            cmdArgs << '-c'
            cmdArgs << message
        }

        cmdArgs << label
        cmdArgs << dirToLabel

        ch.runCommand("Applying label to directory " + dirToLabel, cmdArgs)
    }

    //--------------------------------------------------------------------------------
    //  apply label to vob
    //--------------------------------------------------------------------------------
    void applyLabelToVob(String vobName) {

        def cmdArgs = [commandPath, 'mklabel', '-replace']

        if (message == null) {
            cmdArgs << '-nc'
        }
        else {
            cmdArgs << '-c'
            cmdArgs << message
        }

        cmdArgs << label
        cmdArgs << vobName

        ch.runCommand("Applying label to vob " + vobName, cmdArgs)
    }

    //--------------------------------------------------------------------------------
    //  populate command
    //--------------------------------------------------------------------------------
    public void populate() {
        def exists = null
        if (!"alreadyExists".equals(strategy)) {
            exists = checkExistingView()
        }

        if (exists == null && "alreadyExists".equals(strategy)) {
            updateView()
        }
        else if (exists) {
            if ("everyTime".equals(strategy)) {
                removeView()
                deleteViewDir()
                createView()
                setupConfigSpec()//what does this do
            }
            else if ("notExists".equals(strategy)) {
                setupConfigSpec()
            }
        }
        else {
            if ("everyTime".equals(strategy) || "notExists".equals(strategy)) {
                createView()
                setupConfigSpec()
            }
            else if (!"alreadyExists".equals(strategy)) {
                throw new Exception("Unknown strategy: " + strategy)
            }
        }
    }

    //--------------------------------------------------------------------------------
    //  checExisting view
    //--------------------------------------------------------------------------------
    private boolean checkExistingView() {
        //use lsview to establish if the view exists
        boolean result = true
        def cmdArgs = [commandPath, 'lsview', '-storage', viewPath + File.separator + viewName]
        try {
            ch.runCommand("Checking if the view exists", cmdArgs)
        }
        catch (Exception e) {
            //the command failed means the view doesn't exist
            result = false
        }

        //we need to check if there is a tag for this viewName yet
        if (useTags && !result) {
            cmdArgs = [commandPath, 'lsview', viewName]
            try {
                ch.runCommand("Checking if there is a tag for viewName", cmdArgs)
            }
            catch (Exception e) {
                //the command failed means the view doesn't exist
                result=false
            }
        }
        return result
    }

    //--------------------------------------------------------------------------------
    //  updateView
    //--------------------------------------------------------------------------------
    private void updateView(){
        println("Updating view")
        def vobNameList = getVobNameList()
        if (vobNameList != null && vobNameList.size() > 0) {
            vobNameList.each { vobName ->
                populateVob(vobName)
            }
        }
        else {
            populateVob(null)
        }
    }

    //--------------------------------------------------------------------------------
    //  removeView
    //--------------------------------------------------------------------------------
    private void removeView() {
        File viewParentDir = workDirFile.getParentFile()
        if (!viewParentDir.isDirectory()) {
            throw new Exception("Not a directory!")
        }
        def cmdArgs = [commandPath, 'rmview', '-force', viewPath + File.separator + viewName]
        CommandHelper tempCH = new CommandHelper(viewParentDir)
        tempCH.runCommand("Removing view : " + viewName, cmdArgs)
    }

    //--------------------------------------------------------------------------------
    //  delete view dir
    //--------------------------------------------------------------------------------
    private void deleteViewDir() {
        if (workDirFile.isDirectory()) {
            workDirFile.deleteDir()
        }
    }

    //--------------------------------------------------------------------------------
    //  createView
    //--------------------------------------------------------------------------------
    private void createView() {
        def cmdArgs = [commandPath, 'mkview']
        if (useTags) {
            cmdArgs << '-tag'
            cmdArgs << viewName
        }
        cmdArgs << '-snapshot'
        cmdArgs << '-vws'
        cmdArgs << viewPath + File.separator + viewName

        if (hostname) {
            cmdArgs << '-host'
            cmdArgs << hostname
        }
        if (hostStorPath) {
            cmdArgs << '-hpath'
            cmdArgs << hostStorPath
        }
        if (globStorPath) {
            cmdArgs << '-gpath'
            cmdArgs << globStorPath
        }

        if (ptime) {
            cmdArgs << '-ptime'
        }

        cmdArgs << '-tmode'
        cmdArgs << tmode
        cmdArgs << workDirFile.getAbsolutePath()
        deleteViewDir()
        File viewParentDir = workDirFile.getParentFile()
        if (viewParentDir == null) {
            throw new Exception("No Parent Directory for : " + workDirFile.getAbsolutePath() + ":" + viewParentDir.getAbsolutePath())
        }
        if (!viewParentDir.isDirectory()) {
            throw new Exception("Not a directory!")
        }
        CommandHelper tempCH = new CommandHelper(viewParentDir)
        tempCH.runCommand("Creating view : " + viewName, cmdArgs)
    }

    //--------------------------------------------------------------------------------
    //   setconfigspec
    //--------------------------------------------------------------------------------
    private void setupConfigSpec() {
        File parent = workDirFile.getParentFile()
        if (parent == null || !parent.exists()) {
            throw new IllegalStateException("Working dir does not have a parent!")
        }

        if (!configSpec) {
            throw new IllegalStateException("Please specify a config spec for the view!")
        }

        if (!workDirFile.exists()) {
            throw new IllegalStateException("view was not properly created!")
        }

        File configSpecFile = createViewConfigSpec()
        if (!configSpecFile.exists()) {
            throw new IllegalStateException("config spec was not properly created!")
        }

        def error = false
        def cmdArgs = [commandPath, 'setcs', '-force', configSpecFile.getCanonicalPath()]
        ch.runCommand("Setting config spec", cmdArgs) { proc ->
            proc.out.close()
            StringBuffer buffer = new StringBuffer()
            proc.waitForProcessOutput(buffer, buffer)
            proc.waitFor()
            String output = buffer.toString()
            println output
            if (output && output.contains(NO_VERSION)) {
                error = true
            }
        }

        if (failIfNoVersion && error) {
            System.out.flush()
            System.err.println "An error occurred during checkout. Some files were not able to be checked out because no version " +
                    "was selected in the configuration specification"
            System.exit(1)
        }

        if (checkNewConfigSpec) {
            String catCsOutput = new String()
            String errorOutput = new String()
            StringBuffer oStream = new StringBuffer(catCsOutput)
            StringBuffer eStream = new StringBuffer(errorOutput)
            println("Comparing view config spec with target ...")
            File viewConfigSpecFile = new File(workDirFile, "updatedViewConfigSpec.txt")
            cmdArgs = [commandPath, 'catcs']
            ch.runCommand("Catting config spec", cmdArgs) { proc ->
                proc.out.close()
                proc.waitForProcessOutput(oStream, eStream)
            }
            println("CS: " + oStream)
            println("ERROR: " + eStream.toString())
            println("Finished catting")
            println("ConfigSpec:")
            println(tempConfigSpec)
            if (!tempConfigSpec.equals(oStream.toString())) {
                throw new Exception("The new config spec does not equal the Config spec specified!")
            }
        }
    }

    //--------------------------------------------------------------------------------
    //   populateVob
    //--------------------------------------------------------------------------------
    private void populateVob(String vobName) {
        def cmdArgs = [commandPath, 'update', '-overwrite', '-force', "-add_loadrules"]
        if (vobName) {
            cmdArgs << vobName
        }

        def error = false
        ch.runCommand("Populating Vob named : " + vobName, cmdArgs) { proc ->
            proc.out.close()
            StringBuffer buffer = new StringBuffer()
            proc.waitForProcessOutput(buffer, buffer)
            proc.waitFor()
            String output = buffer.toString()
            println output
            if (output && output.contains(NO_VERSION)) {
                error = true
            }
        }

        if (failIfNoVersion && error) {
            System.out.flush()
            System.err.println "An error occurred during checkout. Some files were not able to be checked out because no version " +
                    "was selected in the configuration specification"
            System.exit(1)
        }
    }

    //--------------------------------------------------------------------------------
    //   createViewConfigSpec
    //--------------------------------------------------------------------------------
    private File createViewConfigSpec() {
        BufferedWriter writer = null
        File cfgFileOut = null
        tempConfigSpec = new String(configSpec)

        cfgFileOut = new File(workDirFile, "confSpecFile.txt")
        writer = new BufferedWriter(new FileWriter(cfgFileOut))

        // start processing special tokens
        println "ClearCase config spec pre processing!"
        int start, end
        while ((start = tempConfigSpec.indexOf('$%')) != -1) {
            end = tempConfigSpec.indexOf('%$')
            if (end == -1) {
                throw new Exception("Unclosed token in config spec: " + tempConfigSpec)
            }

            if (!label) {
                //replace $%*%$ with *
                tempConfigSpec = tempConfigSpec.substring(0,start) +
                        tempConfigSpec.substring(start+2, end) +
                        tempConfigSpec.substring(end+2, tempConfigSpec.length())
            }
            else {
                //replace $%*%$ with label
                tempConfigSpec = tempConfigSpec.substring(0,start) + label +
                        tempConfigSpec.substring(end+2, tempConfigSpec.length())
            }
        }

        if (!label && date != null) {
            String dateStr = "-time " + CC_DATE_FORMAT.format(date) + "UTC+00:00"

            //replace $[time.token] wiht the date string
            while ((start = tempConfigSpec.indexOf('$[time.token]')) != -1) {
                tempConfigSpec = tempConfigSpec.substring(0, start) +
                        dateStr +
                        tempConfigSpec.substring(start + '$[time.token]'.length(), tempConfigSpec.length())
            }
        }
        else {
            //using a label or no date spec
            //strip $[time.token] from config-spec
            while ((start = tempConfigSpec.indexOf('$[time.token]')) != -1) {
                tempConfigSpec = tempConfigSpec.substring(0, start) +
                        tempConfigSpec.substring(start + '$[time.token]'.length(), tempConfigSpec.length())
            }
        }

        // end processing special tokens
        println "ClearCase config spec post processing!"

        tempConfigSpec = fixLineEndings()

        //write the config spec to file
        writer.write(tempConfigSpec, 0, tempConfigSpec.length())
        writer.flush()
        writer.close()

        return cfgFileOut
    }

    //--------------------------------------------------------------------------------
    //   fixLIneEndings
    //--------------------------------------------------------------------------------
    private String fixLineEndings() {
        final char EOL = '\n'
        StringBuilder builder = new StringBuilder()
        BufferedReader reader = new BufferedReader(new StringReader(tempConfigSpec))
        try {
            String line
            while ((line = reader.readLine()) != null) {
                builder.append(line).append(EOL)
            }
        }
        finally {
            reader.close()
        }
        return builder.toString()
    }
}
