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

package com.sun.javafx.tk.quantum;

import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.locks.ReentrantLock;

import com.sun.javafx.geom.CameraImpl;
import com.sun.javafx.geom.RectBounds;
import com.sun.javafx.geom.Rectangle;
import com.sun.javafx.geom.transform.Affine3D;
import com.sun.javafx.geom.transform.GeneralTransform3D;
import com.sun.javafx.jmx.HighlightRegion;
import com.sun.javafx.runtime.SystemProperties;
import com.sun.javafx.sg.DirtyRegionContainer;
import com.sun.javafx.sg.DirtyRegionPool;
import com.sun.javafx.sg.prism.NGNode;
import com.sun.javafx.tk.Toolkit;
import com.sun.prism.BasicStroke;
import com.sun.prism.Graphics;
import com.sun.prism.GraphicsResource;
import com.sun.prism.Presentable;
import com.sun.prism.RenderingContext;
import com.sun.prism.ResourceFactory;
import com.sun.prism.camera.PrismCameraImpl;
import com.sun.prism.camera.PrismPerspectiveCameraImpl;
import com.sun.prism.impl.PrismSettings;
import com.sun.prism.paint.Color;


/*
 * @author Morris Meyer
 */
public abstract class AbstractPainter {

    protected static boolean verbose = 
        AccessController.doPrivileged(new PrivilegedAction<Boolean>() {
            @Override public Boolean run() {
                return Boolean.getBoolean("quantum.paint");
            }
        });
        
    /*
     * This could be a per-scene lock but there is no guarantee that the 
     * FX handlers called in GlassViewEventHandler would not modify other scenes.
     */
    protected static final ReentrantLock renderLock = new ReentrantLock();

    protected static final PaintCollector collector = PaintCollector.getInstance();

    protected Presentable       presentable;
    protected RenderingContext  context = null;
    protected ResourceFactory   factory;
    protected AtomicBoolean     liveRepaint = new AtomicBoolean(false);

    /* FIXME - comment */
    protected int               width;
    protected int               height;
    
    private boolean             renderOverlay = false;
    
    Rectangle                   dirtyRect;
    RectBounds                  clip;
    RectBounds                  dirtyRegionTemp;
    DirtyRegionPool             dirtyRegionPool;
    DirtyRegionContainer        dirtyRegionContainer;
    Affine3D                    tx;
    Affine3D                    scaleTx;
    GeneralTransform3D          viewProjTx;
    GeneralTransform3D          projTx;
    NGNode                      root, overlayRoot;
    GlassScene                  scene;

    protected AbstractPainter(GlassScene gs) {
        if (PrismSettings.dirtyOptsEnabled) {
            tx = new Affine3D();
            viewProjTx = new GeneralTransform3D();
            projTx = new GeneralTransform3D();
            scaleTx = new Affine3D();
            clip = new RectBounds();
            dirtyRect = new Rectangle();
            dirtyRegionTemp = new RectBounds();
            dirtyRegionPool = new DirtyRegionPool(PrismSettings.dirtyRegionCount);
            dirtyRegionContainer = dirtyRegionPool.checkOut();
        }
        scene    = gs;
    }
    
    protected void setPaintBounds(int w, int h) {
        width  = w;
        height = h;
    }
    
    protected void setRoot(NGNode node) {
        root = node;
    }

    protected NGNode getRoot() {
        return (root);
    }

    protected void setOverlayRoot(NGNode node) {
        overlayRoot = node;
    }
    
    protected NGNode getOverlayRoot() {
        return (overlayRoot);
    }
    
    protected void setRenderOverlay(boolean val) {
        renderOverlay = val;
    }
    
    protected abstract void doPaint(Graphics g);
    
    protected abstract CameraImpl getCamera();

    private void adjustPerspective(CameraImpl camera) {
        if (camera instanceof PrismPerspectiveCameraImpl) {
            PrismPerspectiveCameraImpl perspCamera = (PrismPerspectiveCameraImpl) camera;
            scaleTx.setToScale(width / 2.0, -height / 2.0, 1);
            scaleTx.translate(1, -1);
            projTx.mul(scaleTx);
            viewProjTx = perspCamera.getProjViewTx(viewProjTx, width, height);
            projTx.mul(viewProjTx);
        }
    }

