package hudson.scm;

import hudson.model.AbstractBuild;
import hudson.model.Action;
import hudson.model.Hudson;
import hudson.model.TaskListener;
import hudson.model.LargeText;
import hudson.scm.SubversionSCM.SvnInfo;
import hudson.util.CopyOnWriteMap;
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.StaplerResponse;
import org.tmatesoft.svn.core.SVNException;
import org.tmatesoft.svn.core.SVNURL;
import org.tmatesoft.svn.core.wc.SVNClientManager;
import org.tmatesoft.svn.core.wc.SVNCopyClient;
import org.tmatesoft.svn.core.wc.SVNRevision;

import javax.servlet.ServletException;
import java.io.IOException;
import java.io.PrintStream;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Pattern;
import java.util.regex.Matcher;
import java.util.Map.Entry;
import java.lang.ref.WeakReference;

/**
 * {@link Action} that lets people create tag for the given build.
 * 
 * @author Kohsuke Kawaguchi
 */
public class SubversionTagAction extends AbstractScmTagAction {

    /**
     * If non-null, that means the build is already tagged.
     * Map is from the repository URL to the URL of the tag.
     * If a module is not tagged, the value will be null.
     */
    private final Map<SvnInfo,String> tags = new CopyOnWriteMap.Tree<SvnInfo,String>();

    /*package*/ SubversionTagAction(AbstractBuild build,Collection<SvnInfo> svnInfos) {
        super(build);
        Map<SvnInfo,String> m = new HashMap<SvnInfo, String>();
        for (SvnInfo si : svnInfos)
            m.put(si,null);
        tags.putAll(m);
    }

    public String getIconFileName() {
        if(tags==null && !Hudson.isAdmin())
            return null;
        return "save.gif";
    }

    public String getDisplayName() {
        int nonNullTag = 0;
        for (String v : tags.values()) {
            if(v!=null) {
                nonNullTag++;
                if(nonNullTag>1)
                    break;
            }
        }
        if(nonNullTag==0)
            return "Tag this build";
        if(nonNullTag==1)
            return "Subversion tag";
        else
            return "Subversion tags";
    }

    /**
     * @see #tags
     */
    public Map<SvnInfo,String> getTags() {
        return Collections.unmodifiableMap(tags);
    }

    private static final Pattern TRUNK_BRANCH_MARKER = Pattern.compile("/(trunk|branches)(/|$)");

    /**
     * Creates a URL, to be used as the default value of the module tag URL.
     *
     * @return
     *      null if failed to guess.
     */
    public String makeTagURL(SvnInfo si) {
        // assume the standard trunk/branches/tags repository layout
        Matcher m = TRUNK_BRANCH_MARKER.matcher(si.url);
        if(!m.find())
            return null;    // doesn't have 'trunk' nor 'branches'

        return si.url.substring(0,m.start())+"/tags/"+build.getProject().getName()+"-"+build.getNumber();
    }

    /**
     * Invoked to actually tag the workspace.
     */
    public synchronized void doSubmit(StaplerRequest req, StaplerResponse rsp) throws IOException, ServletException {
        if(!Hudson.adminCheck(req,rsp))
            return;

        Map<SvnInfo,String> newTags = new HashMap<SvnInfo,String>();

        int i=-1;
        for (Entry<SvnInfo, String> e : tags.entrySet()) {
            i++;
            if(tags.size()>1 && req.getParameter("tag"+i)==null)
                continue; // when tags.size()==1, UI won't show the checkbox.
            newTags.put(e.getKey(),req.getParameter("name" + i));
        }

        new TagWorkerThread(newTags).start();

        rsp.sendRedirect(".");
    }

    /**
     * The thread that performs tagging operation asynchronously.
     */
    public final class TagWorkerThread extends AbstractTagWorkerThread {
        private final Map<SvnInfo,String> tagSet;

        public TagWorkerThread(Map<SvnInfo, String> tagSet) {
            this.tagSet = tagSet;
        }

        public synchronized void start() {
            SubversionTagAction.this.workerThread = this;
            SubversionTagAction.this.log = new WeakReference<LargeText>(text);
            super.start();
        }

        @Override
        protected void perform(TaskListener listener) {
            SVNClientManager cm = SubversionSCM.createSvnClientManager(SubversionSCM.DescriptorImpl.DESCRIPTOR.createAuthenticationProvider());

            for (Entry<SvnInfo, String> e : tagSet.entrySet()) {
                PrintStream logger = listener.getLogger();
                logger.println("Tagging "+e.getKey()+" to "+e.getValue());

                try {
                    SVNURL src = SVNURL.parseURIDecoded(e.getKey().url);
                    SVNURL dst = SVNURL.parseURIDecoded(e.getValue());

                    SVNCopyClient svncc = cm.getCopyClient();
                    svncc.doCopy(src, SVNRevision.create(e.getKey().revision), dst, false, true, "Tagged from "+build );
                } catch (SVNException x) {
                    x.printStackTrace(listener.error("Failed to tag"));
                }
            }
        }
    }
}

@@ -4257,24 +4257,46 @@
 listener) %7B%0A
+            try %7B%0A    
             
@@ -4432,32 +4432,36 @@
 );%0A%0A            
+ 
+   
 for (Entry%3CSvnIn
@@ -4513,16 +4513,20 @@
         
+    
 PrintStr
@@ -4556,24 +4556,28 @@
 etLogger();%0A
+    
             
@@ -4648,38 +4648,46 @@
                 
+    
 try %7B%0A
+    
                 
@@ -4759,24 +4759,28 @@
             
+   
+ 
 SVNURL dst =
@@ -4835,24 +4835,28 @@
             
+    
 SVNCopyClien
@@ -4905,16 +4905,20 @@
         
+ 
+   
 svncc.do
@@ -5026,16 +5026,20 @@
         
+  
+  
 %7D catch 
@@ -5053,24 +5053,28 @@
 eption x) %7B%0A
+    
             
@@ -5145,27 +5145,321 @@
             
-%7D%0A 
+        return;%0A                    %7D%0A                %7D%0A%0A                // completed successfully%0A                SubversionTagAction.this.tags.putAll(tagSet);%0A                build.save();%0A           %7D catch (Throwable e) %7B%0A               e.printStackTrace(listener.fatalError(e.getMessage()));%0A
            %7D

needs to save the state once the tagging is complete.