import { fillRectangle, rectangle, rectangleArray } from "../base/2d";
import { LayerAB, Scene } from "../base/Scene";
import { createProgram, createShader } from "../base/utils";
import { AreaGroup, Center, Color, createTexture, TexImage } from "./common";
import { FillerData, FillerDataListener } from "./FillerData";
import { Mask } from "./Mask";

export class HintLayer extends LayerAB implements FillerDataListener {
  private vertexShaderCode = /*glsl*/ `
    attribute vec2 a_position;
    attribute vec2 a_texCoord;
    uniform mat4 u_matrix;
    varying vec2 v_texCoord;

    void main() {
      gl_Position = u_matrix * vec4(a_position, 0, 1);
      v_texCoord = a_texCoord;
    }

  `;

  private fragmentShaderCode = /*glsl*/ `
    precision mediump float;
    uniform sampler2D u_hint;
    uniform sampler2D u_mask;
    uniform vec2 u_pixelSize;
    uniform vec4 u_scale;
    uniform vec4 u_colorDark;
    uniform vec4 u_colorLight;
    
    varying vec2 v_texCoord;


    const float w1 = 0.147761;
    const float w2 = 0.118318; 
    const float w3 = 0.0947416; 
    
    
    vec4 GaussianBlur(in sampler2D image, in vec2 texCoord, in vec2 pixelSize) {
      vec4 C00 = texture2D(image, texCoord + vec2(-pixelSize.x, -pixelSize.y)) * w3 ;
      vec4 C01 = texture2D(image, texCoord + vec2(0.0, -pixelSize.y)) * w2;
      vec4 C02 = texture2D(image, texCoord + vec2(pixelSize.x, -pixelSize.y)) * w3 ;
    
      vec4 C10 = texture2D(image, texCoord + vec2(-pixelSize.x, 0.0)) * w2;
      vec4 C11 = texture2D(image, texCoord + vec2(0.0, 0.0)) * w1;
      vec4 C12 = texture2D(image, texCoord + vec2(pixelSize.x, 0.0)) * w2;
    
      vec4 C20 = texture2D(image, texCoord + vec2(-pixelSize.x, pixelSize.y)) * w3;
      vec4 C21 = texture2D(image, texCoord + vec2(0.0, pixelSize.y)) * w2;
      vec4 C22 = texture2D(image, texCoord + vec2(pixelSize.x, pixelSize.y)) * w3;
    
      return 
        C00 + C01 + C02 +
        C10 + C11 + C12 +
        C20 + C21 + C22 ;
    }


    void main() {
      vec4 hint = texture2D(u_hint, v_texCoord);
      vec4 mask = GaussianBlur(u_mask, v_texCoord, u_pixelSize);
      if(mask.r >= 0.4) {
          //gl_FragColor = hint;
            if(hint.r > 0.5) {
                gl_FragColor = u_colorDark;
                //gl_FragColor = vec4(1, 0, 0, 1);
            }else{
                gl_FragColor = u_colorLight;
                //gl_FragColor = vec4(0, 1, 0, 1);
            }
      }else {
            gl_FragColor = vec4(0,0,0,0);
      }
    }
  
  `;

  program: WebGLProgram;

  aPositionLoc: number;
  aTexcoordLoc: number;
  uMatrixLoc: WebGLUniformLocation;
  uScaleLoc: WebGLUniformLocation;
  uPixelSizeLoc: WebGLUniformLocation;
  uHintLoc: WebGLUniformLocation;
  uMaskLoc: WebGLUniformLocation;
  uColorDarkLoc: WebGLUniformLocation;
  uColorLightLoc: WebGLUniformLocation;

  vertexBuffer: WebGLBuffer;
  texcoordBuffer: WebGLBuffer;

  vertexArray: Float32Array;
  texCoordArray: Float32Array;

  mask: Mask;
  hintTexture: WebGLTexture;

  colorDarkArray = new Float32Array(4);
  colorLightArray = new Float32Array(4);

  override dispose() {
    this.mask.dispose();
    let gl = this.scene.gl;
    gl.deleteProgram(this.program);
    gl.deleteBuffer(this.vertexBuffer);
    gl.deleteBuffer(this.texcoordBuffer);
    gl.deleteTexture(this.hintTexture);
  }

  constructor(
    public readonly scene: Scene,
    private fillerData: FillerData,
  ) {
    super();

    this.fillerData.addListener(this);

    const gl = scene.gl;

    this.program = createProgram(
      gl,
      createShader(gl, gl.VERTEX_SHADER, this.vertexShaderCode)!,
      createShader(gl, gl.FRAGMENT_SHADER, this.fragmentShaderCode)!,
    )!;

    this.aPositionLoc = gl.getAttribLocation(this.program, "a_position");
    this.aTexcoordLoc = gl.getAttribLocation(this.program, "a_texCoord");
    this.uMatrixLoc = gl.getUniformLocation(this.program, "u_matrix")!;

    this.uScaleLoc = gl.getUniformLocation(this.program, "u_scale")!;
    this.uPixelSizeLoc = gl.getUniformLocation(this.program, "u_pixelSize")!;
    this.uHintLoc = gl.getUniformLocation(this.program, "u_hint")!;
    this.uMaskLoc = gl.getUniformLocation(this.program, "u_mask")!;
    this.uColorDarkLoc = gl.getUniformLocation(this.program, "u_colorDark")!;
    this.uColorLightLoc = gl.getUniformLocation(this.program, "u_colorLight")!;

    this.vertexArray = rectangleArray(
      0,
      0,
      fillerData.width,
      fillerData.height,
    );
    this.texCoordArray = rectangleArray(0, 0, 1, 1);

    this.vertexBuffer = gl.createBuffer()!;
    gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer);
    gl.bufferData(gl.ARRAY_BUFFER, this.vertexArray, gl.STATIC_DRAW);