    private int setDirtyRect(Graphics g) {
        clip.setBounds(0, 0, width, height);
        dirtyRegionTemp.makeEmpty();
        dirtyRegionContainer.reset();
        tx.setToIdentity();
        projTx.setIdentity();
        adjustPerspective(getCamera());
        int status = root.accumulateDirtyRegions(clip, dirtyRegionTemp,
                                                 dirtyRegionPool, dirtyRegionContainer, 
                                                 tx, projTx);
        
        return status;
    }

    public void paintImpl(Graphics g) {
        int status = DirtyRegionContainer.DTR_INVALID;
        if (PrismSettings.dirtyOptsEnabled) {
            if (!scene.isEntireSceneDirty()) {
                status = setDirtyRect(g);
                if (status == DirtyRegionContainer.DTR_OK) {
                    root.doPreCulling(dirtyRegionContainer,
                                      tx, projTx);
                }
            }
        }
                    
        if (!PrismSettings.showDirtyRegions && status == DirtyRegionContainer.DTR_OK) {
            g.setHasPreCullingBits(true);
            for (int i = 0; i < dirtyRegionContainer.size(); i++) {
                final RectBounds dirtyRegion = dirtyRegionContainer.getDirtyRegion(i);
                // make sure we are not trying to render in some invalid region
                if (dirtyRegion.getWidth() > 0 && dirtyRegion.getHeight() > 0) {
                    // set the clip rectangle using integer
                    // bounds since a fractional bounding box will
                    // still require a complete repaint on
                    // pixel boundaries
                    dirtyRect.setBounds(dirtyRegion);
                    g.setClipRect(dirtyRect);
                    g.setClipRectIndex(i);
                    doPaint(g);
                }
            }
        } else {
            g.setHasPreCullingBits(false);
            g.setClipRect(null);
            this.doPaint(g);
        }
        
        if (PrismSettings.showDirtyRegions) {
            if (PrismSettings.showCull) {
                 root.drawCullBits(g);
            }
            g.setDepthTest(false);
            if (status == DirtyRegionContainer.DTR_OK) {
                g.setPaint(new Color(1, 0, 0, .3f));
                for (int i = 0; i < dirtyRegionContainer.size(); i++) {
                    RectBounds reg = dirtyRegionContainer.getDirtyRegion(i);
                    g.fillRect(reg.getMinX(), reg.getMinY(), 
                               reg.getWidth(), reg.getHeight());                        
                }
            } else {
                g.setPaint(new Color(1, 0, 0, .3f));
                g.fillRect(0, 0, width, height);
            }
        }
        
        if (SystemProperties.isDebug()) {
            Set<HighlightRegion> highlightRegions =
                    Toolkit.getToolkit().getHighlightedRegions();
            if (highlightRegions != null) {
                g.setStroke(new BasicStroke(1f,
                                            BasicStroke.CAP_BUTT,
                                            BasicStroke.JOIN_BEVEL,
                                            10f));
                for (HighlightRegion region: highlightRegions) {
                    if (scene.equals(region.getTKScene())) {
                        g.setPaint(new Color(1, 1, 1, 1));
                        g.drawRect((float) region.getMinX(),
                                   (float) region.getMinY(),
                                   (float) region.getWidth(),
                                   (float) region.getHeight());
                        g.setPaint(new Color(0, 0, 0, 1));
                        g.drawRect((float) region.getMinX() - 1,
                                   (float) region.getMinY() - 1,
                                   (float) region.getWidth() + 2,
                                   (float) region.getHeight() + 2);
                    }
                }
            }
        }
        if (renderOverlay) {
            overlayRoot.render(g);
        }
    }

    void disposePresentable() {
        if (presentable instanceof GraphicsResource) {
            ((GraphicsResource)presentable).dispose();
        }
        presentable = null;
    }
    
    protected boolean validateStageGraphics() {
        if (scene == null) {
            return false;
        }

        if (scene.glassStage == null) {
            return false;
        }
        return true;
    }    
}
