import '../../../assert/js/assert.js';
import '../../../globals.js';
// Copyright 2023-2024, University of Colorado Boulder
/**
 * Similar to an DOM Canvas, but stores a vector representation of the relevant drawing commands.
 *
 * TODO TODO: This should be done instead in the rational-half-edge space, so we get better exactness on the repeated
 * TODO: CAG. What is causing the issues?
 *
 * TODO: We really would want to "cache" all of the commands at once, build up a "pending" RenderProgram, and then
 * TODO: handle all the paths at once.
 *
 * @author Jonathan Olson <jonathan.olson@colorado.edu>
 */
import Bounds2 from '../../../dot/js/Bounds2.js';
import Matrix3 from '../../../dot/js/Matrix3.js';
import Vector2 from '../../../dot/js/Vector2.js';
import { combineOptions } from '../../../phet-core/js/optionize.js';
import { alpenglow, BoundsClipping, CombinedRaster, getPolygonFilterGridBounds, LinearEdge, PolygonalBoolean, PolygonalFace, PolygonFilterType, Rasterize, RenderableFace, RenderColor, RenderColorSpace, RenderGradientStop, RenderLinearGradient, RenderLinearGradientAccuracy, RenderPath, RenderRadialGradient, RenderRadialGradientAccuracy, RenderStack } from '../imports.js';
export default class VectorCanvas {
    constructor(width, height, colorSpace = 'srgb', 
    // TODO: proper options object?
    polygonFiltering = PolygonFilterType.Box) {
        this.width = width;
        this.height = height;
        this.colorSpace = colorSpace;
        this.polygonFiltering = polygonFiltering;
        this.renderableFaces = [];
        this.bounds = Bounds2.NOTHING;
        this.gridBounds = Bounds2.NOTHING;
        this.updateWidthHeight(width, height);
    }
    // Assumes sRGB? We'll do srgb-linear blending to mimic for now?
    fillColor(renderPath, color) {
        this.fillRenderProgram(renderPath, this.colorToRenderProgram(color));
    }
    fillLinearGradient(renderPath, start, end, stops, extend, accuracy = RenderLinearGradientAccuracy.SplitAccurate) {
        this.fillRenderProgram(renderPath, new RenderLinearGradient(Matrix3.IDENTITY, start, end, stops.map(stop => new RenderGradientStop(stop.ratio, this.colorToRenderProgram(stop.color))), extend, accuracy));
        this.renderableFaces = this.renderableFaces.flatMap(face => face.split());
    }
    fillRadialGradient(renderPath, transform, start, startRadius, end, endRadius, stops, extend, accuracy = RenderRadialGradientAccuracy.SplitAccurate) {
        this.fillRenderProgram(renderPath, new RenderRadialGradient(Matrix3.IDENTITY, start, startRadius, end, endRadius, stops.map(stop => new RenderGradientStop(stop.ratio, this.colorToRenderProgram(stop.color))), extend, accuracy));
        this.renderableFaces = this.renderableFaces.flatMap(face => face.split());
    }
    copy() {
        const canvas = new VectorCanvas(this.width, this.height, this.colorSpace, this.polygonFiltering);
        canvas.renderableFaces = this.renderableFaces.slice();
        return canvas;
    }
    colorToRenderProgram(color) {
        return new RenderColor(color).colorConverted(RenderColorSpace.sRGB, this.colorSpace === 'srgb' ? RenderColorSpace.premultipliedSRGB : RenderColorSpace.premultipliedDisplayP3);
    }
    fillRenderProgram(renderPath, renderProgram) {
        const bounds = renderPath.getBounds();
        renderPath = new RenderPath(renderPath.fillRule, renderPath.subpaths.map(subpath => BoundsClipping.boundsClipPolygon(subpath, this.bounds.minX, this.bounds.minY, this.bounds.maxX, this.bounds.maxY, this.bounds.centerX, this.bounds.centerY)));
        const newRenderableFaces = [];
        for (let i = 0; i < this.renderableFaces.length; i++) {
            const renderableFace = this.renderableFaces[i];
            if (renderableFace.bounds.intersectsBounds(bounds)) {
                const existingRenderPath = new RenderPath('nonzero', renderableFace.face.toPolygonalFace().polygons);
                const overlaps = PolygonalBoolean.getOverlaps(existingRenderPath, renderPath);
                if (overlaps.intersection.length) {
                    if (overlaps.aOnly.length) {
                        const aOnlyFace = new PolygonalFace(overlaps.aOnly);
                        newRenderableFaces.push(new RenderableFace(aOnlyFace, renderableFace.renderProgram, aOnlyFace.getBounds()));
                    }
                    const intersectionFace = new PolygonalFace(overlaps.intersection);
                    newRenderableFaces.push(new RenderableFace(intersectionFace, new RenderStack([renderableFace.renderProgram, renderProgram]).simplified(), intersectionFace.getBounds()));
                }
                else {
                    newRenderableFaces.push(renderableFace);
                }
            }
            else {
                newRenderableFaces.push(renderableFace);
            }
        }
        this.renderableFaces = newRenderableFaces;
        this.combineFaces();
    }
    combineFaces() {
        // TODO: ONLY split linear/radial gradients AFTER we have combined faces!!!!
        for (let i = 0; i < this.renderableFaces.length; i++) {
            const renderableFace = this.renderableFaces[i];
            const renderProgram = renderableFace.renderProgram;
            const compatibleFaces = [];
            const compatibleIndices = [];
            for (let j = i + 1; j < this.renderableFaces.length; j++) {
                const otherRenderableFace = this.renderableFaces[j];
                if (renderProgram.equals(otherRenderableFace.renderProgram)) {
                    compatibleFaces.push(otherRenderableFace);
                    compatibleIndices.push(j);
                }
            }
            if (compatibleFaces.length) {
                // TODO: something better than using LinearEdge.toPolygons, it is not high performance (we should trace edges,
                // TODO: like traceCombineFaces).
                const polygons = [renderableFace, ...compatibleFaces].map(renderableFace => renderableFace.face.toPolygonalFace().polygons).flat();
                const simplifiedPolygons = LinearEdge.toPolygons(LinearEdge.fromPolygons(polygons));
                const newFace = new PolygonalFace(simplifiedPolygons);
                this.renderableFaces[i] = new RenderableFace(newFace, renderProgram, newFace.getBounds());
                while (compatibleIndices.length) {
                    this.renderableFaces.splice(compatibleIndices.pop(), 1);
                }
            }
        }
    }
    updateWidthHeight(width, height) {
        assert && assert(Number.isInteger(width) && Number.isInteger(height));
        this.width = width;
        this.height = height;
        this.bounds = new Bounds2(0, 0, width, height);
        this.gridBounds = getPolygonFilterGridBounds(this.bounds, this.polygonFiltering, 1);
        this.renderableFaces.length = 0;
        this.renderableFaces.push(new RenderableFace(PolygonalFace.fromBounds(this.bounds), RenderColor.TRANSPARENT, this.bounds));
        // TODO: splitting of radial/linear gradients!!!
    }
    getImageData(options) {
        const raster = new CombinedRaster(this.width, this.height, combineOptions({
            colorSpace: this.colorSpace
        }, options));
        Rasterize.rasterizeAccumulate(raster, this.renderableFaces, this.bounds, this.gridBounds, Vector2.ZERO, this.polygonFiltering, 1, 'evaluation', null);
        return raster.toImageData();
    }
    getCanvas(options) {
        return Rasterize.imageDataToCanvas(this.getImageData(options));
    }
}
alpenglow.register('VectorCanvas', VectorCanvas);
//# sourceMappingURL=VectorCanvas.js.map