/*
* Licensed Materials - Property of IBM Corp.
* IBM UrbanCode Build
* (c) Copyright IBM Corporation 2012, 2014. All Rights Reserved.
*
* U.S. Government Users Restricted Rights - Use, duplication or disclosure restricted by
* GSA ADP Schedule Contract with IBM Corp.
*/
package com.urbancode.air.plugin.scm;

import com.urbancode.air.*

import java.io.File;
import java.text.DateFormat;
import java.util.Date;
import java.util.regex.Matcher;

abstract public class SCMStep {

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

    static public final String REPO_TYPE = 'git'

    /*
     * Use of null (e.g. \0 OR %x00) breaks git log format string under msysgit,
     * so we will use the unit-separator character as an alternative to null when needed.
     */
    static protected final char UNIT_SEPARATOR = '\037'
    static protected final String UNIT_SEPARATOR_FMT = '%x1F'

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

    def out = System.out;

    String scmCommand;
    File directory;

    String remoteName;
    String remoteBaseUrl;
    String remoteUrl;
    String remoteUser;
    String remotePass;
    String branch;
    String revision;
    String fetchRef; //for Gerrit preflight checkout

    /**
     * Initialize the workspace with data from the remote repository {@link #remoteUrl} for {@link #branch} up to {@link #revision}
     */
    protected void initWorkspace() {
        // create the repository directory if not present
        if (!directory.canonicalFile.mkdirs() && !directory.exists()) {
            throw new Exception("Could not create working directory ${directory}")
        }
        else if (directory.isFile()) {
            throw new Exception("Specified working directory is a file!! ${directory}")
        }

        final def cmdHelper = getCommandHelperBase();

        // Repository does not exist, initialize a new one
        if (!new File(directory, '.git').isDirectory()) {
            def initCommand = [getScmCommand(), '--no-pager', 'init', '.']
            cmdHelper.runCommand('Git init', initCommand);
        }

        //
        // Add remote if absent
        //  TODO check remote fetch-url?
        //
        def remoteList = getCommandText([getScmCommand(), '--no-pager', 'remote']).readLines()
        if (!remoteList.contains(getRemoteName())) {
            def repoUrl = calculateRepositoryUrl();
            def remoteCommand = [getScmCommand(), '--no-pager', 'remote', 'add', getRemoteName(), repoUrl]
            cmdHelper.runCommand('Git Remote Add', remoteCommand);
        }

        //
        // Fetch revisions from remote
        // WARN: if http prompts for authentication this will hang!
        //
        def fetchCommand = [getScmCommand(), '--no-pager', 'fetch', '--force', '--prune', getRemoteName()]
        if (fetchRef) {
            def ref = getRemoteTrackingBranch()
            fetchCommand << "+$fetchRef:refs/remotes/$ref"
            cmdHelper.runCommand('Git Remote Fetch Specific Ref', fetchCommand)
        }
        else {
            cmdHelper.runCommand('Git Remote Fetch Branch', fetchCommand);
        }
    }

    //
    // Utility Methods
    //

    /**
     * Calculate the repository's complete URL from the source config url and the remoteBaseUrl
     * @return
     */
    protected String calculateRepositoryUrl() {
        def pathURI = new URI(remoteUrl);
        def result = pathURI;
        if (remoteBaseUrl) {
            def prefixURI = new URI(remoteBaseUrl+"/");
            result = prefixURI.resolve(remoteUrl);
        }

        String repoUrl = result.normalize().toString();

        // Embed username and password credentials on remoteUrl
        if (remoteUser) {
            if (repoUrl.startsWith('http')) {
                def userinfo = remoteUser;
                if (remotePass) {
                    userinfo += ":$remotePass"
                }
                repoUrl = repoUrl.replaceFirst(~'^https?://', '$0'+Matcher.quoteReplacement(userinfo)+'@')
            }
            else {
                println "warning: can not use username/password with non-http(s) url"
            }
        }
        return repoUrl;
    }

    /**
     * Print a message to the output stream of choice.
     * @param msg
     */
    protected void println(String msg) {
        out.println(msg);
    }

    protected String getCommandText(def command) {
        final def cmdHelper = getCommandHelperBase();
        def outputText = null;
        cmdHelper.runCommand(null, command){ Process proc ->
            proc.out.close() // close stdin
            proc.consumeProcessErrorStream(out) // forward stderr
            outputText = proc.text.trim();
        };

        // sanitize the raw output slightly and print
        def entrySepPattern = '\0|'+UNIT_SEPARATOR;
        println(outputText.replaceAll(entrySepPattern, '\n'));

        return outputText;
    }

    protected DateFormat getDateArgumentFormatter() {
        DateFormat gitDateFormat = new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss.S Z")
        gitDateFormat.timeZone = TimeZone.getTimeZone("GMT")
        return gitDateFormat;
    }

    protected CommandHelper getCommandHelperBase() {
        return new CommandHelper(directory);
    }

    // Convert date into revision
    protected String lookupRevision(Date date) {
        if (date) {
            String dateString = getDateArgumentFormatter().format(date);

            def remoteBranch = this.getRemoteName()+"/"+this.getBranch();
            def revListCommand = [this.getScmCommand(), '--no-pager', 'rev-list', '--max-count=1', "--before=$dateString", remoteBranch];
            return getCommandText(revListCommand);
        }
        else {
            return null;
        }
    }


    //
    // Default Getters
    //

    /**
     * Get the branch to operate upon. Default to "master"
     * @return
     */
    public String getBranch() {
        return this.branch ?: 'master'
    }

    /**
     * Get the remote name to use.  Default to "origin"
     * @return
     */
    public String getRemoteName() {
        return this.remoteName ?: 'origin';
    }

    /**
     * Get the scm command to use. Default to "git"
     * @return
     */
    public String getScmCommand() {
        return this.scmCommand ?: 'git';
    }
    
    public String getRemoteTrackingBranch() {
        def ref = fetchRef ?: this.getBranch()
        def shortRef = ref.replaceFirst('^refs/(?:heads/|tags/|remotes/[^/]+/)?', '')
        return this.getRemoteName() + "/" + shortRef
    }
}
