/**
 * @author Alexander Gessler / http://www.greentoken.de/
 * https://github.com/acgessler
 *
 * Loader for models imported with Open Asset Import Library (http://assimp.sf.net)
 * through assimp2json (https://github.com/acgessler/assimp2json).
 *
 * Supports any input format that assimp supports, including 3ds, obj, dae, blend,
 * fbx, x, ms3d, lwo (and many more).
 *
 * See webgl_loader_assimp2json example.
 */

THREE.AssimpJSONLoader = function ( manager ) {

	this.manager = ( manager !== undefined ) ? manager : THREE.DefaultLoadingManager;

};

THREE.AssimpJSONLoader.prototype = {

	constructor: THREE.AssimpJSONLoader,

	crossOrigin: 'anonymous',

	load: function ( url, onLoad, onProgress, onError ) {

		var scope = this;

		var path = ( scope.path === undefined ) ? THREE.LoaderUtils.extractUrlBase( url ) : scope.path;

		var loader = new THREE.FileLoader( this.manager );
		loader.setPath( scope.path );
		loader.load( url, function ( text ) {

			var json = JSON.parse( text );
			var metadata = json.__metadata__;

			// check if __metadata__ meta header is present
			// this header is used to disambiguate between different JSON-based file formats

			if ( typeof metadata !== 'undefined' ) {

				// check if assimp2json at all

				if ( metadata.format !== 'assimp2json' ) {

					onError( 'THREE.AssimpJSONLoader: Not an assimp2json scene.' );
					return;

					// check major format version

				} else if ( metadata.version < 100 && metadata.version >= 200 ) {

					onError( 'THREE.AssimpJSONLoader: Unsupported assimp2json file format version.' );
					return;

				}

			}

			onLoad( scope.parse( json, path ) );

		}, onProgress, onError );

	},

	setPath: function ( value ) {

		this.path = value;
		return this;

	},

	setResourcePath: function ( value ) {

		this.resourcePath = value;
		return this;

	},

	setCrossOrigin: function ( value ) {

		this.crossOrigin = value;
		return this;

	},

	parse: function ( json, path ) {

		function parseList( json, handler ) {

			var meshes = new Array( json.length );

			for ( var i = 0; i < json.length; ++ i ) {

				meshes[ i ] = handler.call( this, json[ i ] );

			}

			return meshes;

		}

		function parseMesh( json ) {

			var geometry = new THREE.BufferGeometry();

			var i, l, face;

			var indices = [];

			var vertices = json.vertices || [];
			var normals = json.normals || [];
			var uvs = json.texturecoords || [];
			var colors = json.colors || [];

			uvs = uvs[ 0 ] || []; // only support for a single set of uvs

			for ( i = 0, l = json.faces.length; i < l; i ++ ) {

				face = json.faces[ i ];
				indices.push( face[ 0 ], face[ 1 ], face[ 2 ] );

			}

			geometry.setIndex( indices );
			geometry.addAttribute( 'position', new THREE.Float32BufferAttribute( vertices, 3 ) );

			if ( normals.length > 0 ) {

				geometry.addAttribute( 'normal', new THREE.Float32BufferAttribute( normals, 3 ) );

			}

			if ( uvs.length > 0 ) {

				geometry.addAttribute( 'uv', new THREE.Float32BufferAttribute( uvs, 2 ) );

			}

			if ( colors.length > 0 ) {

				geometry.addAttribute( 'color', new THREE.Float32BufferAttribute( colors, 3 ) );

			}

			geometry.computeBoundingSphere();

			return geometry;

		}

		function parseMaterial( json ) {

			var material = new THREE.MeshPhongMaterial();

			for ( var i in json.properties ) {

				var property = json.properties[ i ];
				var key = property.key;
				var value = property.value;

				switch ( key ) {

					case '$tex.file': {

						var semantic = property.semantic;

						// prop.semantic gives the type of the texture
						// 1: diffuse
						// 2: specular map
						// 4: emissive map
						// 5: height map (bumps)
						// 6: normal map
						// more values (i.e. environment, etc) are known by assimp and may be relevant

						if ( semantic === 1 || semantic === 2 || semantic === 4 || semantic === 5 || semantic === 6 ) {

							var keyname;

							switch ( semantic ) {

								case 1:
									keyname = 'map';
									break;
								case 2:
									keyname = 'specularMap';
									break;
								case 4:
									keyname = 'emissiveMap';
									break;
								case 5:
									keyname = 'bumpMap';
									break;
								case 6:
									keyname = 'normalMap';
									break;

							}

							var texture = textureLoader.load( value );

							// TODO: read texture settings from assimp.
							// Wrapping is the default, though.

							texture.wrapS = texture.wrapT = THREE.RepeatWrapping;

							material[ keyname ] = texture;

						}

						break;

					}

					case '?mat.name':
						material.name = value;
						break;

					case '$clr.diffuse':
						material.color.fromArray( value );
						break;

					case '$clr.specular':
						material.specular.fromArray( value );
						break;

					case '$clr.emissive':
						material.emissive.fromArray( value );
						break;

					case '$mat.shininess':
						material.shininess = value;
						break;

					case '$mat.shadingm':
						// aiShadingMode_Flat
						material.flatShading = ( value === 1 ) ? true : false;
						break;

					case '$mat.opacity':
						if ( value < 1 ) {

							material.opacity = value;
							material.transparent = true;

						}
						break;

				}

			}

			return material;

		}

		function parseObject( json, node, meshes, materials ) {

			var obj = new THREE.Object3D(),	i, idx;

			obj.name = node.name || '';
			obj.matrix = new THREE.Matrix4().fromArray( node.transformation ).transpose();
			obj.matrix.decompose( obj.position, obj.quaternion, obj.scale );

			for ( i = 0; node.meshes && i < node.meshes.length; i ++ ) {

				idx = node.meshes[ i ];
				obj.add( new THREE.Mesh( meshes[ idx ], materials[ json.meshes[ idx ].materialindex ] ) );

			}

			for ( i = 0; node.children && i < node.children.length; i ++ ) {

				obj.add( parseObject( json, node.children[ i ], meshes, materials ) );

			}

			return obj;

		}

		var textureLoader = new THREE.TextureLoader( this.manager );
		textureLoader.setPath( this.resourcePath || path ).setCrossOrigin( this.crossOrigin );

		var meshes = parseList( json.meshes, parseMesh );
		var materials = parseList( json.materials, parseMaterial );
		return parseObject( json, json.rootnode, meshes, materials );

	}

};