    this.texcoordBuffer = gl.createBuffer()!;
    gl.bindBuffer(gl.ARRAY_BUFFER, this.texcoordBuffer);
    gl.bufferData(gl.ARRAY_BUFFER, this.texCoordArray, gl.STATIC_DRAW);

    this.mask = new Mask(gl, fillerData);
    this.hintTexture = this.createHintTexture(
      gl,
      fillerData.width,
      fillerData.height,
    );

    if (this.fillerData.currentGroup) {
      this.setGroup(this.fillerData.currentGroup);
    }
  }

  createHintTexture(
    gl: WebGL2RenderingContext,
    width: number,
    height: number,
  ): WebGLTexture {
    let texture = gl.createTexture()!;
    gl.bindTexture(gl.TEXTURE_2D, texture);

    const size = 10;
    const nx = Math.floor(width / size);
    const ny = Math.floor(height / size);
    const pixels = new Uint8Array(nx * ny);
    for (var y = 0; y < ny; y++) {
      let k = y % 2 == 0;
      for (var x = 0; x < nx; x++) {
        k = !k;
        let index = y * nx + x;
        pixels[index] = k ? 0 : 255;
      }
    }

    gl.pixelStorei(gl.UNPACK_ALIGNMENT, 1);
    gl.texImage2D(
      gl.TEXTURE_2D,
      0,
      gl.LUMINANCE,
      nx,
      ny,
      0,
      gl.LUMINANCE,
      gl.UNSIGNED_BYTE,
      pixels,
    );
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);

    return texture;
  }

  /** replay 阶段设为 false，隐藏 hint 覆盖层 */
  public enabled: boolean = true;

  setGroup(group: AreaGroup) {
    for (var i = 0; i < group.areas.length; i++) {
      this.mask.addArea(group.areas[i]);
    }
    this.mask.flush(true);
    this.scene.invalidate();
  }

  onGroupChange(group: AreaGroup | null): void {
    console.log("onGroupChange", group);

    if (group != null) this.setGroup(group);

    this.fillerData.callback?.onSwitchGroup();
  }

  // 注释掉，不允许随意点击自动切换group
  // override tap(cx: number, cy: number, sx: number, sy: number): void {
  //   let area = this.fillerData.getArea(cx, cy, 40, this.fillerData.data.areaHash)
  //   if (area != null) {
  //     let group = this.fillerData.data.groupHash.get(area.id)
  //     // this.setGroup(group!)
  //     this.fillerData.currentGroup = group as AreaGroup
  //   }
  // }

  override draw() {
    if (!this.enabled) return;

    const gl = this.scene.gl;
    gl.useProgram(this.program);

    gl.enableVertexAttribArray(this.aPositionLoc);
    gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer);
    gl.vertexAttribPointer(this.aPositionLoc, 2, gl.FLOAT, false, 0, 0);

    gl.enableVertexAttribArray(this.aTexcoordLoc);
    gl.bindBuffer(gl.ARRAY_BUFFER, this.texcoordBuffer);
    gl.vertexAttribPointer(this.aTexcoordLoc, 2, gl.FLOAT, false, 0, 0);

    gl.uniformMatrix4fv(this.uMatrixLoc, false, this.scene.drawMatrix);
    gl.uniform1f(this.uScaleLoc, this.scene.userMat[0]);
    gl.uniform2f(this.uPixelSizeLoc, 1 / this.mask.width, 1 / this.mask.height);

    new Color(this.fillerData.config.hintColorDark).fillFloatArray(
      this.colorDarkArray,
      0,
    );
    new Color(this.fillerData.config.hintColorLight).fillFloatArray(
      this.colorLightArray,
      0,
    );
    gl.uniform4fv(this.uColorDarkLoc, this.colorDarkArray);
    gl.uniform4fv(this.uColorLightLoc, this.colorLightArray);

    gl.uniform1i(this.uHintLoc, 0); // texture unit 0
    gl.uniform1i(this.uMaskLoc, 1); // texture unit 1

    gl.activeTexture(gl.TEXTURE0);
    gl.bindTexture(gl.TEXTURE_2D, this.hintTexture);
    gl.activeTexture(gl.TEXTURE1);
    gl.bindTexture(gl.TEXTURE_2D, this.mask.texture);

    gl.drawArrays(gl.TRIANGLES, 0, 6);
  }

  toString(): string {
    return `HintLayer()`;
  }
}
