/**
 * @Author: Caow
 * @Date: 2021-05-01 17:38:47
 */

const __indexOf =
  [].indexOf ||
  function(item) {
    for (let i = 0, l = this.length; i < l; i++) {
      if (i in this && this[i] === item) return i
    }
    return -1
  }

const vertexShaderBlit =
  'attribute vec4 position;\nvarying vec2 texcoord;\nvoid main(){\n    texcoord = position.xy*0.5+0.5;\n    gl_Position = position;\n}'

const fragmentShaderBlit =
  '#ifdef GL_FRAGMENT_PRECISION_HIGH\n    precision highp int;\n    precision highp float;\n#else\n    precision mediump int;\n    precision mediump float;\n#endif\nuniform sampler2D source;\nvarying vec2 texcoord;'

const nukeVendorPrefix = function() {
  let getExtension, getSupportedExtensions, vendorRe, vendors
  if (window.WebGLRenderingContext != null) {
    vendors = ['WEBKIT', 'MOZ', 'MS', 'O']
    vendorRe = /^WEBKIT_(.*)|MOZ_(.*)|MS_(.*)|O_(.*)/
    getExtension = WebGLRenderingContext.prototype.getExtension
    WebGLRenderingContext.prototype.getExtension = function(name) {
      let extobj, match, vendor, _i, _len
      match = name.match(vendorRe)
      if (match !== null) {
        name = match[1]
      }
      extobj = getExtension.call(this, name)
      if (extobj === null) {
        for (_i = 0, _len = vendors.length; _i < _len; _i++) {
          vendor = vendors[_i]
          extobj = getExtension.call(this, vendor + '_' + name)
          if (extobj !== null) {
            return extobj
          }
        }
        return null
      } else {
        return extobj
      }
    }
    getSupportedExtensions =
      WebGLRenderingContext.prototype.getSupportedExtensions
    return (WebGLRenderingContext.prototype.getSupportedExtensions = function() {
      let extension, match, result, supported, _i, _len
      supported = getSupportedExtensions.call(this)
      result = []
      for (_i = 0, _len = supported.length; _i < _len; _i++) {
        extension = supported[_i]
        match = extension.match(vendorRe)
        if (match !== null) {
          extension = match[1]
        }
        if (__indexOf.call(result, extension) < 0) {
          result.push(extension)
        }
      }
      return result
    })
  }
}

