import { coverRadius, fillRectangle } from "../base/2d";
import { Animator } from "../base/Animator";
import { m4 } from "../base/m4";
import { Disposable, Scene } from "../base/Scene";
import { createProgram, createShader } from "../base/utils";
import { Area, Color } from "./common";
import { FillerData } from "./FillerData";

export class AnimatingArea {
  constructor(
    readonly area: Area,
    readonly x: number,
    readonly y: number,
    public progress: number,
  ) {}
}

export class AnimatableMask implements Disposable {
  private vertexShaderCode = /*glsl*/ `
    attribute vec2 a_position;
    attribute vec2 a_texCoord;
    
    attribute vec4 a_areaId;
    attribute vec2 a_center;
    attribute float a_progress;
    attribute float a_maxRadius;
    
    uniform mat4 u_matrix;
    
    varying vec4 v_areaId;
    varying vec2 v_center;
    varying float v_progress;
    varying float v_maxRadius;
    varying vec2 v_texCoord;
    varying vec2 v_position;
    void main() {
      gl_Position = u_matrix * vec4(a_position, 0, 1);
      v_areaId = a_areaId;
      v_center = a_center;
      v_progress = a_progress;
      v_maxRadius = a_maxRadius;
      v_texCoord = a_texCoord;
      v_position = a_position;
      
    }

  `;

  private fragmentShaderCode = /*glsl*/ `
    precision mediump float;
    uniform sampler2D u_map;
    //uniform sampler2D u_colored;
    //uniform sampler2D u_mask;
    varying vec4 v_areaId;
    varying vec2 v_center;
    varying float v_progress;
    varying float v_maxRadius;
    varying vec2 v_texCoord;
    varying vec2 v_position;
    
    void main() {
        vec4 map = texture2D(u_map, v_texCoord);
        float dist = distance(map, v_areaId);


        /*
        if(dist < 0.001) {

          if(v_progress >= 1.0 ) {
            gl_FragColor = vec4(1, 1, 0, 1);
          }else{
            float dist2 = distance(v_position, v_center);

            if(dist2 < v_maxRadius * v_progress) {
              gl_FragColor = vec4(1, 1, 0, 1);
            }else{
              gl_FragColor = vec4(0, 0, 0, 0);
            }
          }

        }else{
          gl_FragColor = vec4(0, 0, 0, 0);
        }
          */


        //gl_FragColor = vec4(1, 1, 0, 1);
        //gl_FragColor = map;

        vec4 colored = vec4(1, 1, 0, 1);
        
        if(dist < 0.001) {
            if( v_progress < 1.0 ) {
                float dist2 = distance(v_position, v_center);
                float r = v_maxRadius * v_progress + 0.001;
                if(dist2 < r) {
                    float f = dist2 / r;
                    if(f  < v_progress) {
                        gl_FragColor = colored;
                    }else if(v_progress < 1.0){
                        float a = (f - 1.0) / (v_progress  - 1.0);
                        gl_FragColor = vec4(colored.xyz, a);
                    }else{
                        gl_FragColor = colored;
                    }
                }else {
                    gl_FragColor = vec4(0,0,0,0);
                }
            }else{
                gl_FragColor = colored;
            }
        }else{
            gl_FragColor = vec4(0, 0, 0, 0);
        }
    }
  
  `;

  program: WebGLProgram;

  aPositionLoc: number;
  aTexcoordLoc: number;
  aAreaIdLoc: number;
  aCenterLoc: number;
  aProgressLoc: number;
  aMaxRadiusLoc: number;
  uMatrixLoc: WebGLUniformLocation;

  positionBuffer: WebGLBuffer;
  texCoordBuffer: WebGLBuffer;
  areaIdBuffer: WebGLBuffer;
  centerBuffer: WebGLBuffer;
  progressBuffer: WebGLBuffer;
  maxRadiusBuffer: WebGLBuffer;

