// Returns an [x, y] point on a circle, with given origin and radius, at a given angle
// counter-clockwise from the positive horizontal axis.
function circleCoordinates(origin, radius, radians) {
    return [
        origin[0] + Math.cos(radians) * radius,
        origin[1] + Math.sin(radians) * radius
    ];
}

// Animator handles calling and stopping requestAnimationFrame and keeping track of framerate.
class Animator {
    framesCount = 0;
    totalFramesMs = 0;
    animating = false;
    renderer = null;

    start() {
        if (this.animating === false) {
            this.animating = true;
            this.framesCount = 0;
            const frameStartMs = performance.now();

            const drawFrame = () => {
                if (this.animating && this.renderer) {
                    requestAnimationFrame(drawFrame);
                    this.framesCount++;

                    const [x, y] = circleCoordinates([-70, -70], 50, this.framesCount/100);
                    this.renderer.render(x, y);

                    const frameTimeMs = performance.now() - frameStartMs;
                    this.totalFramesMs = frameTimeMs;
                }
            };
            requestAnimationFrame(drawFrame);
        }
    }

    stop() {
        this.animating = false;
    }
}

// The following three renderers draw a repeating pattern of paths.
// The approximate height and width of this repeated pattern is given by PATTERN_BOUNDS:
const PATTERN_BOUNDS = 600;
// And the spacing of the pattern (distance between repeated paths) is given by PATTERN_SPACING:
const PATTERN_SPACING = 70;

class SVGRenderer {
    constructor(svgObjectElement) {
        this.svgObjectElement = svgObjectElement;
        this.svgElArray = [];
        // Create an SVG element for every position in the pattern
        for (let xo = 0; xo < PATTERN_BOUNDS; xo += PATTERN_SPACING) {
            for (let yo = 0; yo < PATTERN_BOUNDS; yo += PATTERN_SPACING) {
                const clonedSVG = svgObjectElement.cloneNode(true);
                this.svgElArray.push(clonedSVG);
                svgObjectElement.parentElement.appendChild(clonedSVG);
            }
        }
    }

    render(x, y) {
        let i = 0;
        for (let xo = 0; xo < PATTERN_BOUNDS; xo += PATTERN_SPACING) {
            for (let yo = 0; yo < PATTERN_BOUNDS; yo += PATTERN_SPACING) {
                this.svgElArray[i].style.transform = `translate(${x + xo}px, ${y + yo}px)`;
                i++;
            }
        }
    }
}

class Path2dRenderer {
    constructor(svgData, offscreenCanvas) {
        this.data = svgData.map(([pathString, fillColor]) => [new Path2D(pathString), fillColor]);

        this.ctx = offscreenCanvas.getContext('2d');
    }

    render(x, y) {
        const ctx = this.ctx;

        ctx.clearRect(0, 0, 500, 500);

        for (let xo = 0; xo < PATTERN_BOUNDS; xo += PATTERN_SPACING) {
            for (let yo = 0; yo < PATTERN_BOUNDS; yo += PATTERN_SPACING) {
                ctx.save();
                ctx.translate(x + xo, y + yo);

                for (const [path, fillColor] of this.data) {
                    ctx.fillStyle = fillColor;
                    ctx.fill(path);
                }
                ctx.restore();
            }
        }
    }
}

class CanvasKitRenderer {
    constructor(svgData, offscreenCanvas, CanvasKit) {
        this.CanvasKit = CanvasKit;
        this.data = svgData.map(([pathString, fillColor]) => [
            CanvasKit.Path.MakeFromSVGString(pathString),
            CanvasKit.parseColorString(fillColor)
        ]);

        this.surface = CanvasKit.MakeWebGLCanvasSurface(offscreenCanvas, null);
        if (!this.surface) {
            throw 'Could not make canvas surface';
        }
        this.canvas = this.surface.getCanvas();

        this.paint = new CanvasKit.Paint();
        this.paint.setAntiAlias(true);
        this.paint.setStyle(CanvasKit.PaintStyle.Fill);
    }

    render(x, y) {
        const canvas = this.canvas;

        canvas.clear(this.CanvasKit.WHITE);

        for (let xo = 0; xo < PATTERN_BOUNDS; xo += PATTERN_SPACING) {
            for (let yo = 0; yo < PATTERN_BOUNDS; yo += PATTERN_SPACING) {
                canvas.save();
                canvas.translate(x + xo, y + yo);

                for (const [path, color] of this.data) {
                    this.paint.setColor(color);
                    canvas.drawPath(path, this.paint);
                }
                canvas.restore();
            }
        }
        this.surface.flush();
    }
}