const textureFloatShims = function() {
  let checkColorBuffer,
    checkFloatLinear,
    checkSupport,
    checkTexture,
    createSourceCanvas,
    getExtension,
    getSupportedExtensions,
    name,
    shimExtensions,
    shimLookup,
    unshimExtensions,
    unshimLookup,
    _i,
    _len
  createSourceCanvas = function() {
    let canvas, ctx, imageData
    canvas = document.createElement('canvas')
    canvas.width = 2
    canvas.height = 2
    ctx = canvas.getContext('2d')
    imageData = ctx.getImageData(0, 0, 2, 2)
    imageData.data.set(
      new Uint8ClampedArray([
        0,
        0,
        0,
        0,
        255,
        255,
        255,
        255,
        0,
        0,
        0,
        0,
        255,
        255,
        255,
        255
      ])
    )
    ctx.putImageData(imageData, 0, 0)
    return canvas
  }
  createSourceCanvas()
  checkFloatLinear = function(gl, sourceType) {
    let buffer,
      cleanup,
      fragmentShader,
      framebuffer,
      positionLoc,
      program,
      readBuffer,
      result,
      source,
      sourceCanvas,
      sourceLoc,
      target,
      vertexShader,
      vertices
    program = gl.createProgram()
    vertexShader = gl.createShader(gl.VERTEX_SHADER)
    gl.attachShader(program, vertexShader)
    gl.shaderSource(
      vertexShader,
      'attribute vec2 position;\nvoid main(){\n    gl_Position = vec4(position, 0.0, 1.0);\n}'
    )
    gl.compileShader(vertexShader)
    if (!gl.getShaderParameter(vertexShader, gl.COMPILE_STATUS)) {
      throw gl.getShaderInfoLog(vertexShader)
    }
    fragmentShader = gl.createShader(gl.FRAGMENT_SHADER)
    gl.attachShader(program, fragmentShader)
    gl.shaderSource(
      fragmentShader,
      'uniform sampler2D source;\nvoid main(){\n    gl_FragColor = texture2D(source, vec2(1.0, 1.0));\n}'
    )
    gl.compileShader(fragmentShader)
    if (!gl.getShaderParameter(fragmentShader, gl.COMPILE_STATUS)) {
      throw gl.getShaderInfoLog(fragmentShader)
    }
    gl.linkProgram(program)
    if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
      throw gl.getProgramInfoLog(program)
    }
    gl.useProgram(program)
    cleanup = function() {
      gl.deleteShader(fragmentShader)
      gl.deleteShader(vertexShader)
      gl.deleteProgram(program)
      gl.deleteBuffer(buffer)
      gl.deleteTexture(source)
      gl.deleteTexture(target)
      gl.deleteFramebuffer(framebuffer)
      gl.bindBuffer(gl.ARRAY_BUFFER, null)
      gl.useProgram(null)
      gl.bindTexture(gl.TEXTURE_2D, null)
      return gl.bindFramebuffer(gl.FRAMEBUFFER, null)
    }
    target = gl.createTexture()
    gl.bindTexture(gl.TEXTURE_2D, target)
    gl.texImage2D(
      gl.TEXTURE_2D,
      0,
      gl.RGBA,
      2,
      2,
      0,
      gl.RGBA,
      gl.UNSIGNED_BYTE,
      null
    )
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR)
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR)
    framebuffer = gl.createFramebuffer()
    gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer)
    gl.framebufferTexture2D(
      gl.FRAMEBUFFER,
      gl.COLOR_ATTACHMENT0,
      gl.TEXTURE_2D,
      target,
      0
    )
    sourceCanvas = createSourceCanvas()
    source = gl.createTexture()
    gl.bindTexture(gl.TEXTURE_2D, source)
    gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, sourceType, sourceCanvas)
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR)
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR)
    vertices = new Float32Array([1, 1, -1, 1, -1, -1, 1, 1, -1, -1, 1, -1])
    buffer = gl.createBuffer()
    gl.bindBuffer(gl.ARRAY_BUFFER, buffer)
    gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW)
    positionLoc = gl.getAttribLocation(program, 'position')
    sourceLoc = gl.getUniformLocation(program, 'source')
    gl.enableVertexAttribArray(positionLoc)
    gl.vertexAttribPointer(positionLoc, 2, gl.FLOAT, false, 0, 0)
    gl.uniform1i(sourceLoc, 0)
    gl.drawArrays(gl.TRIANGLES, 0, 6)
    readBuffer = new Uint8Array(4 * 4)
    gl.readPixels(0, 0, 2, 2, gl.RGBA, gl.UNSIGNED_BYTE, readBuffer)
    result = Math.abs(readBuffer[0] - 127) < 10
    cleanup()
    return result
  }
  checkTexture = function(gl, targetType) {
    let target
    target = gl.createTexture()
    gl.bindTexture(gl.TEXTURE_2D, target)
    gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 2, 2, 0, gl.RGBA, targetType, null)
    if (gl.getError() === 0) {
      gl.deleteTexture(target)
      return true
    } else {
      gl.deleteTexture(target)
      return false
    }
  }
  checkColorBuffer = function(gl, targetType) {
    let check, framebuffer, target
    target = gl.createTexture()
    gl.bindTexture(gl.TEXTURE_2D, target)
    gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 2, 2, 0, gl.RGBA, targetType, null)
    framebuffer = gl.createFramebuffer()
    gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer)
    gl.framebufferTexture2D(
      gl.FRAMEBUFFER,
      gl.COLOR_ATTACHMENT0,
      gl.TEXTURE_2D,
      target,
      0
    )
    check = gl.checkFramebufferStatus(gl.FRAMEBUFFER)
    gl.deleteTexture(target)
    gl.deleteFramebuffer(framebuffer)
    gl.bindTexture(gl.TEXTURE_2D, null)
    gl.bindFramebuffer(gl.FRAMEBUFFER, null)
    if (check === gl.FRAMEBUFFER_COMPLETE) {
      return true
    } else {
      return false
    }
  }
  shimExtensions = []
  shimLookup = {}
  unshimExtensions = []
  checkSupport = function() {
    let canvas,
      extobj,
      gl,
      halfFloatExt,
      halfFloatTexturing,
      singleFloatExt,
      singleFloatTexturing
    canvas = document.createElement('canvas')
    gl = null
    try {
      gl = canvas.getContext('experimental-webgl')
      if (gl === null) {
        gl = canvas.getContext('webgl')
      }
      // eslint-disable-next-line no-empty
    } catch (_error) {}
    if (gl != null) {
      singleFloatExt = gl.getExtension('OES_texture_float')
      if (singleFloatExt === null) {
        if (checkTexture(gl, gl.FLOAT)) {
          singleFloatTexturing = true
          shimExtensions.push('OES_texture_float')
          shimLookup.OES_texture_float = {
            shim: true
          }
        } else {
          singleFloatTexturing = false
          unshimExtensions.push('OES_texture_float')
        }
      } else {
        if (checkTexture(gl, gl.FLOAT)) {
          singleFloatTexturing = true
          shimExtensions.push('OES_texture_float')
        } else {
          singleFloatTexturing = false
          unshimExtensions.push('OES_texture_float')
        }
      }
      if (singleFloatTexturing) {
        extobj = gl.getExtension('WEBGL_color_buffer_float')
        if (extobj === null) {
          if (checkColorBuffer(gl, gl.FLOAT)) {
            shimExtensions.push('WEBGL_color_buffer_float')
            shimLookup.WEBGL_color_buffer_float = {
              shim: true,
              RGBA32F_EXT: 0x8814,
              RGB32F_EXT: 0x8815,
              FRAMEBUFFER_ATTACHMENT_COMPONENT_TYPE_EXT: 0x8211,
              UNSIGNED_NORMALIZED_EXT: 0x8c17
            }
          } else {
            unshimExtensions.push('WEBGL_color_buffer_float')
          }
        } else {
          if (checkColorBuffer(gl, gl.FLOAT)) {
            shimExtensions.push('WEBGL_color_buffer_float')
          } else {
            unshimExtensions.push('WEBGL_color_buffer_float')
          }
        }
        extobj = gl.getExtension('OES_texture_float_linear')
        if (extobj === null) {
          if (checkFloatLinear(gl, gl.FLOAT)) {
            shimExtensions.push('OES_texture_float_linear')
            shimLookup.OES_texture_float_linear = {
              shim: true
            }
          } else {
            unshimExtensions.push('OES_texture_float_linear')
          }
        } else {
          if (checkFloatLinear(gl, gl.FLOAT)) {
            shimExtensions.push('OES_texture_float_linear')
          } else {
            unshimExtensions.push('OES_texture_float_linear')
          }
        }
      }
      halfFloatExt = gl.getExtension('OES_texture_half_float')
      if (halfFloatExt === null) {
        if (checkTexture(gl, 0x8d61)) {
          halfFloatTexturing = true
          shimExtensions.push('OES_texture_half_float')
          halfFloatExt = shimLookup.OES_texture_half_float = {
            HALF_FLOAT_OES: 0x8d61,
            shim: true
          }
        } else {
          halfFloatTexturing = false
          unshimExtensions.push('OES_texture_half_float')
        }
      } else {
        if (checkTexture(gl, halfFloatExt.HALF_FLOAT_OES)) {
          halfFloatTexturing = true
          shimExtensions.push('OES_texture_half_float')
        } else {
          halfFloatTexturing = false
          unshimExtensions.push('OES_texture_half_float')
        }
      }
      if (halfFloatTexturing) {
        extobj = gl.getExtension('EXT_color_buffer_half_float')
        if (extobj === null) {
          if (checkColorBuffer(gl, halfFloatExt.HALF_FLOAT_OES)) {
            shimExtensions.push('EXT_color_buffer_half_float')
            shimLookup.EXT_color_buffer_half_float = {
              shim: true,
              RGBA16F_EXT: 0x881a,
              RGB16F_EXT: 0x881b,
              FRAMEBUFFER_ATTACHMENT_COMPONENT_TYPE_EXT: 0x8211,
              UNSIGNED_NORMALIZED_EXT: 0x8c17
            }
          } else {
            unshimExtensions.push('EXT_color_buffer_half_float')
          }
        } else {
          if (checkColorBuffer(gl, halfFloatExt.HALF_FLOAT_OES)) {
            shimExtensions.push('EXT_color_buffer_half_float')
          } else {
            unshimExtensions.push('EXT_color_buffer_half_float')
          }
        }
        extobj = gl.getExtension('OES_texture_half_float_linear')
        if (extobj === null) {
          if (checkFloatLinear(gl, halfFloatExt.HALF_FLOAT_OES)) {
            shimExtensions.push('OES_texture_half_float_linear')
            return (shimLookup.OES_texture_half_float_linear = {
              shim: true
            })
          } else {
            return unshimExtensions.push('OES_texture_half_float_linear')
          }
        } else {
          if (checkFloatLinear(gl, halfFloatExt.HALF_FLOAT_OES)) {
            return shimExtensions.push('OES_texture_half_float_linear')
          } else {
            return unshimExtensions.push('OES_texture_half_float_linear')
          }
        }
      }
    }
  }
  if (window.WebGLRenderingContext != null) {
    checkSupport()
    unshimLookup = {}
    for (_i = 0, _len = unshimExtensions.length; _i < _len; _i++) {
      name = unshimExtensions[_i]
      unshimLookup[name] = true
    }
    getExtension = WebGLRenderingContext.prototype.getExtension
    WebGLRenderingContext.prototype.getExtension = function(name) {
      let extobj
      extobj = shimLookup[name]
      if (extobj === void 0) {
        if (unshimLookup[name]) {
          return null
        } else {
          return getExtension.call(this, name)
        }
      } else {
        return extobj
      }
    }
    getSupportedExtensions =
      WebGLRenderingContext.prototype.getSupportedExtensions
    WebGLRenderingContext.prototype.getSupportedExtensions = function() {
      let extension, result, supported, _j, _k, _len1, _len2
      supported = getSupportedExtensions.call(this)
      result = []
      for (_j = 0, _len1 = supported.length; _j < _len1; _j++) {
        extension = supported[_j]
        if (unshimLookup[extension] === void 0) {
          result.push(extension)
        }
      }
      for (_k = 0, _len2 = shimExtensions.length; _k < _len2; _k++) {
        extension = shimExtensions[_k]
        if (__indexOf.call(result, extension) < 0) {
          result.push(extension)
        }
      }
      return result
    }
    return (WebGLRenderingContext.prototype.getFloatExtension = function(spec) {
      let candidate,
        candidates,
        half,
        halfFramebuffer,
        halfLinear,
        halfTexture,
        i,
        importance,
        preference,
        result,
        single,
        singleFramebuffer,
        singleLinear,
        singleTexture,
        use,
        _j,
        _k,
        _l,
        _len1,
        _len2,
        _len3,
        _len4,
        _m,
        _ref,
        _ref1,
        _ref2
      if (spec.prefer == null) {
        spec.prefer = ['half']
      }
      if (spec.require == null) {
        spec.require = []
      }
      if (spec.throws == null) {
        spec.throws = true
      }
      singleTexture = this.getExtension('OES_texture_float')
      halfTexture = this.getExtension('OES_texture_half_float')
      singleFramebuffer = this.getExtension('WEBGL_color_buffer_float')
      halfFramebuffer = this.getExtension('EXT_color_buffer_half_float')
      singleLinear = this.getExtension('OES_texture_float_linear')
      halfLinear = this.getExtension('OES_texture_half_float_linear')
      single = {
        texture: singleTexture !== null,
        filterable: singleLinear !== null,
        renderable: singleFramebuffer !== null,
        score: 0,
        precision: 'single',
        half: false,
        single: true,
        type: this.FLOAT
      }
      half = {
        texture: halfTexture !== null,
        filterable: halfLinear !== null,
        renderable: halfFramebuffer !== null,
        score: 0,
        precision: 'half',
        half: true,
        single: false,
        type:
          (_ref = halfTexture != null ? halfTexture.HALF_FLOAT_OES : void 0) !=
          null
            ? _ref
            : null
      }
      candidates = []
      if (single.texture) {
        candidates.push(single)
      }
      if (half.texture) {
        candidates.push(half)
      }
      result = []
      for (_j = 0, _len1 = candidates.length; _j < _len1; _j++) {
        candidate = candidates[_j]
        use = true
        _ref1 = spec.require
        for (_k = 0, _len2 = _ref1.length; _k < _len2; _k++) {
          name = _ref1[_k]
          if (candidate[name] === false) {
            use = false
          }
        }
        if (use) {
          result.push(candidate)
        }
      }
      for (_l = 0, _len3 = result.length; _l < _len3; _l++) {
        candidate = result[_l]
        _ref2 = spec.prefer
        for (i = _m = 0, _len4 = _ref2.length; _m < _len4; i = ++_m) {
          preference = _ref2[i]
          importance = Math.pow(2, spec.prefer.length - i - 1)
          if (candidate[preference]) {
            candidate.score += importance
          }
        }
      }
      result.sort(function(a, b) {
        if (a.score === b.score) {
          return 0
        } else if (a.score < b.score) {
          return 1
        } else if (a.score > b.score) {
          return -1
        }
      })
      if (result.length === 0) {
        if (spec.throws) {
          throw 'No floating point texture support that is ' +
            spec.require.join(', ')
        } else {
          return null
        }
      } else {
        result = result[0]
        return {
          filterable: result.filterable,
          renderable: result.renderable,
          type: result.type,
          precision: result.precision
        }
      }
    })
  }
}

