/* This code is part of Freenet. It is distributed under the GNU General
 * Public License, version 2 (or at your option any later version). See
 * http://www.gnu.org/ for further details of the GPL. */
package freenet.support.compress;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.ThreadFactory;

import freenet.client.InsertException;
import freenet.client.InsertException.InsertExceptionMode;
import freenet.client.async.ClientContext;
import freenet.node.PrioRunnable;
import freenet.support.Logger;
import freenet.support.io.NativeThread;

public class RealCompressor {
    private final ExecutorService executorService;
    private ClientContext context;

    private static volatile boolean logMINOR;
    static {
        Logger.registerClass(RealCompressor.class);
    }

    public RealCompressor() {
        this.executorService = Executors.newFixedThreadPool(getMaxRunningCompressionThreads(),
                                                            new CompressorThreadFactory());
    }

    public void setClientContext(ClientContext context) {
        this.context = context;
    }

    public void enqueueNewJob(final CompressJob j) {
        if(logMINOR)
            Logger.minor(this, "Enqueueing compression job: "+j);

        Future<String> task = null;
        while(!executorService.isShutdown() && task == null) {
            try {
                    task = executorService.submit(new PrioRunnable() {
                    @Override
                    public void run() {
                        freenet.support.Logger.OSThread.logPID(this);
                        try {
                            try {
                                j.tryCompress(context);
                            } catch (InsertException e) {
                                j.onFailure(e, null, context);
                            } catch (Throwable t) {
                                Logger.error(this, "Caught in OffThreadCompressor: " + t, t);
                                System.err.println("Caught in OffThreadCompressor: " + t);
                                t.printStackTrace();
                                // Try to fail gracefully
                                j.onFailure(
                                    new InsertException(InsertExceptionMode.INTERNAL_ERROR, t,
                                                        null),
                                    null, context);
                            }

                        } catch (Throwable t) {
                            Logger.error(this, "Caught " + t + " in " + this, t);
                        }
                    }

                    @Override
                    public int getPriority() {
                        return NativeThread.MIN_PRIORITY;
                    }
                }, "Compressor thread for " + j);
                if(logMINOR)
                    Logger.minor(this, "Compression job: "+j+ "has been enqueued.");
            }catch (RejectedExecutionException e) {
                Logger.error(this, "RejectedExectutionException for "+j,e);
                task = null;
            }
        }
    }

    private static int getMaxRunningCompressionThreads() {
        int maxRunningThreads = 1;

        String osName = System.getProperty("os.name");
        if(!osName.contains("Windows") && (osName.toLowerCase().indexOf("mac os x") > 0) || (!NativeThread.usingNativeCode()))
            // OS/X niceness is really weak, so we don't want any more background CPU load than necessary
            // Also, on non-Windows, we need the native threads library to be working.
            maxRunningThreads = 1;
        else {
            // Most other OSs will have reasonable niceness, so go by RAM.
            Runtime r = Runtime.getRuntime();
            int max = r.availableProcessors(); // FIXME this may change in a VM, poll it
            long maxMemory = r.maxMemory();
            if(maxMemory < 128 * 1024 * 1024)
                max = 1;
            else
                // one compressor thread per (128MB of ram + available core)
                max = Math.min(max, (int) (Math.min(Integer.MAX_VALUE, maxMemory / (128 * 1024 * 1024))));
            maxRunningThreads = max;
        }
        Logger.minor(RealCompressor.class, "Maximum Compressor threads: " + maxRunningThreads);
        return maxRunningThreads;
    }

    public void shutdown() {
        // TODO: should we wait here?
        this.executorService.shutdown();
    }

    public static class CompressorThreadFactory implements ThreadFactory {
        @Override
        public Thread newThread(Runnable r) {
            return new NativeThread(r, "Compressor thread", NativeThread.MIN_PRIORITY, true);
        }
    }
}
