/*
 * Copyright (c) 2010, 2025, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

package sun.java2d.xr;

import java.awt.*;
import java.util.*;

/**
 * We render non-antialiased geometry (consisting of rectangles) into a mask,
 * which is later used in a composition step.
 * To avoid mask-allocations of large size, MaskTileManager splits
 * geometry larger than MASK_SIZE into several tiles,
 * and stores the geometry in instances of MaskTile.
 *
 * @author Clemens Eisserer
 */

public final class MaskTileManager {

    public static final int MASK_SIZE = 256;

    MaskTile mainTile = new MaskTile();

    ArrayList<MaskTile> tileList;
    int allocatedTiles = 0;
    int xTiles, yTiles;

    XRCompositeManager xrMgr;
    XRBackend con;

    int maskPixmap;
    int maskPicture;
    long maskGC;

    public MaskTileManager(XRCompositeManager xrMgr, int parentXid) {
        tileList = new ArrayList<MaskTile>();
        this.xrMgr = xrMgr;
        this.con = xrMgr.getBackend();

        maskPixmap = con.createPixmap(parentXid, 8, MASK_SIZE, MASK_SIZE);
        maskPicture = con.createPicture(maskPixmap, XRUtils.PictStandardA8);
        con.renderRectangle(maskPicture, XRUtils.PictOpClear,
                            new XRColor(Color.black),
                            0, 0, MASK_SIZE, MASK_SIZE);
        maskGC = con.createGC(maskPixmap);
        con.setGCExposures(maskGC, false);
    }

    /**
     * Transfers the geometry stored (rectangles, lines) to one or more masks,
     * and renders the result to the destination surface.
     */
    public void fillMask(XRSurfaceData dst) {

        boolean maskRequired = xrMgr.maskRequired();
        boolean maskEvaluated = XRUtils.isMaskEvaluated(xrMgr.compRule);

        if (maskRequired && maskEvaluated) {
            mainTile.calculateDirtyAreas();
            DirtyRegion dirtyArea = mainTile.getDirtyArea().cloneRegion();
            mainTile.translate(-dirtyArea.x, -dirtyArea.y);

            XRColor maskColor = xrMgr.getMaskColor();

            // We don't need tiling if all geometry fits in a single tile
            if (dirtyArea.getWidth() <= MASK_SIZE &&
                dirtyArea.getHeight() <= MASK_SIZE)
            {
                compositeSingleTile(dst, mainTile, dirtyArea,
                                     maskRequired, 0, 0, maskColor);
            } else {
                allocTiles(dirtyArea);
                tileRects();

                for (int i = 0; i < yTiles; i++) {
                    for (int m = 0; m < xTiles; m++) {
                        MaskTile tile = tileList.get(i * xTiles + m);

                        int tileStartX = m * MASK_SIZE;
                        int tileStartY = i * MASK_SIZE;
                        compositeSingleTile(dst, tile, dirtyArea, maskRequired,
                                            tileStartX, tileStartY, maskColor);
                    }
                }
            }
        } else {
            /*
             * If a mask would be required to store geometry (maskRequired)
             * composition has to be done rectangle-by-rectagle.
             */
            if(xrMgr.isSolidPaintActive()) {
                xrMgr.XRRenderRectangles(dst, mainTile.getRects());
            } else {
                xrMgr.XRCompositeRectangles(dst, mainTile.getRects());
            }
        }

        mainTile.reset();
    }

    /**
     * Uploads aa geometry generated for maskblit/fill into the mask pixmap.
     */
    public int uploadMask(int w, int h, int maskscan, int maskoff, byte[] mask) {
        int maskPic = XRUtils.None;

        if (mask != null) {
            float maskAlpha =
                 xrMgr.isTexturePaintActive() ? xrMgr.getExtraAlpha() : 1.0f;
            con.putMaskImage(maskPixmap, maskGC, mask, 0, 0, 0, 0,
                             w, h, maskoff, maskscan, maskAlpha);
            maskPic = maskPicture;
        } else if (xrMgr.isTexturePaintActive()) {
            maskPic = xrMgr.getExtraAlphaMask();
         }

        return maskPic;
    }

    /**
     * Clears the area of the mask-pixmap used for uploading aa coverage values.
     */
    public void clearUploadMask(int mask, int w, int h) {
        if (mask == maskPicture) {
            con.renderRectangle(maskPicture, XRUtils.PictOpClear,
                                XRColor.NO_ALPHA, 0, 0, w, h);
        }
    }