nukeVendorPrefix()

textureFloatShims()

const Shader = (function() {
  function Shader(gl, _arg) {
    let fragment, vertex
    this.gl = gl
    vertex = _arg.vertex
    fragment = _arg.fragment
    this.program = this.gl.createProgram()
    this.vs = this.gl.createShader(this.gl.VERTEX_SHADER)
    this.fs = this.gl.createShader(this.gl.FRAGMENT_SHADER)
    this.gl.attachShader(this.program, this.vs)
    this.gl.attachShader(this.program, this.fs)
    this.compileShader(this.vs, vertex)
    this.compileShader(this.fs, fragment)
    this.link()
    this.value_cache = {}
    this.uniform_cache = {}
    this.attribCache = {}
  }

  Shader.prototype.attribLocation = function(name) {
    let location = this.attribCache[name]
    if (location === void 0) {
      location = this.attribCache[name] = this.gl.getAttribLocation(
        this.program,
        name
      )
    }
    return location
  }

  Shader.prototype.compileShader = function(shader, source) {
    this.gl.shaderSource(shader, source)
    this.gl.compileShader(shader)
    if (!this.gl.getShaderParameter(shader, this.gl.COMPILE_STATUS)) {
      throw 'Shader Compile Error: ' + this.gl.getShaderInfoLog(shader)
    }
  }

  Shader.prototype.link = function() {
    this.gl.linkProgram(this.program)
    if (!this.gl.getProgramParameter(this.program, this.gl.LINK_STATUS)) {
      throw 'Shader Link Error: ' + this.gl.getProgramInfoLog(this.program)
    }
  }

  Shader.prototype.use = function() {
    this.gl.useProgram(this.program)
    return this
  }

  Shader.prototype.uniformLoc = function(name) {
    let location = this.uniform_cache[name]
    if (location === void 0) {
      location = this.uniform_cache[name] = this.gl.getUniformLocation(
        this.program,
        name
      )
    }
    return location
  }

  Shader.prototype.int = function(name, value) {
    let cached, loc
    cached = this.value_cache[name]
    if (cached !== value) {
      this.value_cache[name] = value
      loc = this.uniformLoc(name)
      if (loc) {
        this.gl.uniform1i(loc, value)
      }
    }
    return this
  }

  Shader.prototype.vec2 = function(name, a, b) {
    let loc
    loc = this.uniformLoc(name)
    if (loc) {
      this.gl.uniform2f(loc, a, b)
    }
    return this
  }

  Shader.prototype.float = function(name, value) {
    let cached, loc
    cached = this.value_cache[name]
    if (cached !== value) {
      this.value_cache[name] = value
      loc = this.uniformLoc(name)
      if (loc) {
        this.gl.uniform1f(loc, value)
      }
    }
    return this
  }

  return Shader
})()

