package com.urbancode.air.plugin.clearcasebasesnapshot;

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

/* 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:
----->
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
----->
*/

public class ClearCaseHelper {
    
    /*--------------------------------------------------------------------------------
     *  Class
     --------------------------------------------------------------------------------*/
    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
    
    public ClearCaseHelper(AirPluginTool apTool, Map<String,String> props) {
        this.apTool = apTool;
        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);
        }
        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";
        }
        
        if (!workDirFile.exists()) {
            if (workDirFile.isFile()) {
                throw new IllegalArgumentException("Working directory ${workDirFile} is a file!")
            }
            workDirFile.mkdirs()
        }
        ch = new CommandHelper(workDirFile);
        
        userExcludes = props['source/excludeUsers']
        if (userExcludes) {
          userExcludes = userExcludes.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
        System.out.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") )) {
                                    System.out.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) {
                                System.out.println("No label specified");
                                result = true;
                            }
                            else if ("mklabel".equals(action) || "rmlabel".equals(action)) {
                                System.out.println("Label was made or removed");
                                if (event != null && (event.indexOf("\"" + loadRule[3] + "\"") != -1)) {
                                    System.out.println("A matching label was made or removed");
                                    result = true;
                                }
                                else {
                                    System.out.println("Label did not match the changelog label for this path");
                                    System.out.println("loadRule[3]:" + loadRule[3]);
                                }
                            }
                            else if (labels != null && labels.indexOf(loadRule[3]) != -1) {
                                System.out.println("Found a change that matched our label");
                                result = true;
                            }
                            else {
                                System.out.println("A label existed but did not match our load rules");
                                System.out.println("loadRule[3]:" + loadRule[3]);
                                if (labels != null) {
                                    System.out.println("labels:" + labels);
                                }
                            }
                        }
                        else {
                            System.out.println("revision branch does not match the changelog branch for this path");
                            System.out.println("loadRule[2]:" + loadRule[2]);
                        }
                    }
                    else {
                        System.out.println("revision does not belong to one of the paths specified in the list of changelog rules");
                        System.out.println("fileName:" + fileName);
                        System.out.println("loadRule[0]:" + loadRule[0]);
                        System.out.println("loadRule[1]:" + loadRule[1]);
                    }
                }
                else {
                    System.out.println("Revision is not in our interval");
                    if (endDate != null) {
                        System.out.println("endDate:" + endDate);
                    }
                    if (revisionDate != null) {
                        System.out.println("revisionDate:" + revisionDate);
                    }
                }
            }
        }
        else {
            System.out.println("File name is null");
        }
    
        System.out.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)
        }
        System.out.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
        }
        
        System.out.println("Is file acceptable : " + result);
        return result;
    }
    
    //--------------------------------------------------------------------------------
    // get the changesets from the output
    //--------------------------------------------------------------------------------
    public void parseOutputStream(def out) {
        
        //first we should split on --------->\n<---------
        System.out.println("Output");
        System.out.println("--------------------------------------------------------------------")
        System.out.println(out.toString());
        System.out.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
                System.out.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 "File": 
                                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 xml = groovy.xml.DOMBuilder.newInstance().'change-log'{
                changeSets.each{ changeSet ->
                    'change-set'(){
                        //id(changeSet.id)
                        user(changeSet['user'])
                        date(AIR_DATE_FORMAT.format(changeSet['date']))
                        if (changeSet['comment']) {
                            comment(changeSet['comment'])
                        }
                        if (changeSet['label']) {
                            module(changeSet['label'])
                        }
                        'repository-type'('ClearCase')
                        'repository-id'(System.getenv()['REPOSITORY_ID'])
                        'file-set'{
                            file('change-type':changeSet['event'], changeSet['file'])
                        }
                    }
                }
            }
        
            sendPostRequest(xml)
        }
        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.length} 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;
    }
    
    //--------------------------------------------------------------------------------
    //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];
            
            System.out.println("Getting changelog for " + fileName + " in working directory " +
                workDirFile.getCanonicalPath());
            File file = new File(workDirFile, fileName);
            ByteArrayOutputStream out = new ByteArrayOutputStream();
            
            if (file.exists() && file.isDirectory()) {
                // get the lshistory just for the directory
                def cmdArgs = [commandPath, 'lshistory']
                
                if (useNco) {
                    cmdArgs << '-nco';
                }
                
                cmdArgs << '-d';
                
                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.", cmdArgs) { proc ->
                    proc.out.close();
                    proc.waitForProcessOutput(out, System.out);
                }
                
                //parse the output adding changesets as appropriate to the changeSets array
                parseOutputStream(out);
            }
            
            if (file.exists()) {
                // get the lshistory just for the directory
                def cmdArgs = [commandPath, 'lshistory']
                
                if (useNco) {
                    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 files and subdirectories in directory.", cmdArgs) { proc ->
                    proc.out.close();
                    proc.waitForProcessOutput(out, System.out);
                }
                
                //parse the output adding changesets as appropriate to the changeSets array
                parseOutputStream(out);
            }
            else {
                System.out.println("Did not find file for changelog at " + file.getAbsolutePath());
            }
        }
    }
    
    //--------------------------------------------------------------------------------
    // Upload last updated time to the server
    //--------------------------------------------------------------------------------
    public void uploadLastUpdatedTime() {
        if (!changeSets) {
            System.out.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 = 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) {
                System.out.println("Trying to label " + dirName);
                recursivelyLabelDirPath(vobName.trim(), dirName.trim());
            }
            //next the label needs to be applied to the vob
            applyLabelToVob(vobName);
        }
    }
    
    //--------------------------------------------------------------------------------
    // recursivelyLabel Dir Path
    //--------------------------------------------------------------------------------
    void recursivelyLabelDirPath(String vobName, String dirPath) {
        String path = dirPath;

        // strip any / or \ at the beginning of the path
        while (path.startsWith("\\") || path.startsWith("/")) {
            path = path.substring(1);
        }
        String[] dirPathArray = null;
        if (path.indexOf("\\") > 0) {
            dirPathArray = path.split("\\\\");
        }
        else if (path.indexOf("/") > 0) {
            dirPathArray = path.split("/");
        }
        else {
            dirPathArray = new String[1];
            dirPathArray[0] = path;
        }
        
        System.out.println("We found " + dirPathArray.length + " directory paths to label");
        
        if (dirPathArray != null && dirPathArray.length > 0) {
            for (int i = 0; i < dirPathArray.length; i++) {
                String labelPath = vobName;
                for (int j = 0; j <= i; j++) {
                    labelPath += File.separator + dirPathArray[j];
                }
                if (i == dirPathArray.length - 1) {
                     makeLabelCommand(labelPath, true);
                }
                else {
                    makeLabelCommand(labelPath, false);
                }
            }
        }
        else {
            System.out.println("We did not apply a label for " + dirPath);
        }
    }
    
    //--------------------------------------------------------------------------------
    //  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(){
        System.out.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);
            System.out.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);
            }
            System.out.println("CS: " + oStream);
            System.out.println("ERROR: " + eStream.toString());
            System.out.println("Finished catting");
            System.out.println("ConfigSpec:");
            System.out.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;
       int index;
       tempConfigSpec = new String(configSpec);
       
       cfgFileOut = new File(workDirFile, "confSpecFile.txt");
       writer = new BufferedWriter(new FileWriter(cfgFileOut));
       
       // start processing special tokens
       System.out.println("ClearCase config spec pre processing");
       int start, end;
       start = tempConfigSpec.indexOf('$%');
       while (start != -1) {
           end = tempConfigSpec.indexOf('%$');
           
           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());
           }
           start = tempConfigSpec.indexOf('$%');
       }
       
       if (!label && date != null) {
           String dateStr = " -time " + CC_DATE_FORMAT.format(date) + "UTC+00:00";
           
           //replace $[time.token] with the date string
           index = -1
           while ((index = tempConfigSpec.indexOf('$[time.token]')) != -1) {
              tempConfigSpec = tempConfigSpec.substring(0, index) +
                               dateStr +
                               tempConfigSpec.substring(index + '$[time.token]'.length(), tempConfigSpec.length());
           }
       }
       else {
           //using a label or no date spec
           //strip $[time.token] from config-spec
           index = -1
           while ((index = tempConfigSpec.indexOf('$[time.token]')) != -1) {
              tempConfigSpec = tempConfigSpec.substring(0, index) +
                               tempConfigSpec.substring(index + '$[time.token]'.length(), tempConfigSpec.length());
           }
       }
       
       // end processing special tokens
       System.out.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();
    }
}
