package com.urbancode.air.plugin.scm

import com.urbancode.air.*

import java.util.Date;
import java.util.regex.Matcher

public class SCMCheckout extends SCMStep {
    
    //**************************************************************************
    // CLASS
    //**************************************************************************

    //**************************************************************************
    // INSTANCE
    //**************************************************************************
    
    // Clientspec Creation Variables
    boolean createNewClientSpec
    boolean isUsingTemplate
    String templateName
    String clientspec
    
    // Client Info Variables
    final def ROOT_TOKEN        = 'Root:'
    final def VIEW_TOKEN        = 'View:'
    final def SERVER_DATE_TOKEN = 'Server date:'
    final def PERFORCE_DATE     = new java.text.SimpleDateFormat("yyyy/MM/dd HH:mm:ss")
    Double timezoneDifference
    def clientInfo
    
    // Sync Variables
    boolean forceSync
    boolean preserveUnlabeledFiles
    boolean doNotUpdateHaveList
    String label
    Date date
    String depotPaths
    
    public def execute() {
        this.login()
        
        if (createNewClientSpec || isUsingTemplate) {
            createClientSpec()
        }
        
        getClientspecInfo()
        
        sync()
        
        return depotPaths
    }
    
    public void createClientSpec() {
        if (isUsingTemplate) {
            def command = this.createP4BaseCommand()
            command << 'client' << '-o' << '-t' << templateName << client
            ch.runCommand("Getting Clientspec Template", command) { proc ->
                proc.out.close() // close stdin
                proc.consumeProcessErrorStream(out)
                clientspec = proc.text
            }
        }
        
        // modify the root in the clientspec config to reflect the directoryOffset if
        // one was specified in the command
        clientspec = modifyClientSpecConfigRoot(clientspec, dirOffset)
        
        println("Creating Clientspec:\n" + clientspec + "\n")
        
        // run the process
        def createClientCommand = createP4BaseCommand()
        createClientCommand << 'client' << '-i'
        ch.runCommand('Create Clientspec', createClientCommand) { proc ->
            proc.withWriter{ it << clientspec} // pipe clientspec to child process stdIn and close the stream afterwards
            proc.consumeProcessOutput(out, out)
            //proc.out.close()
        }
    }
    
    public void getClientspecInfo() {
        def clientInfoCmd = createP4BaseCommand()
        clientInfoCmd << 'client' << '-o'
      
        ch.runCommand('Geting Clientspec Info', clientInfoCmd) { proc ->
           proc.consumeProcessErrorStream(out)
           proc.in.withReader{ clientInfo = readClientInfo(it) } // read the client info from stdout
        }
      
        println("Found client root: " + clientInfo.rootPath)
        println("Found client depot paths: ")
        StringBuilder builder = new StringBuilder()
        int counter = 0
        for (depoPath in clientInfo.depoPaths) {
            println("\t${depoPath}")
            
            if (counter == 0) {
                builder.append(depoPath)
            }
            else {
                builder.append("," + depoPath)
            }
            counter++
        }
        
        depotPaths = builder.toString()
      
        //
        // timeDiff
        //
        def serverInfoCmd = createP4BaseCommand()
        serverInfoCmd << 'info'
        ch.runCommand('Getting P4 Server Info', serverInfoCmd) { proc ->
            proc.consumeProcessErrorStream(System.out)
            proc.in.withReader{ timezoneDifference = readServerInfo(it) } // read the p4server info from stdout
        }
    }
    