const Framebuffer = (function() {
  function Framebuffer(gl) {
    this.gl = gl
    this.buffer = this.gl.createFramebuffer()
  }

  Framebuffer.prototype.destroy = function() {
    return this.gl.deleteFRamebuffer(this.buffer)
  }

  Framebuffer.prototype.bind = function() {
    this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, this.buffer)
    return this
  }

  Framebuffer.prototype.unbind = function() {
    this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, null)
    return this
  }

  Framebuffer.prototype.check = function() {
    let result
    result = this.gl.checkFramebufferStatus(this.gl.FRAMEBUFFER)
    switch (result) {
      case this.gl.FRAMEBUFFER_UNSUPPORTED:
        throw 'Framebuffer is unsupported'
        // eslint-disable-next-line no-unreachable
        break
      case this.gl.FRAMEBUFFER_INCOMPLETE_ATTACHMENT:
        throw 'Framebuffer incomplete attachment'
        // eslint-disable-next-line no-unreachable
        break
      case this.gl.FRAMEBUFFER_INCOMPLETE_DIMENSIONS:
        throw 'Framebuffer incomplete dimensions'
        // eslint-disable-next-line no-unreachable
        break
      case this.gl.FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT:
        throw 'Framebuffer incomplete missing attachment'
    }
    return this
  }

  Framebuffer.prototype.color = function(texture) {
    this.gl.framebufferTexture2D(
      this.gl.FRAMEBUFFER,
      this.gl.COLOR_ATTACHMENT0,
      texture.target,
      texture.handle,
      0
    )
    this.check()
    return this
  }

  Framebuffer.prototype.depth = function(buffer) {
    this.gl.framebufferRenderbuffer(
      this.gl.FRAMEBUFFER,
      this.gl.DEPTH_ATTACHMENT,
      this.gl.RENDERBUFFER,
      buffer.id
    )
    this.check()
    return this
  }

  Framebuffer.prototype.destroy = function() {
    return this.gl.deleteFramebuffer(this.buffer)
  }

  return Framebuffer
})()