  dispose(): void {
    let gl = this.scene.gl;

    gl.deleteBuffer(this.positionBuffer);
    gl.deleteBuffer(this.texCoordBuffer);
    gl.deleteBuffer(this.areaIdBuffer);
    gl.deleteBuffer(this.centerBuffer);
    gl.deleteBuffer(this.progressBuffer);
    gl.deleteBuffer(this.maxRadiusBuffer);
    gl.deleteProgram(this.program);
  }

  positionArray: Float32Array;
  texCoordArray: Float32Array;
  areaIdArray: Float32Array;
  centerArray: Float32Array;
  progressArray: Float32Array;
  maxRadiusArray: Float32Array;

  matrix: m4.Matrix4;

  texture: WebGLTexture;
  fb: WebGLFramebuffer;

  private animatingAreas: Array<AnimatingArea> = [];

  get width(): number {
    return this.fillerData.width;
  }
  get height(): number {
    return this.fillerData.height;
  }

  maxCount: number;

  constructor(
    private scene: Scene,
    private fillerData: FillerData,
  ) {
    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.aAreaIdLoc = gl.getAttribLocation(this.program, "a_areaId");
    this.aCenterLoc = gl.getAttribLocation(this.program, "a_center");
    this.aProgressLoc = gl.getAttribLocation(this.program, "a_progress");
    this.aMaxRadiusLoc = gl.getAttribLocation(this.program, "a_maxRadius");
    this.uMatrixLoc = gl.getUniformLocation(this.program, "u_matrix")!;

    // 此处maxCount 等于分组最大区块数并不合理，假设每个颜色分组都是一个区块， 此处maxCount会等于1， 如果用户快速点击多个区块，会造成上一个区块的晕染无法完成
    // this.maxCount = fillerData.data.maxAreaCountOfGroup;
    this.maxCount = 5;

    this.positionBuffer = gl.createBuffer()!;
    this.texCoordBuffer = gl.createBuffer()!;
    this.areaIdBuffer = gl.createBuffer()!;
    this.centerBuffer = gl.createBuffer()!;
    this.progressBuffer = gl.createBuffer()!;
    this.maxRadiusBuffer = gl.createBuffer()!;

    this.matrix = m4.projectionNoflipY(fillerData.width, fillerData.height);

    this.texture = gl.createTexture()!;
    gl.bindTexture(gl.TEXTURE_2D, this.texture);
    gl.texImage2D(
      gl.TEXTURE_2D,
      0,
      gl.RGBA,
      this.fillerData.width,
      this.fillerData.height,
      0,
      gl.RGBA,
      gl.UNSIGNED_BYTE,
      null,
    );
    //gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGB, this.fillerData.width, this.fillerData.height, 0, gl.RGB, gl.UNSIGNED_SHORT_5_6_5, null)

    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
    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);

    this.fb = gl.createFramebuffer()!;
    gl.bindFramebuffer(gl.FRAMEBUFFER, this.fb);
    gl.framebufferTexture2D(
      gl.FRAMEBUFFER,
      gl.COLOR_ATTACHMENT0,
      gl.TEXTURE_2D,
      this.texture,
      0,
    );
    gl.bindFramebuffer(gl.FRAMEBUFFER, null);

