/**
 * @author alteredq / http://alteredqualia.com/
 */

THREE.DepthPassPlugin = function () {

	this.enabled = false;
	this.renderTarget = null;

	var _gl,
	_renderer,
	_lights, _webglObjects, _webglObjectsImmediate,
	_depthMaterial, _depthMaterialMorph, _depthMaterialSkin, _depthMaterialMorphSkin,

	_frustum = new THREE.Frustum(),
	_projScreenMatrix = new THREE.Matrix4(),
	_renderList = [];

	this.init = function ( renderer, lights, webglObjects, webglObjectsImmediate ) {

		_gl = renderer.context;
		_renderer = renderer;
		_lights = lights;

		_webglObjects = webglObjects;
		_webglObjectsImmediate = webglObjectsImmediate;

		var depthShader = THREE.ShaderLib[ "depthRGBA" ];
		var depthUniforms = THREE.UniformsUtils.clone( depthShader.uniforms );

		_depthMaterial = new THREE.ShaderMaterial( {
			fragmentShader: depthShader.fragmentShader,
			vertexShader: depthShader.vertexShader,
			uniforms: depthUniforms
		} );

		_depthMaterialMorph = new THREE.ShaderMaterial( {
			fragmentShader: depthShader.fragmentShader,
			vertexShader: depthShader.vertexShader,
			uniforms: depthUniforms,
			morphTargets: true
		} );

		_depthMaterialSkin = new THREE.ShaderMaterial( {
			fragmentShader: depthShader.fragmentShader,
			vertexShader: depthShader.vertexShader,
			uniforms: depthUniforms,
			skinning: true
		} );

		_depthMaterialMorphSkin = new THREE.ShaderMaterial( {
			fragmentShader: depthShader.fragmentShader,
			vertexShader: depthShader.vertexShader,
			uniforms: depthUniforms,
			morphTargets: true,
			skinning: true
		} );

		_depthMaterial._shadowPass = true;
		_depthMaterialMorph._shadowPass = true;
		_depthMaterialSkin._shadowPass = true;
		_depthMaterialMorphSkin._shadowPass = true;

	};

	this.render = function ( scene, camera ) {

		if ( ! this.enabled ) return;

		this.update( scene, camera );

	};

	this.update = function ( scene, camera ) {

		var i, il, j, jl, n,

		program, buffer, material,
		webglObject, object, light,
		renderList,

		fog = null;

		// set GL state for depth map

		_gl.clearColor( 1, 1, 1, 1 );
		_gl.disable( _gl.BLEND );

		_renderer.state.setDepthTest( true );

		// update scene

		if ( scene.autoUpdate === true ) scene.updateMatrixWorld();

		// update camera matrices and frustum

		camera.matrixWorldInverse.getInverse( camera.matrixWorld );

		_projScreenMatrix.multiplyMatrices( camera.projectionMatrix, camera.matrixWorldInverse );
		_frustum.setFromMatrix( _projScreenMatrix );

		// render depth map

		_renderer.setRenderTarget( this.renderTarget );
		_renderer.clear();

		// set object matrices & frustum culling

		_renderList.length = 0;
		projectObject(scene, scene, camera);

		// render regular objects

		var objectMaterial, useMorphing, useSkinning;

		for ( j = 0, jl = _renderList.length; j < jl; j ++ ) {

			webglObject = _renderList[ j ];

			object = webglObject.object;
			buffer = webglObject.buffer;

			// todo: create proper depth material for particles

			if ( object instanceof THREE.PointCloud && ! object.customDepthMaterial ) continue;

			objectMaterial = getObjectMaterial( object );

			if ( objectMaterial ) _renderer.setMaterialFaces( object.material );

			useMorphing = object.geometry.morphTargets !== undefined && object.geometry.morphTargets.length > 0 && objectMaterial.morphTargets;
			useSkinning = object instanceof THREE.SkinnedMesh && objectMaterial.skinning;

			if ( object.customDepthMaterial ) {

				material = object.customDepthMaterial;

			} else if ( useSkinning ) {

				material = useMorphing ? _depthMaterialMorphSkin : _depthMaterialSkin;

			} else if ( useMorphing ) {

				material = _depthMaterialMorph;

			} else {

				material = _depthMaterial;

			}

			if ( buffer instanceof THREE.BufferGeometry ) {

				_renderer.renderBufferDirect( camera, _lights, fog, material, buffer, object );

			} else {

				_renderer.renderBuffer( camera, _lights, fog, material, buffer, object );

			}


		}

		// set matrices and render immediate objects

		for ( j = 0, jl = _webglObjectsImmediate.length; j < jl; j ++ ) {

			webglObject = _webglObjectsImmediate[ j ];
			object = webglObject.object;

			if ( object.visible ) {

				object._modelViewMatrix.multiplyMatrices( camera.matrixWorldInverse, object.matrixWorld );

				_renderer.renderImmediateObject( camera, _lights, fog, _depthMaterial, object );

			}

		}

		// restore GL state

		var clearColor = _renderer.getClearColor(),
		clearAlpha = _renderer.getClearAlpha();

		_gl.clearColor( clearColor.r, clearColor.g, clearColor.b, clearAlpha );
		_gl.enable( _gl.BLEND );

	};

	function projectObject(scene, object,camera) {

		if ( object.visible ) {

			var webglObjects = _webglObjects[object.id];

			if (webglObjects && (object.frustumCulled === false || _frustum.intersectsObject( object ) === true) ) {


				for (var i = 0, l = webglObjects.length; i < l; i ++) {

					var webglObject = webglObjects[i];

					object._modelViewMatrix.multiplyMatrices( camera.matrixWorldInverse, object.matrixWorld );
					_renderList.push(webglObject);

				}
			}

			for (var i = 0, l = object.children.length; i < l; i ++) {

				projectObject(scene, object.children[i], camera);
			}

		}
	}

	// For the moment just ignore objects that have multiple materials with different animation methods
	// Only the first material will be taken into account for deciding which depth material to use

	function getObjectMaterial( object ) {

		return object.material instanceof THREE.MeshFaceMaterial
			? object.material.materials[ 0 ]
			: object.material;

	}

};