const Texture = (function() {
  function Texture(gl, params) {
    let _ref, _ref1
    this.gl = gl
    if (params == null) {
      params = {}
    }
    this.channels = this.gl[
      ((_ref = params.channels) != null ? _ref : 'rgba').toUpperCase()
    ]
    if (typeof params.type === 'number') {
      this.type = params.type
    } else {
      this.type = this.gl[
        ((_ref1 = params.type) != null ? _ref1 : 'unsigned_byte').toUpperCase()
      ]
    }
    switch (this.channels) {
      case this.gl.RGBA:
        this.chancount = 4
        break
      case this.gl.RGB:
        this.chancount = 3
        break
      case this.gl.LUMINANCE_ALPHA:
        this.chancount = 2
        break
      default:
        this.chancount = 1
    }
    this.target = this.gl.TEXTURE_2D
    this.handle = this.gl.createTexture()
  }

  Texture.prototype.destroy = function() {
    return this.gl.deleteTexture(this.handle)
  }

  Texture.prototype.bind = function(unit) {
    if (unit == null) {
      unit = 0
    }
    if (unit > 15) {
      throw 'Texture unit too large: ' + unit
    }
    this.gl.activeTexture(this.gl.TEXTURE0 + unit)
    this.gl.bindTexture(this.target, this.handle)
    return this
  }

  Texture.prototype.setSize = function(width, height) {
    this.width = width
    this.height = height
    this.gl.texImage2D(
      this.target,
      0,
      this.channels,
      this.width,
      this.height,
      0,
      this.channels,
      this.type,
      null
    )
    return this
  }

  Texture.prototype.upload = function(data) {
    this.width = data.width
    this.height = data.height
    this.gl.texImage2D(
      this.target,
      0,
      this.channels,
      this.channels,
      this.type,
      data
    )
    return this
  }

  Texture.prototype.linear = function() {
    this.gl.texParameteri(
      this.target,
      this.gl.TEXTURE_MAG_FILTER,
      this.gl.LINEAR
    )
    this.gl.texParameteri(
      this.target,
      this.gl.TEXTURE_MIN_FILTER,
      this.gl.LINEAR
    )
    return this
  }

  Texture.prototype.nearest = function() {
    this.gl.texParameteri(
      this.target,
      this.gl.TEXTURE_MAG_FILTER,
      this.gl.NEAREST
    )
    this.gl.texParameteri(
      this.target,
      this.gl.TEXTURE_MIN_FILTER,
      this.gl.NEAREST
    )
    return this
  }

  Texture.prototype.clampToEdge = function() {
    this.gl.texParameteri(
      this.target,
      this.gl.TEXTURE_WRAP_S,
      this.gl.CLAMP_TO_EDGE
    )
    this.gl.texParameteri(
      this.target,
      this.gl.TEXTURE_WRAP_T,
      this.gl.CLAMP_TO_EDGE
    )
    return this
  }

  Texture.prototype.repeat = function() {
    this.gl.texParameteri(this.target, this.gl.TEXTURE_WRAP_S, this.gl.REPEAT)
    this.gl.texParameteri(this.target, this.gl.TEXTURE_WRAP_T, this.gl.REPEAT)
    return this
  }

  return Texture
})()

const Node = (function() {
  function Node(gl, width, height) {
    let floatExt
    this.gl = gl
    this.width = width
    this.height = height
    floatExt = this.gl.getFloatExtension({
      require: ['renderable']
    })
    this.texture = new Texture(this.gl, {
      type: floatExt.type
    })
      .bind(0)
      .setSize(this.width, this.height)
      .nearest()
      .clampToEdge()
    this.fbo = new Framebuffer(this.gl)
      .bind()
      .color(this.texture)
      .unbind()
  }

  Node.prototype.use = function() {
    return this.fbo.bind()
  }

  Node.prototype.bind = function(unit) {
    return this.texture.bind(unit)
  }

  Node.prototype.end = function() {
    return this.fbo.unbind()
  }

  Node.prototype.resize = function(width, height) {
    this.width = width
    this.height = height
    return this.texture.bind(0).setSize(this.width, this.height)
  }

  return Node
})()

