/*
 * Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved.
 */

package com.sun.javafx.tk.quantum;

import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.Map;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Future;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.RunnableFuture;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;

import com.sun.glass.ui.Application;
import com.sun.glass.ui.Pixels;
import com.sun.glass.ui.Screen;
import com.sun.prism.GraphicsPipeline;
import com.sun.prism.ResourceFactory;
import com.sun.prism.impl.PrismSettings;
import com.sun.prism.render.CompletionListener;
import com.sun.prism.render.RenderJob;
import com.sun.scenario.effect.impl.Renderer;
import com.sun.scenario.effect.impl.prism.PrFilterContext;

/*
 * Quantum Renderer
 *
 * @author Morris Meyer
 */
public final class QuantumRenderer extends ThreadPoolExecutor  {
    private static boolean usePurgatory = // TODO - deprecate
        AccessController.doPrivileged(new PrivilegedAction<Boolean>() {
            @Override public Boolean run() {
                return Boolean.getBoolean("decora.purgatory");
            }
        });
        
        
    private Thread          _renderer;
    private Throwable       _initThrowable = null;
    private CountDownLatch  initLatch = new CountDownLatch(1);
    
    private QuantumRenderer() { 
        super(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>());
        setThreadFactory(new QuantumThreadFactory());
    }
    
    protected Throwable initThrowable() {
        return _initThrowable;
    }

    private void setInitThrowable(Throwable th) {
        _initThrowable = th;
    }
        
    private class PipelineRunnable implements Runnable {
        private Runnable    work;

        public PipelineRunnable(Runnable runner) {
            work = runner;
        }
        
        public void init() {
            try { 
                if (GraphicsPipeline.createPipeline() == null) {
                    String MSG = "Error initializing QuantumRenderer: no suitable pipeline found";
                    System.err.println(MSG);
                    throw new RuntimeException(MSG);
                } else {
                    Map device = GraphicsPipeline.getPipeline().getDeviceDetails();
                    Application.setDeviceDetails(device);
                }
            } catch (Throwable th) {
                QuantumRenderer.this.setInitThrowable(th); 
            } finally {
                initLatch.countDown();
            }
        }
        
        public void cleanup() {
            GraphicsPipeline pipeline = GraphicsPipeline.getPipeline();
            if (pipeline != null) {
                pipeline.dispose();
            }
        }
        
        @Override public void run() {
            try {
                init();
                work.run();
            } finally {
                cleanup();
            }
        }
    }
    
    private class QuantumThreadFactory implements ThreadFactory {
        final AtomicInteger threadNumber = new AtomicInteger(0);

        @Override public Thread newThread(Runnable r) {
            final PipelineRunnable pipeline = new PipelineRunnable(r);
            _renderer =
                AccessController.doPrivileged(new PrivilegedAction<Thread>() {
                    @Override public Thread run() {
                        Thread th = new Thread(pipeline);
                        th.setName("QuantumRenderer-" + threadNumber.getAndIncrement());
                        th.setDaemon(true);
                        th.setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
                            @Override public void uncaughtException(Thread t, Throwable thr) {
                                System.err.println(t.getName() + " uncaught: " + thr.getClass().getName());
                                thr.printStackTrace();
                            }
                        });
                        return th;
                    }
                });   
            
            assert threadNumber.get() == 1;
            
            return _renderer;
        }    
    }

    protected void createResourceFactory() {
        final CountDownLatch createLatch = new CountDownLatch(1);

        final CompletionListener createDone = new CompletionListener() {
            @Override public void done(RenderJob job) {
                createLatch.countDown();
            }
        };

        final Runnable factoryCreator = new Runnable() {
            @Override public void run() {
                ResourceFactory factory = GraphicsPipeline.getDefaultResourceFactory();
            }
        };
        
        final RenderJob job = new RenderJob(factoryCreator, createDone);

        submit(job);

        try {
            createLatch.await();
        } catch (Throwable th) {
            th.printStackTrace(System.err);
        }
    }
    
    protected void stopRenderer() {
        AccessController.doPrivileged(new PrivilegedAction<Void>() {
                @Override public Void run() {
                    shutdown();
                    return null;
                };
            });
        if (PrismSettings.verbose) {
            System.out.println("QuantumRenderer: shutdown");
        }
         
        assert isShutdown();
        
        /*
         * ThreadPoolExecutor cannot be restarted once it has been 
         * shutdown.  Create a new QuantumRenderer for the next 
         * toolkit invocation.
         */
        theInstance = null;
    }
    
    @Override protected <T> RunnableFuture<T> newTaskFor(Runnable runnable, T value) {
        return (RenderJob)runnable;
    }
    
    protected Future submitRenderJob(RenderJob r) {
        return (submit(r));
    }
   
    /* java.util.concurrent.ThreadPoolExecutor */

    @Override public void afterExecute(Runnable r, Throwable t) {
        super.afterExecute(r, t);
        
        /* 
         * clean up what we can after every render job 
         *
         * we should really be keeping RenderJob/Scene pools
         */
        if (usePurgatory) {
            Screen screen = Screen.getMainScreen();
            Renderer renderer = Renderer.getRenderer(PrFilterContext.getInstance(screen));
            renderer.releasePurgatory();
        }
    }
    
    boolean checkRendererIdle() {
        PaintCollector collector = PaintCollector.getInstance();
        final boolean busy = AbstractPainter.renderLock.isLocked() && 
                             AbstractPainter.renderLock.isHeldByCurrentThread() == false &&
                             collector.isReleased() == false; 
        
        if (PrismSettings.threadCheck && busy) {
            System.err.println("ERROR: PrismPen / FX threads co-running:" +
                               " DIRTY: " + collector.hasDirty().get());
            QuantumToolkit qt = (QuantumToolkit)QuantumToolkit.getToolkit();
            for (StackTraceElement s : qt.getFxUserThread().getStackTrace()) {
                System.err.println("FX: " + s);
            }
            for (StackTraceElement q : _renderer.getStackTrace()) {
                System.err.println("QR: " + q);
            }
        }
        return !busy;
    }

    private static QuantumRenderer theInstance;
    public static synchronized QuantumRenderer getInstance() {
        if (theInstance == null) {
            synchronized (QuantumRenderer.class) {
                QuantumRenderer newTk = null;
                try {
                    newTk = new QuantumRenderer();
                    newTk.prestartCoreThread();

                    newTk.initLatch.await();
                } catch (Throwable t) {
                    newTk.setInitThrowable(t);
                    if (PrismSettings.verbose) {
                        t.printStackTrace();
                    }
                }
                if (newTk.initThrowable() != null) {
                    // TODO - UX for software driver update for D3D BadHardware
                    // LOCALIZE

                    if (PrismSettings.noFallback) {
                        System.err.println("Cannot initialize a graphics pipeline, and Prism fallback is disabled");
                        throw new InternalError("Could not initialize prism toolkit, " +
                                                "and the fallback is disabled.");
                    } else {
                        throw new RuntimeException(newTk.initThrowable());
                    }
                }
                theInstance = newTk;
            }
        }
        return theInstance;
    }
}