    this.positionArray = new Float32Array(this.maxCount * 12);
    this.texCoordArray = new Float32Array(this.maxCount * 12);
    this.areaIdArray = new Float32Array(this.maxCount * 24);
    this.centerArray = new Float32Array(this.maxCount * 12);
    this.progressArray = new Float32Array(this.maxCount * 6);
    this.maxRadiusArray = new Float32Array(this.maxCount * 6);
  }

  addArea(area: Area, cx: number, cy: number, duration = 800) {
    let aa = new AnimatingArea(area, cx, cy, 0);
    let animator = new Animator(
      duration,
      () => {
        aa.progress = animator.value();
      },
      () => {},
    );
    this.scene.addAnimator(animator);
    this.animatingAreas.push(aa);
  }

  fillPoint(
    arr: Float32Array,
    offset: number,
    x: number,
    y: number,
    count: number,
  ) {
    for (var i = 0; i < count; i++) {
      arr[offset + i * 2] = x;
      arr[offset + i * 2 + 1] = y;
    }
  }

  fillNumber(arr: Float32Array, offset: number, n: number, count: number) {
    for (var i = 0; i < count; i++) {
      arr[offset + i] = n;
    }
  }

  flush() {
    console.log("animationAreas.length=", this.animatingAreas.length);

    if (this.animatingAreas.length <= 0) return;

    var vertexOffset = 0;
    var colorOffset = 0;
    var nOffset = 0;

    const width = this.fillerData.width;
    const height = this.fillerData.height;

    for (var i = 0; i < this.animatingAreas.length; i++) {
      var aa = this.animatingAreas[i];
      var area = aa.area;

      fillRectangle(
        this.positionArray,
        vertexOffset,
        area.rect.x,
        area.rect.y,
        area.rect.width,
        area.rect.height,
      );
      fillRectangle(
        this.texCoordArray,
        vertexOffset,
        area.rect.x / width,
        area.rect.y / height,
        area.rect.width / width,
        area.rect.height / height,
      );
      this.fillPoint(this.centerArray, vertexOffset, aa.x, aa.y, 6);

      var color = new Color(area.id);
      color.fillFloatArray(this.areaIdArray, colorOffset, 6);

      var maxRadius = coverRadius(area.rect, aa.x, aa.y);
      // console.log('rect=', area.rect, maxRadius, aa.x, aa.y);
      //maxRadius =

      this.fillNumber(this.progressArray, nOffset, aa.progress, 6);
      this.fillNumber(this.maxRadiusArray, nOffset, maxRadius, 6);

      vertexOffset += 12;
      colorOffset += 24;
      nOffset += 6;
    }

    //console.log('areaIdArray', this.areaIdArray)

    const gl = this.scene.gl;

    gl.bindBuffer(gl.ARRAY_BUFFER, this.positionBuffer);
    gl.bufferData(gl.ARRAY_BUFFER, this.positionArray, gl.STATIC_DRAW);

    gl.bindBuffer(gl.ARRAY_BUFFER, this.texCoordBuffer);
    gl.bufferData(gl.ARRAY_BUFFER, this.texCoordArray, gl.STATIC_DRAW);

    gl.bindBuffer(gl.ARRAY_BUFFER, this.areaIdBuffer);
    gl.bufferData(gl.ARRAY_BUFFER, this.areaIdArray, gl.STATIC_DRAW);

    gl.bindBuffer(gl.ARRAY_BUFFER, this.centerBuffer);
    gl.bufferData(gl.ARRAY_BUFFER, this.centerArray, gl.STATIC_DRAW);

    gl.bindBuffer(gl.ARRAY_BUFFER, this.progressBuffer);
    gl.bufferData(gl.ARRAY_BUFFER, this.progressArray, gl.STATIC_DRAW);

    gl.bindBuffer(gl.ARRAY_BUFFER, this.maxRadiusBuffer);
    gl.bufferData(gl.ARRAY_BUFFER, this.maxRadiusArray, gl.STATIC_DRAW);

    this.draw(this.animatingAreas.length * 6);

    this.animatingAreas = this.animatingAreas.filter((aa) => aa.progress < 1);
  }

  private draw(n: number) {
    const gl = this.scene.gl;

    gl.bindFramebuffer(gl.FRAMEBUFFER, this.fb);

    //console.log('fb=', this.progressArray)

    gl.useProgram(this.program);

    gl.viewport(0, 0, this.fillerData.width, this.fillerData.height);

    gl.enableVertexAttribArray(this.aPositionLoc);
    gl.bindBuffer(gl.ARRAY_BUFFER, this.positionBuffer);
    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.enableVertexAttribArray(this.aAreaIdLoc);
    gl.bindBuffer(gl.ARRAY_BUFFER, this.areaIdBuffer);
    gl.vertexAttribPointer(this.aAreaIdLoc, 4, gl.FLOAT, false, 0, 0);

    gl.enableVertexAttribArray(this.aCenterLoc);
    gl.bindBuffer(gl.ARRAY_BUFFER, this.centerBuffer);
    gl.vertexAttribPointer(this.aCenterLoc, 2, gl.FLOAT, false, 0, 0);

    gl.enableVertexAttribArray(this.aProgressLoc);
    gl.bindBuffer(gl.ARRAY_BUFFER, this.progressBuffer);
    gl.vertexAttribPointer(this.aProgressLoc, 1, gl.FLOAT, false, 0, 0);

    gl.enableVertexAttribArray(this.aMaxRadiusLoc);
    gl.bindBuffer(gl.ARRAY_BUFFER, this.maxRadiusBuffer);
    gl.vertexAttribPointer(this.aMaxRadiusLoc, 1, gl.FLOAT, false, 0, 0);

    gl.uniformMatrix4fv(this.uMatrixLoc, false, this.matrix);

    gl.activeTexture(gl.TEXTURE0);
    gl.bindTexture(gl.TEXTURE_2D, this.fillerData.mapTexure);

    gl.drawArrays(gl.TRIANGLES, 0, n);

    gl.bindFramebuffer(gl.FRAMEBUFFER, null);
  }

  // 恢复上次已填色的部分
  initTask() {
    let taskLisk = this.fillerData.taskList;

    const width = this.fillerData.width;
    const height = this.fillerData.height;

    for (let task of taskLisk) {
      let area = this.fillerData.data.areaHash.get(task);
      if (area) {
        fillRectangle(
          this.positionArray,
          0,
          area.rect.x,
          area.rect.y,
          area.rect.width,
          area.rect.height,
        );
        fillRectangle(
          this.texCoordArray,
          0,
          area.rect.x / width,
          area.rect.y / height,
          area.rect.width / width,
          area.rect.height / height,
        );
        this.fillPoint(this.centerArray, 0, area.center.x, area.center.y, 6);

        var color = new Color(area.id);
        color.fillFloatArray(this.areaIdArray, 0, 6);

        var maxRadius = coverRadius(area.rect, area.center.x, area.center.y);
        // console.log('rect=', area.rect, maxRadius, area.center.x, area.center.y);

        this.fillNumber(this.progressArray, 0, 1, 6);
        this.fillNumber(this.maxRadiusArray, 0, maxRadius, 6);

        const gl = this.scene.gl;

        gl.bindBuffer(gl.ARRAY_BUFFER, this.positionBuffer);
        gl.bufferData(gl.ARRAY_BUFFER, this.positionArray, gl.STATIC_DRAW);

        gl.bindBuffer(gl.ARRAY_BUFFER, this.texCoordBuffer);
        gl.bufferData(gl.ARRAY_BUFFER, this.texCoordArray, gl.STATIC_DRAW);

        gl.bindBuffer(gl.ARRAY_BUFFER, this.areaIdBuffer);
        gl.bufferData(gl.ARRAY_BUFFER, this.areaIdArray, gl.STATIC_DRAW);

        gl.bindBuffer(gl.ARRAY_BUFFER, this.centerBuffer);
        gl.bufferData(gl.ARRAY_BUFFER, this.centerArray, gl.STATIC_DRAW);

        gl.bindBuffer(gl.ARRAY_BUFFER, this.progressBuffer);
        gl.bufferData(gl.ARRAY_BUFFER, this.progressArray, gl.STATIC_DRAW);

        gl.bindBuffer(gl.ARRAY_BUFFER, this.maxRadiusBuffer);
        gl.bufferData(gl.ARRAY_BUFFER, this.maxRadiusArray, gl.STATIC_DRAW);

        this.draw(6);
      }
    }
  }
}
