/*
 * Copyright (c) 2010, 2011, Oracle  and/or its affiliates. All rights reserved.
 *
 * Use is subject to license terms.
 */

package com.sun.javafx.tk.quantum;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.Future;
import java.util.concurrent.atomic.AtomicBoolean;

import javafx.stage.StageStyle;

import com.sun.glass.ui.Pen;
import com.sun.glass.ui.View;
import com.sun.javafx.PlatformUtil;
import com.sun.javafx.geom.CameraImpl;
import com.sun.javafx.sg.prism.NGNode;
import com.sun.javafx.tk.Toolkit;
import com.sun.prism.GraphicsPipeline;
import com.sun.prism.camera.PrismCameraImpl;
import com.sun.prism.camera.PrismParallelCameraImpl;
import com.sun.prism.paint.Color;
import com.sun.prism.paint.Paint;
import com.sun.prism.render.ToolkitInterface;

public final class PrismPen extends Pen {

    private static PrismCameraImpl DEFAULT_CAMERA = PrismParallelCameraImpl.getInstance();
    private static PaintCollector  collector      = PaintCollector.getInstance();


    Future paintRunnableFuture;
    boolean valid;
    
    PrismCameraImpl camera;
    ViewScene scene;
    HashMap caps = new HashMap();
    AtomicBoolean painting;

    // A singleton object to perform the painting, so we don't create a
    // new Runnable instance on every repaint.
    private ViewPainter        painter;
    protected PaintRenderJob   paintRenderJob;

    // Indicates whether the surface to which we are rendering has a depth buffer
    private boolean depthBuffer;

    public PrismPen(ViewScene scene, boolean depthBuffer) {
        this.scene = scene;
        this.valid = false;
        this.depthBuffer = depthBuffer;
        this.painting = new AtomicBoolean(false);

        scene.setFillPaint(Color.WHITE);
    }

    boolean getDepthBuffer() {
        return depthBuffer;
    }

    @Override
    public Map getCapabilities() {
        Boolean k3dValue = Boolean.TRUE;

        // gznote: temporary workaround for http://javafx-jira.kenai.com/browse/RT-12712
        // J2D crashes hard on Mac if it uses View3D
        if (PlatformUtil.isMac()) {
            GraphicsPipeline pipeline = GraphicsPipeline.getPipeline();
            if (pipeline != null && pipeline.getClass().getName().endsWith("J2DPipeline")) {
                k3dValue = Boolean.FALSE;
            }
        }
        if (!caps.containsKey(View.Capability.k3dKey)) {
            addCapability(caps, View.Capability.k3dKey, k3dValue);
            // TODO: Sure we only request for 16bit and not 24bit?
            addCapability(caps, View.Capability.k3dDepthKey, new Integer(depthBuffer ? 16 : 0));
        }
        return caps;
    }

    public void repaint() {
        View view = scene.getPlatformView();
        if (view == null) {
            return;
        }

        if (painting.getAndSet(true) == false) {
            Toolkit tk = Toolkit.getToolkit();
            ToolkitInterface toolkit = (ToolkitInterface)tk;
            paintRunnableFuture = toolkit.addRenderJob(paintRenderJob);
        }
    }
    
    protected Future getFuture() {
        return paintRunnableFuture;
    }
    
    protected AtomicBoolean getPainting() {
        return painting;
    }
    
    protected ViewPainter getPainter() {
        return painter;
    }
    
    protected void setPainter(ViewPainter vp) {
        painter = vp;
        
        paintRenderJob = new PaintRenderJob(scene, collector.getRendered(), (Runnable)painter);
    }

    protected Color getClearColor() {
        WindowStage windowStage = (scene != null) ? scene.getWindowStage() : null;
        if ((windowStage != null) && windowStage.getStyle() == StageStyle.TRANSPARENT) {
            return (Color.TRANSPARENT);
        } else {
            if (scene.fillPaint == null) {
                return Color.WHITE;
            } else if (scene.fillPaint.isOpaque()) {
                if (scene.fillPaint.getType() == Paint.Type.COLOR) {
                    return (Color)scene.fillPaint;
                } else if (depthBuffer) {
                    // Must set clearColor in order for the depthBuffer to be cleared
                    return Color.TRANSPARENT;
                } else {
                    return null;
                }
            } else {
                return Color.WHITE;
            }
        }
    }

    protected Paint getCurrentPaint() {
        WindowStage windowStage = (scene != null) ? scene.getWindowStage() : null;
        if ((windowStage != null) && windowStage.getStyle() == StageStyle.TRANSPARENT) {
            return (Color.TRANSPARENT.equals(scene.fillPaint) ? null : scene.fillPaint);
        } else {
            if (scene.fillPaint == null) {
                return null;
            } else if (scene.fillPaint.isOpaque()) {
                if (scene.fillPaint.getType() == Paint.Type.COLOR)
                    return null;
                else
                    return scene.fillPaint;
            } else {
                return scene.fillPaint;
            }
        }
    }

    @Override
    public void paint(long time, int width, int height) {
        //TODO: if the following is really needed, then it will need to be implemented
        //by sending an event to the User Thread. calling repaint directly will
        //cause threading issues.

        // gznote: without handling this event we get garbage in the window on ceation and resize
        // this method gets called automatically on system repaint and must be handled
        // TODO: enable after testing
        //repaint(width, height);
    }

    void setCamera(final CameraImpl camera) {
        if (camera != null) {
            this.camera = (PrismCameraImpl) camera;
        } else {
            this.camera = DEFAULT_CAMERA;
        }
    }
    
    protected CameraImpl getCamera() {
        return camera;
    }
}
