import Pass from 'claygl/src/compositor/Pass';
import Geometry from 'claygl/src/Geometry';
import Mesh from 'claygl/src/Mesh';
import Material from 'claygl/src/Material';
import Shader from 'claygl/src/Shader';
import Texture2D from 'claygl/src/Texture2D';
import Texture from 'claygl/src/Texture';
import OrthoCamera from 'claygl/src/camera/Orthographic';
import PlaneGeometry from 'claygl/src/geometry/Plane';
import FrameBuffer from 'claygl/src/FrameBuffer';
import Line2DGeometry from './Line2D'; // import TemporalSS from '../../effect/TemporalSuperSampling';

import vectorFieldParticleGLSL from './vectorFieldParticle.glsl.js';
Shader['import'](vectorFieldParticleGLSL);

function createSpriteCanvas(size) {
  var canvas = document.createElement('canvas');
  canvas.width = canvas.height = size;
  var ctx = canvas.getContext('2d');
  ctx.fillStyle = '#fff';
  ctx.arc(size / 2, size / 2, size / 2, 0, Math.PI * 2);
  ctx.fill();
  return canvas;
} // import spriteUtil from '../../util/sprite';


var VectorFieldParticleSurface = function () {
  /**
   * @type {number}
   */
  this.motionBlurFactor = 0.99;
  /**
   * Vector field lookup image
   * @type {clay.Texture2D}
   */

  this.vectorFieldTexture = new Texture2D({
    type: Texture.FLOAT,
    // minFilter: Texture.NEAREST,
    // magFilter: Texture.NEAREST,
    flipY: false
  });
  /**
   * Particle life range
   * @type {Array.<number>}
   */

  this.particleLife = [5, 20];
  this._particleType = 'point';
  /**
   * @type {number}
   */

  this._particleSize = 1;
  /**
   * @type {Array.<number>}
   */

  this.particleColor = [1, 1, 1, 1];
  /**
   * @type {number}
   */

  this.particleSpeedScaling = 1.0;
  /**
   * @type {clay.Texture2D}
   */

  this._thisFrameTexture = null;
  this._particlePass = null;
  this._spawnTexture = null;
  this._particleTexture0 = null;
  this._particleTexture1 = null;
  this._particlePointsMesh = null;
  this._surfaceFrameBuffer = null;
  this._elapsedTime = 0.0;
  this._scene = null;
  this._camera = null;
  this._lastFrameTexture = null; // this._temporalSS = new TemporalSS(50);
  // this._antialising = false;

  this._supersampling = 1;
  this._downsampleTextures = [];
  this._width = 512;
  this._height = 512;
  this.init();
};