    /**
     * Renders the rectangles provided to the mask, and does a composition
     * operation with the properties set inXRCompositeManager.
     */
    protected void compositeSingleTile(XRSurfaceData dst, MaskTile tile,
                                       DirtyRegion dirtyArea,
                                       boolean maskRequired,
                                       int tileStartX, int tileStartY,
                                       XRColor maskColor) {
        if (tile.rects.getSize() > 0) {
            DirtyRegion tileDirtyArea = tile.getDirtyArea();

            int x = tileDirtyArea.x + tileStartX + dirtyArea.x;
            int y = tileDirtyArea.y + tileStartY + dirtyArea.y;
            int width = tileDirtyArea.x2 - tileDirtyArea.x;
            int height = tileDirtyArea.y2 - tileDirtyArea.y;
            width = Math.min(width, MASK_SIZE);
            height = Math.min(height, MASK_SIZE);

            int rectCnt = tile.rects.getSize();

            if (maskRequired) {
                int mask = XRUtils.None;

                /*
                 * Optimization: When the tile only contains one rectangle, the
                 * composite-operation boundaries can be used as geometry
                 */
                if (rectCnt > 1) {
                    con.renderRectangles(maskPicture, XRUtils.PictOpSrc,
                                         maskColor, tile.rects);
                    mask = maskPicture;
                } else {
                    if (xrMgr.isTexturePaintActive()) {
                        mask = xrMgr.getExtraAlphaMask();
                    }
                }

                xrMgr.XRComposite(XRUtils.None, mask, dst.getPicture(),
                                  x, y, tileDirtyArea.x, tileDirtyArea.y,
                                  x, y, width, height);

                /* Clear dirty rectangle of the rect-mask */
                if (rectCnt > 1) {
                    con.renderRectangle(maskPicture, XRUtils.PictOpClear,
                                        XRColor.NO_ALPHA,
                                        tileDirtyArea.x, tileDirtyArea.y,
                                        width, height);
                }

                tile.reset();
            } else if (rectCnt > 0) {
                tile.rects.translateRects(tileStartX + dirtyArea.x,
                                          tileStartY + dirtyArea.y);
                xrMgr.XRRenderRectangles(dst, tile.rects);
            }
        }
    }


    /**
     * Allocates enough MaskTile instances, to cover the whole
     * mask area, or resets existing ones.
     */
    protected void allocTiles(DirtyRegion maskArea) {
        xTiles = (maskArea.getWidth() / MASK_SIZE) + 1;
        yTiles = (maskArea.getHeight() / MASK_SIZE) + 1;
        int tileCnt = xTiles * yTiles;

        if (tileCnt > allocatedTiles) {
            for (int i = 0; i < tileCnt; i++) {
                if (i < allocatedTiles) {
                    tileList.get(i).reset();
                } else {
                    tileList.add(new MaskTile());
                }
            }

            allocatedTiles = tileCnt;
        }
    }

    /**
     * Tiles the stored rectangles, if they are larger than the MASK_SIZE
     */
    protected void tileRects() {
        GrowableRectArray rects = mainTile.rects;

        for (int i = 0; i < rects.getSize(); i++) {
            int tileXStartIndex = rects.getX(i) / MASK_SIZE;
            int tileYStartIndex = rects.getY(i) / MASK_SIZE;
            int tileXLength =
                ((rects.getX(i) + rects.getWidth(i)) / MASK_SIZE + 1) -
                 tileXStartIndex;
            int tileYLength =
                 ((rects.getY(i) + rects.getHeight(i)) / MASK_SIZE + 1) -
                 tileYStartIndex;

            for (int n = 0; n < tileYLength; n++) {
                for (int m = 0; m < tileXLength; m++) {

                    int tileIndex =
                         xTiles * (tileYStartIndex + n) + tileXStartIndex + m;
                    MaskTile tile = tileList.get(tileIndex);

                    GrowableRectArray rectTileList = tile.getRects();
                    int tileArrayIndex = rectTileList.getNextIndex();

                    int tileStartPosX = (tileXStartIndex + m) * MASK_SIZE;
                    int tileStartPosY = (tileYStartIndex + n) * MASK_SIZE;

                    rectTileList.setX(tileArrayIndex, rects.getX(i) - tileStartPosX);
                    rectTileList.setY(tileArrayIndex, rects.getY(i) - tileStartPosY);
                    rectTileList.setWidth(tileArrayIndex, rects.getWidth(i));
                    rectTileList.setHeight(tileArrayIndex, rects.getHeight(i));

                    limitRectCoords(rectTileList, tileArrayIndex);

                    tile.getDirtyArea().growDirtyRegion
                       (rectTileList.getX(tileArrayIndex),
                        rectTileList.getY(tileArrayIndex),
                        rectTileList.getWidth(tileArrayIndex) +
                             rectTileList.getX(tileArrayIndex),
                        rectTileList.getHeight(tileArrayIndex) +
                            rectTileList.getY(tileArrayIndex));
                }
            }
        }
    }

    /**
     * Limits the rect's coordinates to the mask coordinates. The result is used
     * by growDirtyRegion.
     */
    private void limitRectCoords(GrowableRectArray rects, int index) {
        if ((rects.getX(index) + rects.getWidth(index)) > MASK_SIZE) {
            rects.setWidth(index, MASK_SIZE - rects.getX(index));
        }
        if ((rects.getY(index) + rects.getHeight(index)) > MASK_SIZE) {
            rects.setHeight(index, MASK_SIZE - rects.getY(index));
        }
        if (rects.getX(index) < 0) {
            rects.setWidth(index, rects.getWidth(index) + rects.getX(index));
            rects.setX(index, 0);
        }
        if (rects.getY(index) < 0) {
            rects.setHeight(index, rects.getHeight(index) + rects.getY(index));
            rects.setY(index, 0);
        }
    }

    /**
     * @return MainTile to which rectangles are added before composition.
     */
    public MaskTile getMainTile() {
        return mainTile;
     }
}
