package hudson.plugins.disk_usage;

import hudson.FilePath;
import hudson.Util;
import hudson.Extension;
import hudson.model.AbstractBuild;
import hudson.model.AbstractProject;
import hudson.model.AsyncPeriodicWork;
import hudson.model.Hudson;
import hudson.model.ItemGroup;
import hudson.model.TaskListener;
import hudson.model.TopLevelItem;
import hudson.remoting.Callable;
import java.io.File;
import java.io.IOException;
import java.util.Iterator;
import java.util.List;
import java.util.logging.Level;

/**
 * A Thread responsible for gathering disk usage information
 * 
 * @author dvrzalik
 */
@Extension
public class DiskUsageThread extends AsyncPeriodicWork {
    //trigger disk usage thread each 60 minutes
    public static final int COUNT_INTERVAL_MINUTES = 60;


    public DiskUsageThread() {
        super("Project disk usage");
    }

    public long getRecurrencePeriod() {
        return 1000*60*COUNT_INTERVAL_MINUTES;
    }

    @Override
    protected void execute(TaskListener listener) throws IOException, InterruptedException {
        List items = Hudson.getInstance().getItems();

        //Include nested projects as well
        //TODO fix MatrixProject and use getAllJobs()
        for (TopLevelItem item : Hudson.getInstance().getItems()) {
            if(item instanceof ItemGroup) {
                items.addAll(((ItemGroup)item).getItems());
            }
        }

        for (Object item : items) {
            if (item instanceof AbstractProject) {
                AbstractProject project = (AbstractProject) item;
                if (project.getAction(ProjectDiskUsageAction.class) == null) {
                    try {
                        project.addProperty(new DiskUsageProperty());
                        project.save();
                    } catch (IOException ex) {
                        logger.log(Level.WARNING, "Error when adding disk usage property for " + project.getName(), ex);
                        break;
                    }
                }

                //well, this is not absolutely thread-safe, but in the worst case we get invalid result for one build
                //(which will be rewritten next time)
                if (!project.isBuilding()) {

                    List<AbstractBuild> builds = project.getBuilds();
                    Iterator<AbstractBuild> buildIterator = builds.iterator();
                    try {

                        while (buildIterator.hasNext()) {
                            calculateDiskUsageForBuild(buildIterator.next());
                        }

                        //Assign workspace size to the last build
                        calculateWorkspaceDiskUsage(project);

                    } catch (Exception ex) {
                        logger.log(Level.WARNING, "Error when recording disk usage for " + project.getName(), ex);
                    }
                }
            }
        }
    }

    private static void calculateDiskUsageForBuild(AbstractBuild build)
            throws IOException {

        //Build disk usage has to be always recalculated to be kept up-to-date 
        //- artifacts might be kept only for the last build and users sometimes delete files manually as well.
        long buildSize = DiskUsageCallable.getFileSize(build.getRootDir());
        BuildDiskUsageAction action = build.getAction(BuildDiskUsageAction.class);
        if (action == null) {
            action = new BuildDiskUsageAction(build, 0, buildSize);
            build.addAction(action);
        } else {
            action.diskUsage.buildUsage = buildSize;
        }

        build.save();
    }
    
    private static void calculateWorkspaceDiskUsage(AbstractProject project) throws IOException, InterruptedException {
        AbstractBuild lastBuild = (AbstractBuild) project.getLastBuild();
        if (lastBuild != null) {
            BuildDiskUsageAction bdua = lastBuild.getAction(BuildDiskUsageAction.class);
            if (bdua == null) {
                bdua = new BuildDiskUsageAction(lastBuild, 0, 0);
                lastBuild.addAction(bdua);
            }
            FilePath workspace = project.getSomeWorkspace();
            //slave might be offline...
            if ((workspace != null) && (bdua.diskUsage.wsUsage <= 0)) {
                bdua.diskUsage.wsUsage = workspace.act(new DiskUsageCallable(workspace));
                lastBuild.save();
            }
        }
    }

    /**
     * A {@link Callable} which computes disk usage of remote file object
     */
    public static class DiskUsageCallable implements Callable<Long, IOException> {

        private FilePath path;

        public DiskUsageCallable(FilePath filePath) {
            this.path = filePath;
        }

        public Long call() throws IOException {
            File f = new File(path.getRemote());
            return getFileSize(f);
        }

        public static Long getFileSize(File f) throws IOException {
            long size = 0;

            if (f.isDirectory() && !Util.isSymlink(f)) {
                for (File child : f.listFiles()) {
                    size += getFileSize(child);
                }
            }
            
            return size + f.length();
        }
    }
}

@@ -414,24 +414,47 @@
 OException;%0A
+import java.lang.Math;%0A
 import java.
@@ -3389,32 +3389,69 @@
 eAction.class);%0A
+        boolean updateBuild = false;%0A
         if (acti
@@ -3581,89 +3581,317 @@
     
-%7D else %7B%0A            action.diskUsage.buildUsage = buildSize;%0A        %7D%0A%0A        
+    updateBuild = true;%0A        %7D else %7B%0A        %09if (( action.diskUsage.buildUsage %3C= 0 ) %7C%7C%0A        %09%09%09( Math.abs(action.diskUsage.buildUsage - buildSize) %3E 1024 )) %7B%0A        %09%09action.diskUsage.buildUsage = buildSize;%0A        %09%09updateBuild = true;%0A        %09%7D%0A        %7D%0A        if ( updateBuild ) %7B%0A        %09
 buil
@@ -3896,24 +3896,34 @@
 ild.save();%0A
+        %7D%0A
     %7D%0A    %0A 

Build disk usage is recorded only if changed more then 1kB [HUDSON-5731]