VectorFieldParticleSurface.prototype = {
  constructor: VectorFieldParticleSurface,
  init: function () {
    var parameters = {
      type: Texture.FLOAT,
      minFilter: Texture.NEAREST,
      magFilter: Texture.NEAREST,
      useMipmap: false
    };
    this._spawnTexture = new Texture2D(parameters);
    this._particleTexture0 = new Texture2D(parameters);
    this._particleTexture1 = new Texture2D(parameters);
    this._frameBuffer = new FrameBuffer({
      depthBuffer: false
    });
    this._particlePass = new Pass({
      fragment: Shader.source('ecgl.vfParticle.particle.fragment')
    });

    this._particlePass.setUniform('velocityTexture', this.vectorFieldTexture);

    this._particlePass.setUniform('spawnTexture', this._spawnTexture);

    this._downsamplePass = new Pass({
      fragment: Shader.source('clay.compositor.downsample')
    });
    var particlePointsMesh = new Mesh({
      // Render after last frame full quad
      renderOrder: 10,
      material: new Material({
        shader: new Shader(Shader.source('ecgl.vfParticle.renderPoints.vertex'), Shader.source('ecgl.vfParticle.renderPoints.fragment'))
      }),
      mode: Mesh.POINTS,
      geometry: new Geometry({
        dynamic: true,
        mainAttribute: 'texcoord0'
      })
    });
    var particleLinesMesh = new Mesh({
      // Render after last frame full quad
      renderOrder: 10,
      material: new Material({
        shader: new Shader(Shader.source('ecgl.vfParticle.renderLines.vertex'), Shader.source('ecgl.vfParticle.renderLines.fragment'))
      }),
      geometry: new Line2DGeometry(),
      culling: false
    });
    var lastFrameFullQuad = new Mesh({
      material: new Material({
        shader: new Shader(Shader.source('ecgl.color.vertex'), Shader.source('ecgl.color.fragment')) // DO NOT BLEND Blend will multiply alpha
        // transparent: true

      }),
      geometry: new PlaneGeometry()
    });
    lastFrameFullQuad.material.enableTexture('diffuseMap');
    this._particlePointsMesh = particlePointsMesh;
    this._particleLinesMesh = particleLinesMesh;
    this._lastFrameFullQuadMesh = lastFrameFullQuad;
    this._camera = new OrthoCamera();
    this._thisFrameTexture = new Texture2D();
    this._lastFrameTexture = new Texture2D();
  },
  setParticleDensity: function (width, height) {
    var nVertex = width * height;
    var spawnTextureData = new Float32Array(nVertex * 4);
    var off = 0;
    var lifeRange = this.particleLife;

    for (var i = 0; i < width; i++) {
      for (var j = 0; j < height; j++, off++) {
        // x position, range [0 - 1]
        spawnTextureData[off * 4] = Math.random(); // y position, range [0 - 1]

        spawnTextureData[off * 4 + 1] = Math.random(); // Some property

        spawnTextureData[off * 4 + 2] = Math.random();
        var life = (lifeRange[1] - lifeRange[0]) * Math.random() + lifeRange[0]; // Particle life

        spawnTextureData[off * 4 + 3] = life;
      }
    }

    if (this._particleType === 'line') {
      this._setLineGeometry(width, height);
    } else {
      this._setPointsGeometry(width, height);
    }

    this._spawnTexture.width = width;
    this._spawnTexture.height = height;
    this._spawnTexture.pixels = spawnTextureData;
    this._particleTexture0.width = this._particleTexture1.width = width;
    this._particleTexture0.height = this._particleTexture1.height = height;

    this._particlePass.setUniform('textureSize', [width, height]);
  },
  _setPointsGeometry: function (width, height) {
    var nVertex = width * height;
    var geometry = this._particlePointsMesh.geometry;
    var attributes = geometry.attributes;
    attributes.texcoord0.init(nVertex);
    var off = 0;

    for (var i = 0; i < width; i++) {
      for (var j = 0; j < height; j++, off++) {
        attributes.texcoord0.value[off * 2] = i / width;
        attributes.texcoord0.value[off * 2 + 1] = j / height;
      }
    }

    geometry.dirty();
  },
  _setLineGeometry: function (width, height) {
    var nLine = width * height;

    var geometry = this._getParticleMesh().geometry;

    geometry.setLineCount(nLine);
    geometry.resetOffset();

    for (var i = 0; i < width; i++) {
      for (var j = 0; j < height; j++) {
        geometry.addLine([i / width, j / height]);
      }
    }

    geometry.dirty();
  },
  _getParticleMesh: function () {
    return this._particleType === 'line' ? this._particleLinesMesh : this._particlePointsMesh;
  },
  update: function (renderer, api, deltaTime, firstFrame) {
    var particleMesh = this._getParticleMesh();

    var frameBuffer = this._frameBuffer;
    var particlePass = this._particlePass;

    if (firstFrame) {
      this._updateDownsampleTextures(renderer, api);
    }

    particleMesh.material.set('size', this._particleSize * this._supersampling);
    particleMesh.material.set('color', this.particleColor);
    particlePass.setUniform('speedScaling', this.particleSpeedScaling);
    frameBuffer.attach(this._particleTexture1);
    particlePass.setUniform('firstFrameTime', firstFrame ? (this.particleLife[1] + this.particleLife[0]) / 2 : 0);
    particlePass.setUniform('particleTexture', this._particleTexture0);
    particlePass.setUniform('deltaTime', deltaTime);
    particlePass.setUniform('elapsedTime', this._elapsedTime);
    particlePass.render(renderer, frameBuffer);
    particleMesh.material.set('particleTexture', this._particleTexture1);
    particleMesh.material.set('prevParticleTexture', this._particleTexture0);
    frameBuffer.attach(this._thisFrameTexture);
    frameBuffer.bind(renderer);
    renderer.gl.clear(renderer.gl.DEPTH_BUFFER_BIT | renderer.gl.COLOR_BUFFER_BIT);
    var lastFrameFullQuad = this._lastFrameFullQuadMesh;
    lastFrameFullQuad.material.set('diffuseMap', this._lastFrameTexture);
    lastFrameFullQuad.material.set('color', [1, 1, 1, this.motionBlurFactor]);

    this._camera.update(true);

    renderer.renderPass([lastFrameFullQuad, particleMesh], this._camera);
    frameBuffer.unbind(renderer);

    this._downsample(renderer);

    this._swapTexture();

    this._elapsedTime += deltaTime;
  },
  _downsample: function (renderer) {
    var downsampleTextures = this._downsampleTextures;

    if (downsampleTextures.length === 0) {
      return;
    }

    var current = 0;
    var sourceTexture = this._thisFrameTexture;
    var targetTexture = downsampleTextures[current];

    while (targetTexture) {
      this._frameBuffer.attach(targetTexture);

      this._downsamplePass.setUniform('texture', sourceTexture);

      this._downsamplePass.setUniform('textureSize', [sourceTexture.width, sourceTexture.height]);

      this._downsamplePass.render(renderer, this._frameBuffer);

      sourceTexture = targetTexture;
      targetTexture = downsampleTextures[++current];
    }
  },
  getSurfaceTexture: function () {
    var downsampleTextures = this._downsampleTextures;
    return downsampleTextures.length > 0 ? downsampleTextures[downsampleTextures.length - 1] : this._lastFrameTexture;
  },
  setRegion: function (region) {
    this._particlePass.setUniform('region', region);
  },
  resize: function (width, height) {
    this._lastFrameTexture.width = width * this._supersampling;
    this._lastFrameTexture.height = height * this._supersampling;
    this._thisFrameTexture.width = width * this._supersampling;
    this._thisFrameTexture.height = height * this._supersampling;
    this._width = width;
    this._height = height;
  },
  setParticleSize: function (size) {
    var particleMesh = this._getParticleMesh();

    if (size <= 2) {
      particleMesh.material.disableTexture('spriteTexture');
      particleMesh.material.transparent = false;
      return;
    }

    if (!this._spriteTexture) {
      this._spriteTexture = new Texture2D();
    }

    if (!this._spriteTexture.image || this._spriteTexture.image.width !== size) {
      this._spriteTexture.image = createSpriteCanvas(size);

      this._spriteTexture.dirty();
    }

    particleMesh.material.transparent = true;
    particleMesh.material.enableTexture('spriteTexture');
    particleMesh.material.set('spriteTexture', this._spriteTexture);
    this._particleSize = size;
  },
  setGradientTexture: function (gradientTexture) {
    var material = this._getParticleMesh().material;

    material[gradientTexture ? 'enableTexture' : 'disableTexture']('gradientTexture');
    material.setUniform('gradientTexture', gradientTexture);
  },
  setColorTextureImage: function (colorTextureImg, api) {
    var material = this._getParticleMesh().material;

    material.setTextureImage('colorTexture', colorTextureImg, api, {
      flipY: true
    });
  },
  setParticleType: function (type) {
    this._particleType = type;
  },
  clearFrame: function (renderer) {
    var frameBuffer = this._frameBuffer;
    frameBuffer.attach(this._lastFrameTexture);
    frameBuffer.bind(renderer);
    renderer.gl.clear(renderer.gl.DEPTH_BUFFER_BIT | renderer.gl.COLOR_BUFFER_BIT);
    frameBuffer.unbind(renderer);
  },
  setSupersampling: function (supersampling) {
    this._supersampling = supersampling;
    this.resize(this._width, this._height);
  },
  _updateDownsampleTextures: function (renderer, api) {
    var downsampleTextures = this._downsampleTextures;
    var upScale = Math.max(Math.floor(Math.log(this._supersampling / api.getDevicePixelRatio()) / Math.log(2)), 0);
    var scale = 2;
    var width = this._width * this._supersampling;
    var height = this._height * this._supersampling;

    for (var i = 0; i < upScale; i++) {
      downsampleTextures[i] = downsampleTextures[i] || new Texture2D();
      downsampleTextures[i].width = width / scale;
      downsampleTextures[i].height = height / scale;
      scale *= 2;
    }

    for (; i < downsampleTextures.length; i++) {
      downsampleTextures[i].dispose(renderer);
    }

    downsampleTextures.length = upScale;
  },
  _swapTexture: function () {
    var tmp = this._particleTexture0;
    this._particleTexture0 = this._particleTexture1;
    this._particleTexture1 = tmp;
    var tmp = this._thisFrameTexture;
    this._thisFrameTexture = this._lastFrameTexture;
    this._lastFrameTexture = tmp;
  },
  dispose: function (renderer) {
    renderer.disposeFrameBuffer(this._frameBuffer); // Dispose textures

    renderer.disposeTexture(this.vectorFieldTexture);
    renderer.disposeTexture(this._spawnTexture);
    renderer.disposeTexture(this._particleTexture0);
    renderer.disposeTexture(this._particleTexture1);
    renderer.disposeTexture(this._thisFrameTexture);
    renderer.disposeTexture(this._lastFrameTexture);
    renderer.disposeGeometry(this._particleLinesMesh.geometry);
    renderer.disposeGeometry(this._particlePointsMesh.geometry);
    renderer.disposeGeometry(this._lastFrameFullQuadMesh.geometry);

    if (this._spriteTexture) {
      renderer.disposeTexture(this._spriteTexture);
    }

    this._particlePass.dispose(renderer);

    this._downsamplePass.dispose(renderer);

    this._downsampleTextures.forEach(function (texture) {
      texture.dispose(renderer);
    });
  }
};
export default VectorFieldParticleSurface;