const Heights = (function() {
  function Heights(heatmap, gl, width, height) {
    let i, _i, _ref
    this.heatmap = heatmap
    this.gl = gl
    this.width = width
    this.height = height
    this.shader = new Shader(this.gl, {
      vertex:
        'attribute vec4 position, intensity;\nvarying vec2 off, dim;\nvarying float vIntensity;\nuniform vec2 viewport;\n\nvoid main(){\n    dim = abs(position.zw);\n    off = position.zw;\n    vec2 pos = position.xy + position.zw;\n    vIntensity = intensity.x;\n    gl_Position = vec4((pos/viewport)*2.0-1.0, 0.0, 1.0);\n}',
      fragment:
        '#ifdef GL_FRAGMENT_PRECISION_HIGH\n    precision highp int;\n    precision highp float;\n#else\n    precision mediump int;\n    precision mediump float;\n#endif\nvarying vec2 off, dim;\nvarying float vIntensity;\nvoid main(){\n    float falloff = (1.0 - smoothstep(0.0, 1.0, length(off/dim)));\n    float intensity = falloff*vIntensity;\n    gl_FragColor = vec4(intensity);\n}'
    })
    this.clampShader = new Shader(this.gl, {
      vertex: vertexShaderBlit,
      fragment:
        fragmentShaderBlit +
        'uniform float low, high;\nvoid main(){\n    gl_FragColor = vec4(clamp(texture2D(source, texcoord).rgb, low, high), 1.0);\n}'
    })
    this.multiplyShader = new Shader(this.gl, {
      vertex: vertexShaderBlit,
      fragment:
        fragmentShaderBlit +
        'uniform float value;\nvoid main(){\n    gl_FragColor = vec4(texture2D(source, texcoord).rgb*value, 1.0);\n}'
    })
    this.blurShader = new Shader(this.gl, {
      vertex: vertexShaderBlit,
      fragment:
        fragmentShaderBlit +
        'uniform vec2 viewport;\nvoid main(){\n    vec4 result = vec4(0.0);\n    for(int x=-1; x<=1; x++){\n        for(int y=-1; y<=1; y++){\n            vec2 off = vec2(x,y)/viewport;\n            //float factor = 1.0 - smoothstep(0.0, 1.5, length(off));\n            float factor = 1.0;\n            result += vec4(texture2D(source, texcoord+off).rgb*factor, factor);\n        }\n    }\n    gl_FragColor = vec4(result.rgb/result.w, 1.0);\n}'
    })
    this.nodeBack = new Node(this.gl, this.width, this.height)
    this.nodeFront = new Node(this.gl, this.width, this.height)
    this.vertexBuffer = this.gl.createBuffer()
    this.vertexSize = 8
    this.maxPointCount = 1024 * 10
    this.vertexBufferData = new Float32Array(
      this.maxPointCount * this.vertexSize * 6
    )
    this.vertexBufferViews = []
    for (
      i = _i = 0, _ref = this.maxPointCount;
      0 <= _ref ? _i < _ref : _i > _ref;
      i = 0 <= _ref ? ++_i : --_i
    ) {
      this.vertexBufferViews.push(
        new Float32Array(
          this.vertexBufferData.buffer,
          0,
          i * this.vertexSize * 6
        )
      )
    }
    this.bufferIndex = 0
    this.pointCount = 0
  }

  Heights.prototype.resize = function(width, height) {
    this.width = width
    this.height = height
    this.nodeBack.resize(this.width, this.height)
    return this.nodeFront.resize(this.width, this.height)
  }

  Heights.prototype.update = function() {
    var intensityLoc, positionLoc
    if (this.pointCount > 0) {
      this.gl.enable(this.gl.BLEND)
      this.nodeFront.use()
      this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.vertexBuffer)
      this.gl.bufferData(
        this.gl.ARRAY_BUFFER,
        this.vertexBufferViews[this.pointCount],
        this.gl.STREAM_DRAW
      )
      positionLoc = this.shader.attribLocation('position')
      intensityLoc = this.shader.attribLocation('intensity')
      this.gl.enableVertexAttribArray(1)
      this.gl.vertexAttribPointer(
        positionLoc,
        4,
        this.gl.FLOAT,
        false,
        8 * 4,
        0
      )
      this.gl.vertexAttribPointer(
        intensityLoc,
        4,
        this.gl.FLOAT,
        false,
        8 * 4,
        4 * 4
      )
      this.shader.use().vec2('viewport', this.width, this.height)
      this.gl.drawArrays(this.gl.TRIANGLES, 0, this.pointCount * 6)
      this.gl.disableVertexAttribArray(1)
      this.pointCount = 0
      this.bufferIndex = 0
      this.nodeFront.end()
      return this.gl.disable(this.gl.BLEND)
    }
  }

  Heights.prototype.clear = function() {
    this.nodeFront.use()
    this.gl.clearColor(0, 0, 0, 1)
    this.gl.clear(this.gl.COLOR_BUFFER_BIT)
    return this.nodeFront.end()
  }

  Heights.prototype.clamp = function(min, max) {
    this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.heatmap.quad)
    this.gl.vertexAttribPointer(0, 4, this.gl.FLOAT, false, 0, 0)
    this.nodeFront.bind(0)
    this.nodeBack.use()
    this.clampShader
      .use()
      .int('source', 0)
      .float('low', min)
      .float('high', max)
    this.gl.drawArrays(this.gl.TRIANGLES, 0, 6)
    this.nodeBack.end()
    return this.swap()
  }

  Heights.prototype.multiply = function(value) {
    this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.heatmap.quad)
    this.gl.vertexAttribPointer(0, 4, this.gl.FLOAT, false, 0, 0)
    this.nodeFront.bind(0)
    this.nodeBack.use()
    this.multiplyShader
      .use()
      .int('source', 0)
      .float('value', value)
    this.gl.drawArrays(this.gl.TRIANGLES, 0, 6)
    this.nodeBack.end()
    return this.swap()
  }

  Heights.prototype.blur = function() {
    this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.heatmap.quad)
    this.gl.vertexAttribPointer(0, 4, this.gl.FLOAT, false, 0, 0)
    this.nodeFront.bind(0)
    this.nodeBack.use()
    this.blurShader
      .use()
      .int('source', 0)
      .vec2('viewport', this.width, this.height)
    this.gl.drawArrays(this.gl.TRIANGLES, 0, 6)
    this.nodeBack.end()
    return this.swap()
  }

  Heights.prototype.swap = function() {
    let tmp = this.nodeFront
    this.nodeFront = this.nodeBack
    return (this.nodeBack = tmp)
  }

  Heights.prototype.addVertex = function(x, y, xs, ys, intensity) {
    this.vertexBufferData[this.bufferIndex++] = x
    this.vertexBufferData[this.bufferIndex++] = y
    this.vertexBufferData[this.bufferIndex++] = xs
    this.vertexBufferData[this.bufferIndex++] = ys
    this.vertexBufferData[this.bufferIndex++] = intensity
    this.vertexBufferData[this.bufferIndex++] = intensity
    this.vertexBufferData[this.bufferIndex++] = intensity
    return (this.vertexBufferData[this.bufferIndex++] = intensity)
  }

  Heights.prototype.addPoint = function(x, y, size, intensity) {
    let s
    if (size == null) {
      size = 50
    }
    if (intensity == null) {
      intensity = 0.2
    }
    if (this.pointCount >= this.maxPointCount - 1) {
      this.update()
    }
    y = this.height - y
    s = size / 2
    this.addVertex(x, y, -s, -s, intensity)
    this.addVertex(x, y, +s, -s, intensity)
    this.addVertex(x, y, -s, +s, intensity)
    this.addVertex(x, y, -s, +s, intensity)
    this.addVertex(x, y, +s, -s, intensity)
    this.addVertex(x, y, +s, +s, intensity)
    return (this.pointCount += 1)
  }

  return Heights
})()

