import { assert, unreachable } from '../../../common/util/util.js';

import { runRefTest } from './gpu_ref_test.js';

type WriteCanvasMethod = 'draw' | 'copy';

export function run(
  format: GPUTextureFormat,
  alphaMode: GPUCanvasAlphaMode,
  writeCanvasMethod: WriteCanvasMethod
) {
  runRefTest(t => {
    const module = t.device.createShaderModule({
      code: `
struct VertexOutput {
@builtin(position) Position : vec4<f32>,
@location(0) fragColor : vec4<f32>,
}

@vertex
fn mainVS(@builtin(vertex_index) VertexIndex : u32) -> VertexOutput {
var pos = array<vec2<f32>, 6>(
    vec2<f32>( 0.75,  0.75),
    vec2<f32>( 0.75, -0.75),
    vec2<f32>(-0.75, -0.75),
    vec2<f32>( 0.75,  0.75),
    vec2<f32>(-0.75, -0.75),
    vec2<f32>(-0.75,  0.75));

var offset = array<vec2<f32>, 4>(
vec2<f32>( -0.25,  0.25),
vec2<f32>( 0.25, 0.25),
vec2<f32>(-0.25, -0.25),
vec2<f32>( 0.25,  -0.25));

// Alpha channel value is set to 0.5 regardless of the canvas alpha mode.
// For 'opaque' mode, it shouldn't affect the end result, as the alpha channel should always get cleared to 1.0.
var color = array<vec4<f32>, 4>(
    vec4<f32>(0.4, 0.0, 0.0, 0.5),
    vec4<f32>(0.0, 0.4, 0.0, 0.5),
    vec4<f32>(0.0, 0.0, 0.4, 0.5),
    vec4<f32>(0.4, 0.4, 0.0, 0.5)); // 0.4 -> 0x66

var output : VertexOutput;
output.Position = vec4<f32>(pos[VertexIndex % 6u] + offset[VertexIndex / 6u], 0.0, 1.0);
output.fragColor = color[VertexIndex / 6u];
return output;
}

@fragment
fn mainFS(@location(0) fragColor: vec4<f32>) -> @location(0) vec4<f32> {
return fragColor;
}
      `,
    });

    document.querySelectorAll('canvas').forEach(canvas => {
      const ctx = canvas.getContext('webgpu');
      assert(ctx instanceof GPUCanvasContext, 'Failed to get WebGPU context from canvas');

      switch (format) {
        case 'bgra8unorm':
        case 'bgra8unorm-srgb':
        case 'rgba8unorm':
        case 'rgba8unorm-srgb':
        case 'rgba16float':
          break;
        default:
          unreachable();
      }

      let usage = 0;
      switch (writeCanvasMethod) {
        case 'draw':
          usage = GPUTextureUsage.RENDER_ATTACHMENT;
          break;
        case 'copy':
          usage = GPUTextureUsage.COPY_DST;
          break;
      }
      ctx.configure({
        device: t.device,
        format,
        usage,
        alphaMode,
      });

      // The blending behavior here is to mimic 2d context blending behavior
      // of drawing rects in order
      // https://drafts.fxtf.org/compositing/#porterduffcompositingoperators_srcover
      const kBlendStateSourceOver = {
        color: {
          srcFactor: 'src-alpha',
          dstFactor: 'one-minus-src-alpha',
          operation: 'add',
        },
        alpha: {
          srcFactor: 'one',
          dstFactor: 'one-minus-src-alpha',
          operation: 'add',
        },
      } as const;

      const pipeline = t.device.createRenderPipeline({
        layout: 'auto',
        vertex: {
          module,
          entryPoint: 'mainVS',
        },
        fragment: {
          module,
          entryPoint: 'mainFS',
          targets: [
            {
              format,
              blend: { premultiplied: kBlendStateSourceOver, opaque: undefined }[alphaMode],
            },
          ],
        },
        primitive: {
          topology: 'triangle-list',
        },
      });

      let renderTarget: GPUTexture;
      switch (writeCanvasMethod) {
        case 'draw':
          renderTarget = ctx.getCurrentTexture();
          break;
        case 'copy':
          renderTarget = t.device.createTexture({
            size: [ctx.canvas.width, ctx.canvas.height],
            format,
            usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.COPY_SRC,
          });
          break;
      }
      const renderPassDescriptor: GPURenderPassDescriptor = {
        colorAttachments: [
          {
            view: renderTarget.createView(),
            clearValue: { r: 0.0, g: 0.0, b: 0.0, a: 0.0 },
            loadOp: 'clear',
            storeOp: 'store',
          },
        ],
      };

      const commandEncoder = t.device.createCommandEncoder();
      const passEncoder = commandEncoder.beginRenderPass(renderPassDescriptor);
      passEncoder.setPipeline(pipeline);
      passEncoder.draw(6, 1, 0, 0);
      passEncoder.draw(6, 1, 6, 0);
      passEncoder.draw(6, 1, 12, 0);
      passEncoder.draw(6, 1, 18, 0);
      passEncoder.end();

      switch (writeCanvasMethod) {
        case 'draw':
          break;
        case 'copy':
          commandEncoder.copyTextureToTexture(
            {
              texture: renderTarget,
            },
            {
              texture: ctx.getCurrentTexture(),
            },
            [ctx.canvas.width, ctx.canvas.height]
          );
          break;
      }

      t.device.queue.submit([commandEncoder.finish()]);
    });
  });
}
