package com.joelj.jenkins.eztemplates.utils;

import com.google.common.base.Predicate;
import com.google.common.collect.Collections2;
import hudson.XmlFile;
import hudson.model.AbstractProject;
import hudson.model.Items;
import hudson.model.JobProperty;
import hudson.triggers.Trigger;
import hudson.util.AtomicFileWriter;
import hudson.util.IOException2;
import jenkins.model.Jenkins;
import org.kohsuke.stapler.Ancestor;
import org.kohsuke.stapler.StaplerRequest;

import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.stream.StreamResult;
import java.io.IOException;
import java.lang.reflect.Field;
import java.util.Collection;
import java.util.List;

public class ProjectUtils {

    public static Collection<AbstractProject> findProjectsWithProperty(final Class<? extends JobProperty<?>> property) {
        List<AbstractProject> projects = Jenkins.getInstance().getAllItems(AbstractProject.class);
        return Collections2.filter(projects, new Predicate<AbstractProject>() {
            @Override
            public boolean apply(AbstractProject abstractProject) {
                return abstractProject.getProperty(property) != null;
            }
        });
    }

    public static AbstractProject findProject(StaplerRequest request) {
        Ancestor ancestor = request.getAncestors().get(request.getAncestors().size() - 1);
        while (ancestor != null && !(ancestor.getObject() instanceof AbstractProject)) {
            ancestor = ancestor.getPrev();
        }
        if (ancestor == null) {
            return null;
        }
        return (AbstractProject) ancestor.getObject();
    }

    /**
     * Get a project by its fullName (including any folder structure if present).
     * Temporarily also allows a match by name if one exists.
     */
    public static AbstractProject findProject(String fullName) {
        List<AbstractProject> projects = Jenkins.getInstance().getAllItems(AbstractProject.class);
        AbstractProject nameOnlyMatch = null; // marc: 20140831, Remove compat patch for users upgrading
        for (AbstractProject project : projects) {
            if (fullName.equals(project.getFullName())) {
                return project;
            }
            if (fullName.equals(project.getName())) {
                nameOnlyMatch = project;
            }
        }
        return nameOnlyMatch;
    }

    /**
     * Silently saves the project without triggering any save events.
     * Use this method to save a project from within an Update event handler.
     */
    public static void silentSave(AbstractProject project) throws IOException {
        project.getConfigFile().write(project);
    }

    /**
     * Copied from {@link AbstractProject#updateByXml(javax.xml.transform.Source)}, removing the save event and
     * returning the project after the update.
     */
    @SuppressWarnings("unchecked")
    public static AbstractProject updateProjectWithXmlSource(AbstractProject project, Source source) throws IOException {

        XmlFile configXmlFile = project.getConfigFile();
        AtomicFileWriter out = new AtomicFileWriter(configXmlFile.getFile());
        try {
            try {
                // this allows us to use UTF-8 for storing data,
                // plus it checks any well-formedness issue in the submitted
                // data
                Transformer t = TransformerFactory.newInstance()
                        .newTransformer();
                t.transform(source,
                        new StreamResult(out));
                out.close();
            } catch (TransformerException e) {
                throw new IOException2("Failed to persist configuration.xml", e);
            }

            // try to reflect the changes by reloading
            new XmlFile(Items.XSTREAM, out.getTemporaryFile()).unmarshal(project);
            project.onLoad(project.getParent(), project.getRootDir().getName());
            Jenkins.getInstance().rebuildDependencyGraph();

            // if everything went well, commit this new version
            out.commit();
            return ProjectUtils.findProject(project.getFullName());
        } finally {
            out.abort(); // don't leave anything behind
        }
    }

    public static List<Trigger<?>> getTriggers(AbstractProject implementationProject) {
        try {
            Field triggers = AbstractProject.class.getDeclaredField("triggers");
            triggers.setAccessible(true);
            Object result = triggers.get(implementationProject);
            //noinspection unchecked
            return (List<Trigger<?>>) result;
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
        } catch (NoSuchFieldException e) {
            throw new RuntimeException(e);
        }
    }
}

@@ -150,16 +150,50 @@
 mlFile;%0A
+import hudson.model.AbstractItem;%0A
 import h
@@ -413,16 +413,72 @@
 enkins;%0A
+import jenkins.security.NotReallyRoleSensitiveCallable;%0A
 import o
@@ -2904,16 +2904,24 @@
 ed from 
+1.580.3 
 %7B@link A
@@ -2927,23 +2927,20 @@
 Abstract
-Project
+Item
 #updateB
@@ -3153,16 +3153,22 @@
 lSource(
+final 
 Abstract
@@ -3859,17 +3859,16 @@
 xception
-2
 (%22Failed
@@ -3889,15 +3889,8 @@
 nfig
-uration
 .xml
@@ -3973,24 +3973,35 @@
 %0A           
+ Object o =
  new XmlFile
@@ -4076,74 +4076,648 @@
     
-project.onLoad(project.getParent(), project.getRootDir().getName()
+if (o!=project) %7B%0A                // ensure that we've got the same job type. extending this code to support updating%0A                // to different job type requires destroying & creating a new job type%0A                throw new IOException(%22Expecting %22+project.getClass()+%22 but got %22+o.getClass()+%22 instead%22);%0A            %7D%0A            Items.whileUpdatingByXml(new NotReallyRoleSensitiveCallable%3CVoid,IOException%3E() %7B%0A                @Override public Void call() throws IOException %7B%0A                    project.onLoad(project.getParent(), project.getRootDir().getName());%0A                    return null;%0A                %7D%0A            %7D
 );%0A 
@@ -4889,21 +4889,8 @@
 urn 
-ProjectUtils.
 find

Update cloned code to match that in 1.580.3