// NormalPass will generate normal and depth data.
// TODO Animation
import Texture2D from 'claygl/src/Texture2D';
import Texture from 'claygl/src/Texture';
import Shader from 'claygl/src/Shader';
import FrameBuffer from 'claygl/src/FrameBuffer';
import Material from 'claygl/src/Material';
import Pass from 'claygl/src/compositor/Pass';
import textureUtil from 'claygl/src/util/texture';
import normalGLSL from '../util/shader/normal.glsl.js';
Shader.import(normalGLSL);

function attachTextureToSlot(renderer, program, symbol, texture, slot) {
  var gl = renderer.gl;
  program.setUniform(gl, '1i', symbol, slot);
  gl.activeTexture(gl.TEXTURE0 + slot); // Maybe texture is not loaded yet;

  if (texture.isRenderable()) {
    texture.bind(renderer);
  } else {
    // Bind texture to null
    texture.unbind(renderer);
  }
} // TODO Use globalShader insteadof globalMaterial?


function getBeforeRenderHook(renderer, defaultNormalMap, defaultBumpMap, defaultRoughnessMap, normalMaterial) {
  var previousNormalMap;
  var previousBumpMap;
  var previousRoughnessMap;
  var previousRenderable;
  var gl = renderer.gl;
  return function (renderable, normalMaterial, prevNormalMaterial) {
    // Material not change
    if (previousRenderable && previousRenderable.material === renderable.material) {
      return;
    }

    var material = renderable.material;
    var program = renderable.__program;
    var roughness = material.get('roughness');

    if (roughness == null) {
      roughness = 1;
    }

    var normalMap = material.get('normalMap') || defaultNormalMap;
    var roughnessMap = material.get('roughnessMap');
    var bumpMap = material.get('bumpMap');
    var uvRepeat = material.get('uvRepeat');
    var uvOffset = material.get('uvOffset');
    var detailUvRepeat = material.get('detailUvRepeat');
    var detailUvOffset = material.get('detailUvOffset');
    var useBumpMap = !!bumpMap && material.isTextureEnabled('bumpMap');
    var useRoughnessMap = !!roughnessMap && material.isTextureEnabled('roughnessMap');
    var doubleSide = material.isDefined('fragment', 'DOUBLE_SIDED');
    bumpMap = bumpMap || defaultBumpMap;
    roughnessMap = roughnessMap || defaultRoughnessMap;

    if (prevNormalMaterial !== normalMaterial) {
      normalMaterial.set('normalMap', normalMap);
      normalMaterial.set('bumpMap', bumpMap);
      normalMaterial.set('roughnessMap', roughnessMap);
      normalMaterial.set('useBumpMap', useBumpMap);
      normalMaterial.set('useRoughnessMap', useRoughnessMap);
      normalMaterial.set('doubleSide', doubleSide);
      uvRepeat != null && normalMaterial.set('uvRepeat', uvRepeat);
      uvOffset != null && normalMaterial.set('uvOffset', uvOffset);
      detailUvRepeat != null && normalMaterial.set('detailUvRepeat', detailUvRepeat);
      detailUvOffset != null && normalMaterial.set('detailUvOffset', detailUvOffset);
      normalMaterial.set('roughness', roughness);
    } else {
      program.setUniform(gl, '1f', 'roughness', roughness);

      if (previousNormalMap !== normalMap) {
        attachTextureToSlot(renderer, program, 'normalMap', normalMap, 0);
      }

      if (previousBumpMap !== bumpMap && bumpMap) {
        attachTextureToSlot(renderer, program, 'bumpMap', bumpMap, 1);
      }

      if (previousRoughnessMap !== roughnessMap && roughnessMap) {
        attachTextureToSlot(renderer, program, 'roughnessMap', roughnessMap, 2);
      }

      if (uvRepeat != null) {
        program.setUniform(gl, '2f', 'uvRepeat', uvRepeat);
      }

      if (uvOffset != null) {
        program.setUniform(gl, '2f', 'uvOffset', uvOffset);
      }

      if (detailUvRepeat != null) {
        program.setUniform(gl, '2f', 'detailUvRepeat', detailUvRepeat);
      }

      if (detailUvOffset != null) {
        program.setUniform(gl, '2f', 'detailUvOffset', detailUvOffset);
      }

      program.setUniform(gl, '1i', 'useBumpMap', +useBumpMap);
      program.setUniform(gl, '1i', 'useRoughnessMap', +useRoughnessMap);
      program.setUniform(gl, '1i', 'doubleSide', +doubleSide);
    }

    previousNormalMap = normalMap;
    previousBumpMap = bumpMap;
    previousRoughnessMap = roughnessMap;
    previousRenderable = renderable;
  };
}

function NormalPass(opt) {
  opt = opt || {};
  this._depthTex = new Texture2D({
    format: Texture.DEPTH_COMPONENT,
    type: Texture.UNSIGNED_INT
  });
  this._normalTex = new Texture2D({
    type: Texture.HALF_FLOAT
  });
  this._framebuffer = new FrameBuffer();

  this._framebuffer.attach(this._normalTex);

  this._framebuffer.attach(this._depthTex, FrameBuffer.DEPTH_ATTACHMENT);

  this._normalMaterial = new Material({
    shader: new Shader(Shader.source('ecgl.normal.vertex'), Shader.source('ecgl.normal.fragment'))
  });

  this._normalMaterial.enableTexture(['normalMap', 'bumpMap', 'roughnessMap']);

  this._defaultNormalMap = textureUtil.createBlank('#000');
  this._defaultBumpMap = textureUtil.createBlank('#000');
  this._defaultRoughessMap = textureUtil.createBlank('#000');
  this._debugPass = new Pass({
    fragment: Shader.source('clay.compositor.output')
  });

  this._debugPass.setUniform('texture', this._normalTex);

  this._debugPass.material.undefine('fragment', 'OUTPUT_ALPHA');
}

NormalPass.prototype.getDepthTexture = function () {
  return this._depthTex;
};

NormalPass.prototype.getNormalTexture = function () {
  return this._normalTex;
};

NormalPass.prototype.update = function (renderer, scene, camera) {
  var width = renderer.getWidth();
  var height = renderer.getHeight();
  var depthTexture = this._depthTex;
  var normalTexture = this._normalTex;
  var normalMaterial = this._normalMaterial;
  depthTexture.width = width;
  depthTexture.height = height;
  normalTexture.width = width;
  normalTexture.height = height;
  var opaqueList = scene.getRenderList(camera).opaque;

  this._framebuffer.bind(renderer);

  renderer.gl.clearColor(0, 0, 0, 0);
  renderer.gl.clear(renderer.gl.COLOR_BUFFER_BIT | renderer.gl.DEPTH_BUFFER_BIT);
  renderer.gl.disable(renderer.gl.BLEND);
  renderer.renderPass(opaqueList, camera, {
    getMaterial: function () {
      return normalMaterial;
    },
    ifRender: function (object) {
      return object.renderNormal;
    },
    beforeRender: getBeforeRenderHook(renderer, this._defaultNormalMap, this._defaultBumpMap, this._defaultRoughessMap, this._normalMaterial),
    sort: renderer.opaqueSortCompare
  });

  this._framebuffer.unbind(renderer);
};

NormalPass.prototype.renderDebug = function (renderer) {
  this._debugPass.render(renderer);
};

NormalPass.prototype.dispose = function (renderer) {
  this._depthTex.dispose(renderer);

  this._normalTex.dispose(renderer);
};

export default NormalPass;