    public void sync() {
        def syncCmd = createP4BaseCommand()
        syncCmd << 'sync'
        if (forceSync) {
            syncCmd << '-f'
        }
        else if (doNotUpdateHaveList) {
            syncCmd << '-p'
        }
        
        if (label) {
            if (preserveUnlabeledFiles) {
                syncCmd << "@$label,@$label"
            }
            else {
                syncCmd << "@$label"
            }
        }
        else if (date != null) {
            // take server date and format it using the p4 server timezone
            def commandDate = date
        
            // adjust date for difference between air-agent and p4-server
            if (timezoneDifference) {
                def p4Cal = Calendar.instance
                p4Cal.time = commandDate
                
                // Timezone Difference is in hours and can be a decimal like 1.5
                p4Cal.add(Calendar.HOUR_OF_DAY, (int)timezoneDifference)
                int minDiff = (int)(timezoneDifference * 60) % 60
                if (minDiff) {
                    p4Cal.add(Calendar.MINUTE, minDiff)
                }
                commandDate = p4Cal.time
            }
        
            syncCmd << "@${PERFORCE_DATE.format(commandDate)}"
        }
        
        ch.runCommand("Performing P4 Sync", syncCmd)
    }
    
    /**
    * replace path in a configspec with given path
    */
    def modifyClientSpecConfigRoot = {def config, def newRoot ->
        if (!config) {
            return config
        }
        else if (!newRoot) {
           return config.replaceAll(/(?mi)Root:.*$/, Matcher.quoteReplacement("Root: ${new File('.').canonicalPath}"))
        }
        return config.replaceAll(/(?mi)Root:.*$/, Matcher.quoteReplacement("Root: ${new File('.', newRoot).canonicalPath}"))
    }
   
    /**
    * @param base the base time in milliseconds since Epoc
    * @param target the target time in milliseconds since Epoc
    * @return the number of hours between the two times (rounded to nearest half-hour)
    */
    public double getHourDifference(long base, long target) {
        double difference = (double)(base - target) / (1000*60*60) // difference in hours
        return Math.round(difference / 0.5) * 0.5                  // round to nearest 1/2 hour
    }
  
    /**
    *  read stdout of get-client-info afor the workspacepath and depoPathArray
    *  @param br the buffered reader of the p4 command output
    *  @return a map containing 'rootPath':String and 'depoPaths':String[]
    */
    def readClientInfo = { br ->
        def result = [rootPath: null, depoPaths: []]
  
        String line = null
        while( (line = br.readLine()) != null )  {
            if (line.startsWith(ROOT_TOKEN)) {
                println "Found 'Root' section of Client Spec"
                result.rootPath = line.substring(ROOT_TOKEN.length()).trim()
            }
            else if (line.startsWith(VIEW_TOKEN)) {
                println "Found 'View' section of Client Spec"
                // after hitting View section, read until the end of file
                // we experienced a problem where there was a empty string line that did not take us into this inner
                // while 
                while ((line = br.readLine()?.trim()) != null) { // read until end of file
                    // match first token '//....' where ... has no white-space OR '"//...."' where ... has no quote
                    def path = line.find(~'((?:"//[^"]+)|(?://\\S+))') { match, path -> return path }
                    if (path) {
                        println "Found depot path: ${path}"
                        result.depoPaths << path
                    }
                }
            }
        }
        return result
    }

    /**
    * read the timezone offset between client and server
    * @param the server info output
    * @return the number of hours difference between local time and the server time-stamp (as a double)
    */
    def readServerInfo = { br ->
       //timezoneDifference is in hours and can a decimal like 1.5
       double timezoneDifference = 0
       long currentTime = System.currentTimeMillis()

       def serverDateLine = br.readLines().find{it.startsWith(SERVER_DATE_TOKEN) && it.length() > 20}
       if (serverDateLine) {
           // TODO the displayed date-format for perforce appears to be
           //   'yyyy/MM/dd HH:mm:ss Z z' i.e. '2008/08/18 16:02:19 -0400 EDT'
           //   which we could use more directly to get the hr offset and/or the timezone?
           def serverDate = PERFORCE_DATE.parse(serverDateLine.substring(13,32))
           timezoneDifference = getHourDifference(serverDate.getTime(), currentTime)
       }
  
       // print the results
       println("Time difference between the server and the client is ${timezoneDifference} hours!");
       return timezoneDifference
    }
}