/**
 * Provide the necessary methods to get a snapshot of the framebuffer into a render texture.
 * Class meant to be used as a singleton.
 * Created with the precious advices of dev7355608.
 */
class FramebufferSnapshot {
  constructor() {
    /**
     * The RenderTexture that is the render destination for the framebuffer snapshot.
     * @type {PIXI.RenderTexture}
     */
    this.framebufferTexture = FramebufferSnapshot.#createRenderTexture();

    // Listen for resize events
    canvas.app.renderer.on("resize", () => this.#hasResized = true);
  }

  /**
   * To know if we need to update the texture.
   * @type {boolean}
   */
  #hasResized = true;

  /**
   * A placeholder for temporary copy.
   * @type {PIXI.Rectangle}
   */
  #tempSourceFrame = new PIXI.Rectangle();

  /* ---------------------------------------- */

  /**
   * Get the framebuffer texture snapshot.
   * @param {PIXI.Renderer} renderer    The renderer for this context.
   * @returns {PIXI.RenderTexture}      The framebuffer snapshot.
   */
  getFramebufferTexture(renderer) {
    // Need resize?
    if ( this.#hasResized ) {
      CachedContainer.resizeRenderTexture(renderer, this.framebufferTexture);
      this.#hasResized = false;
    }

    // Flush batched operations before anything else
    renderer.batch.flush();

    const fb = renderer.framebuffer.current;
    const vf = this.#tempSourceFrame.copyFrom(renderer.renderTexture.viewportFrame);

    // Inverted Y in the case of canvas
    if ( !fb ) vf.y = renderer.view.height - (vf.y + vf.height);

    // Empty viewport
    if ( !(vf.width > 0 && vf.height > 0) ) return PIXI.Texture.WHITE;

    // Computing bounds of the source
    let srcX = vf.x;
    let srcY = vf.y;
    let srcX2 = srcX + vf.width;
    let srcY2 = srcY + vf.height;

    // Inverted Y in the case of canvas
    if ( !fb ) {
      srcY = renderer.view.height - 1 - srcY;
      srcY2 = srcY - vf.height;
    }

    // Computing bounds of the destination
    let dstX = 0;
    let dstY = 0;
    let dstX2 = vf.width;
    let dstY2 = vf.height;

    // Preparing the gl context
    const gl = renderer.gl;
    const framebufferSys = renderer.framebuffer;
    const currentFramebuffer = framebufferSys.current;

    // Binding our render texture to the framebuffer
    framebufferSys.bind(this.framebufferTexture.framebuffer, framebufferSys.viewport);
    // Current framebuffer is binded as a read framebuffer (to prepare the blit)
    gl.bindFramebuffer(gl.READ_FRAMEBUFFER, fb?.glFramebuffers[framebufferSys.CONTEXT_UID].framebuffer);
    // Blit current framebuffer into our render texture
    gl.blitFramebuffer(srcX, srcY, srcX2, srcY2, dstX, dstY, dstX2, dstY2, gl.COLOR_BUFFER_BIT, gl.NEAREST);
    // Restore original behavior
    framebufferSys.bind(currentFramebuffer, framebufferSys.viewport);

    return this.framebufferTexture;
  }

  /* ---------------------------------------- */

  /**
   * Create a render texture, provide a render method and an optional clear color.
   * @returns {PIXI.RenderTexture}              A reference to the created render texture.
   */
  static #createRenderTexture() {
    const renderer = canvas.app?.renderer;
    return PIXI.RenderTexture.create({
      width: renderer?.screen.width ?? window.innerWidth,
      height: renderer?.screen.height ?? window.innerHeight,
      resolution: renderer.resolution ?? PIXI.settings.RESOLUTION
    });
  }
}
