package com.urbancode.air.plugin.scm

import java.util.Date;

import com.urbancode.air.*

public class SCMStep {
    
    //**************************************************************************
    // CLASS
    //**************************************************************************
    static public final String REPO_TYPE = 'perforce'

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

    def out = System.out
    
    String scmCommand = "p4"
    File workDir = new File(".").canonicalFile
    boolean isLoggingIn = false
    File ticketFile
    String username
    String password
    String charset
    String commandCharset
    String client
    String port
    def dirOffset
    
    // Client Info Variables
    final String ROOT_TOKEN        = 'Root:'
    final String VIEW_TOKEN        = 'View:'
    final String SERVER_DATE_TOKEN = 'Server date:'
    final java.text.SimpleDateFormat PERFORCE_DATE = new java.text.SimpleDateFormat("yyyy/MM/dd HH:mm:ss")
    def clientInfo
    Double timezoneDifference
    String depotPaths
    boolean integrateOption
    CommandHelper ch = new CommandHelper(workDir);
    
    public void login() {
        if (isLoggingIn && needsLogin()) {
            def loginCmd = createP4BaseLoginCommand()
            loginCmd << "login"
            ch.runCommand('P4 Login', loginCmd) { Process proc ->
                if (password) {
                    proc.out << password
                }
                proc.out.close()
                proc.consumeProcessOutput(out, out) // forward stdout and stderr
            }
        }
    }
    
    public boolean needsLogin() {
//        // Check if we need a login
//        String statusOutput
//        def expired
//        
//        def loginStatusCommand = createP4BaseLoginCommand()
//        loginStatusCommand << "login" << "-s"
//        
//        try {
//            ch.runCommand("Checking P4 ticket status...", loginStatusCommand) { proc ->
//                proc.out.close()
//                proc.consumeProcessErrorStream(out)
//                statusOutput = proc.text
//                println statusOutput
//            }
//            // This is the only time we don't need to log in. Messages include:
//            // User (username) ticket expires in (hour) hours (min) minutes.
//            // Your session has expired, please login again.
//            // User (username) was authenticated by password not ticket.
//            
//            expired = statusOutput.find(~'^User .* ticket expires in [0-9][0-9] hours [0-9][0-9] minutes\\.$') { match -> return match}
//        }
//        catch (ExitCodeException e) {
//            // Perforce password (P4PASSWD) invalid or unset. 
//        }
//        return expired ? false : true
        return true;
    }
    
    /**
    * Create a the list of initial arguments needed for perforce commands
    */
    def createP4BaseLoginCommand = {
        def command =  [scmCommand]
        command << '-p' << port << '-u' << username
        return command
    }
    
    def createP4BaseCommand = {
        def command =  [scmCommand]
        
        if (client) {
            command << '-c' << client
        }
        
        command << '-p' << port << '-u' << username

        if (password && !isLoggingIn) {
            command << '-P' << password
        }
        if (charset) {
            command << '-C' << charset
        }
        if (commandCharset) {
            command << '-Q' << commandCharset
        }
        return command
    }
    
    /**
     *  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
     }
    
    public void getClientspecInfo() {
        def clientInfoCmd = createP4BaseCommand()
        clientInfoCmd << 'client' << '-o'
      
        ch.runCommand('Geting Clientspec Info', clientInfoCmd) { Process 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) { Process proc ->
            proc.consumeProcessErrorStream(System.out)
            proc.in.withReader{ timezoneDifference = readServerInfo(it) } // read the p4server info from stdout
        }
    }
    
    /**
     * @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 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)
      */
      public def readServerInfo = { Reader br ->
         //timezoneDifference is in hours and can be 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
      }
}