const WebGLHeatmap = (function() {
  function WebGLHeatmap(_arg) {
    let alphaEnd,
      alphaRange,
      alphaStart,
      error,
      getColorFun,
      gradientTexture,
      image,
      intensityToAlpha,
      output,
      quad,
      textureGradient,
      _ref,
      _ref1
    ;(_ref = _arg != null ? _arg : {}),
      (this.canvas = _ref.canvas),
      (this.width = _ref.width),
      (this.height = _ref.height),
      (intensityToAlpha = _ref.intensityToAlpha),
      (gradientTexture = _ref.gradientTexture),
      (alphaRange = _ref.alphaRange)
    if (!this.canvas) {
      this.canvas = document.createElement('canvas')
    }
    try {
      this.gl = this.canvas.getContext('experimental-webgl', {
        depth: false,
        antialias: false
      })
      if (this.gl === null) {
        this.gl = this.canvas.getContext('webgl', {
          depth: false,
          antialias: false
        })
        if (this.gl === null) {
          throw 'WebGL not supported'
        }
      }
    } catch (_error) {
      error = _error
      throw 'WebGL not supported'
    }
    if (window.WebGLDebugUtils != null) {
      // eslint-disable-next-line no-undef
      this.gl = WebGLDebugUtils.makeDebugContext(this.gl, function(
        err,
        funcName,
        args
      ) {
        // eslint-disable-next-line no-undef
        throw WebGLDebugUtils.glEnumToString(err) +
          ' was caused by call to: ' +
          funcName
      })
    }
    this.gl.enableVertexAttribArray(0)
    this.gl.blendFunc(this.gl.ONE, this.gl.ONE)
    if (gradientTexture) {
      textureGradient = this.gradientTexture = new Texture(this.gl, {
        channels: 'rgba'
      })
        .bind(0)
        .setSize(2, 2)
        .nearest()
        .clampToEdge()
      if (typeof gradientTexture === 'string') {
        image = new Image()
        image.onload = function() {
          return textureGradient.bind().upload(image)
        }
        image.src = gradientTexture
      } else {
        if (gradientTexture.width > 0 && gradientTexture.height > 0) {
          textureGradient.upload(gradientTexture)
        } else {
          gradientTexture.onload = function() {
            return textureGradient.upload(gradientTexture)
          }
        }
      }
      getColorFun =
        'uniform sampler2D gradientTexture;\nvec3 getColor(float intensity){\n    return texture2D(gradientTexture, vec2(intensity, 0.0)).rgb;\n}'
    } else {
      textureGradient = null
      getColorFun =
        'vec3 getColor(float intensity){\n    vec3 blue = vec3(0.0, 0.0, 1.0);\n    vec3 cyan = vec3(0.0, 1.0, 1.0);\n    vec3 green = vec3(0.0, 1.0, 0.0);\n    vec3 yellow = vec3(1.0, 1.0, 0.0);\n    vec3 red = vec3(1.0, 0.0, 0.0);\n\n    vec3 color = (\n        fade(-0.25, 0.25, intensity)*blue +\n        fade(0.0, 0.5, intensity)*cyan +\n        fade(0.25, 0.75, intensity)*green +\n        fade(0.5, 1.0, intensity)*yellow +\n        smoothstep(0.75, 1.0, intensity)*red\n    );\n    return color;\n}'
    }
    if (intensityToAlpha == null) {
      intensityToAlpha = true
    }
    if (intensityToAlpha) {
      ;(_ref1 = alphaRange != null ? alphaRange : [0, 1]),
        (alphaStart = _ref1[0]),
        (alphaEnd = _ref1[1])
      output =
        'vec4 alphaFun(vec3 color, float intensity){\n    float alpha = smoothstep(' +
        alphaStart.toFixed(8) +
        ', ' +
        alphaEnd.toFixed(8) +
        ', intensity);\n    return vec4(color*alpha, alpha);\n}'
    } else {
      output =
        'vec4 alphaFun(vec3 color, float intensity){\n    return vec4(color, 1.0);\n}'
    }
    this.shader = new Shader(this.gl, {
      vertex: vertexShaderBlit,
      fragment:
        fragmentShaderBlit +
        ('float linstep(float low, float high, float value){\n    return clamp((value-low)/(high-low), 0.0, 1.0);\n}\n\nfloat fade(float low, float high, float value){\n    float mid = (low+high)*0.5;\n    float range = (high-low)*0.5;\n    float x = 1.0 - clamp(abs(mid-value)/range, 0.0, 1.0);\n    return smoothstep(0.0, 1.0, x);\n}\n\n' +
          getColorFun +
          '\n' +
          output +
          '\n\nvoid main(){\n    float intensity = smoothstep(0.0, 1.0, texture2D(source, texcoord).r);\n    vec3 color = getColor(intensity);\n    gl_FragColor = alphaFun(color, intensity);\n}')
    })
    if (this.width == null) {
      this.width = this.canvas.offsetWidth || 2
    }
    if (this.height == null) {
      this.height = this.canvas.offsetHeight || 2
    }
    this.canvas.width = this.width
    this.canvas.height = this.height
    this.gl.viewport(0, 0, this.width, this.height)
    this.quad = this.gl.createBuffer()
    this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.quad)
    quad = new Float32Array([
      -1,
      -1,
      0,
      1,
      1,
      -1,
      0,
      1,
      -1,
      1,
      0,
      1,
      -1,
      1,
      0,
      1,
      1,
      -1,
      0,
      1,
      1,
      1,
      0,
      1
    ])
    this.gl.bufferData(this.gl.ARRAY_BUFFER, quad, this.gl.STATIC_DRAW)
    this.gl.bindBuffer(this.gl.ARRAY_BUFFER, null)
    this.heights = new Heights(this, this.gl, this.width, this.height)
  }

  WebGLHeatmap.prototype.adjustSize = function() {
    let canvasHeight, canvasWidth
    canvasWidth = this.canvas.offsetWidth || 2
    canvasHeight = this.canvas.offsetHeight || 2
    if (this.width !== canvasWidth || this.height !== canvasHeight) {
      this.gl.viewport(0, 0, canvasWidth, canvasHeight)
      this.canvas.width = canvasWidth
      this.canvas.height = canvasHeight
      this.width = canvasWidth
      this.height = canvasHeight
      return this.heights.resize(this.width, this.height)
    }
  }

  WebGLHeatmap.prototype.display = function() {
    this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.quad)
    this.gl.vertexAttribPointer(0, 4, this.gl.FLOAT, false, 0, 0)
    this.heights.nodeFront.bind(0)
    if (this.gradientTexture) {
      this.gradientTexture.bind(1)
    }
    this.shader
      .use()
      .int('source', 0)
      .int('gradientTexture', 1)
    return this.gl.drawArrays(this.gl.TRIANGLES, 0, 6)
  }

  WebGLHeatmap.prototype.update = function() {
    return this.heights.update()
  }

  WebGLHeatmap.prototype.clear = function() {
    return this.heights.clear()
  }

  WebGLHeatmap.prototype.clamp = function(min, max) {
    if (min == null) {
      min = 0
    }
    if (max == null) {
      max = 1
    }
    return this.heights.clamp(min, max)
  }

  WebGLHeatmap.prototype.multiply = function(value) {
    if (value == null) {
      value = 0.95
    }
    return this.heights.multiply(value)
  }

  WebGLHeatmap.prototype.blur = function() {
    return this.heights.blur()
  }

  WebGLHeatmap.prototype.addPoint = function(x, y, size, intensity) {
    return this.heights.addPoint(x, y, size, intensity)
  }

  WebGLHeatmap.prototype.addPoints = function(items) {
    let item, _i, _len, _results
    _results = []
    for (_i = 0, _len = items.length; _i < _len; _i++) {
      item = items[_i]
      _results.push(this.addPoint(item.x, item.y, item.size, item.intensity))
    }
    return _results
  }

  return WebGLHeatmap
})()

export function createWebGLHeatmap(params) {
  return new WebGLHeatmap(params)
}
