/*
 * If not stated otherwise in this file or this component's LICENSE file the
 * following copyright and licenses apply:
 *
 * Copyright 2023 Comcast Cable Communications Management, LLC.
 *
 * Licensed under the Apache License, Version 2.0 (the License);
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import { CoreRenderOp } from '../CoreRenderOp.js';
import type { WebGlCtxTexture } from './WebGlCtxTexture.js';
import type { WebGlRenderer } from './WebGlRenderer.js';
import type { BufferCollection } from './internal/BufferCollection.js';
import type { WebGlShaderNode } from './WebGlShaderNode.js';
import type { QuadOptions } from '../CoreRenderer.js';
import type { CoreTextNode } from '../../CoreTextNode.js';
import type { RectWithValid } from '../../lib/utils.js';
import type { Dimensions } from '../../../common/CommonTypes.js';

type ReqQuad =
  | 'alpha'
  | 'shader'
  | 'parentHasRenderTexture'
  | 'rtt'
  | 'clippingRect'
  | 'height'
  | 'width';
type RenderOpQuadOptions = Pick<QuadOptions, ReqQuad> &
  Partial<Omit<QuadOptions, ReqQuad>> & {
    sdfShaderProps?: Record<string, unknown>;
    sdfBuffers?: BufferCollection;
  };

/**
 * Can render multiple quads with multiple textures (up to vertex shader texture limit)
 *
 */
export class WebGlRenderOp extends CoreRenderOp {
  numQuads = 0;
  textures: WebGlCtxTexture[] = [];

  /**
   * need to improve this when TextRenderers are refactored
   */
  readonly sdfShaderProps: Record<string, unknown> | undefined;
  readonly sdfNode: CoreTextNode | undefined;
  readonly maxTextures: number;
  readonly buffers: BufferCollection;
  readonly shader: WebGlShaderNode;
  readonly width: number;
  readonly height: number;
  readonly clippingRect: RectWithValid;
  readonly rtt: boolean;
  readonly parentHasRenderTexture: boolean;
  readonly framebufferDimensions?: Dimensions | null;
  readonly alpha: number;
  readonly pixelRatio: number;

  constructor(
    readonly renderer: WebGlRenderer,
    quad: RenderOpQuadOptions,
    readonly bufferIdx: number,
  ) {
    super();
    this.buffers = quad.sdfBuffers || renderer.quadBufferCollection;
    this.shader = quad.shader as WebGlShaderNode;
    this.width = quad.width;
    this.height = quad.height;
    this.clippingRect = quad.clippingRect;
    this.parentHasRenderTexture = quad.parentHasRenderTexture;
    this.framebufferDimensions = quad.framebufferDimensions;
    this.rtt = quad.rtt;
    this.alpha = quad.alpha;
    this.pixelRatio =
      this.parentHasRenderTexture === true ? 1 : renderer.stage.pixelRatio;

    /**
     * related to line 51
     */
    this.sdfShaderProps = quad.sdfShaderProps;

    this.maxTextures = this.shader.program.supportsIndexedTextures
      ? (renderer.glw.getParameter(
          renderer.glw.MAX_VERTEX_TEXTURE_IMAGE_UNITS,
        ) as number)
      : 1;
  }

  addTexture(texture: WebGlCtxTexture): number {
    const { textures, maxTextures } = this;
    let existingIdx = -1;
    const texturesLength = textures.length;
    for (let i = 0; i < texturesLength; i++) {
      const t = textures[i];
      if (t === texture) {
        existingIdx = i;
        break;
      }
    }

    if (existingIdx !== -1) {
      return existingIdx;
    }

    if (texturesLength >= maxTextures) {
      return 0xffffffff;
    }
    this.textures.push(texture);
    return texturesLength;
  }

  draw() {
    const { glw, options, stage } = this.renderer;

    stage.shManager.useShader(this.shader.program);
    this.shader.program.bindRenderOp(this);

    // TODO: Reduce calculations required
    const quadIdx = (this.bufferIdx / 32) * 6 * 2;
    // Clipping
    if (this.clippingRect.valid === true) {
      const clipX = Math.round(this.clippingRect.x * this.pixelRatio);
      const clipWidth = Math.round(this.clippingRect.width * this.pixelRatio);
      const clipHeight = Math.round(this.clippingRect.height * this.pixelRatio);
      let clipY = Math.round(
        options.canvas.height -
          clipHeight -
          this.clippingRect.y * this.pixelRatio,
      );
      // if parent has render texture, we need to adjust the scissor rect
      // to be relative to the parent's framebuffer
      if (this.parentHasRenderTexture) {
        clipY = this.framebufferDimensions
          ? this.framebufferDimensions.height - this.height
          : 0;
      }

      glw.setScissorTest(true);
      glw.scissor(clipX, clipY, clipWidth, clipHeight);
    } else {
      glw.setScissorTest(false);
    }

    glw.drawElements(
      glw.TRIANGLES,
      6 * this.numQuads,
      glw.UNSIGNED_SHORT,
      quadIdx,
    );
  }
}
