( function () {

	/**
 * Description: Early release of an AMF THREE.Loader following the pattern of the
 * example loaders in the three.js project.
 *
 * More information about the AMF format: http://amf.wikispaces.com
 *
 * Usage:
 *	const loader = new AMFLoader();
 *	loader.load('/path/to/project.amf', function(objecttree) {
 *		scene.add(objecttree);
 *	});
 *
 * Materials now supported, material colors supported
 * Zip support, requires fflate
 * No constellation support (yet)!
 *
 */

	class AMFLoader extends THREE.Loader {

		constructor( manager ) {

			super( manager );

		}

		load( url, onLoad, onProgress, onError ) {

			const scope = this;
			const loader = new THREE.FileLoader( scope.manager );
			loader.setPath( scope.path );
			loader.setResponseType( 'arraybuffer' );
			loader.setRequestHeader( scope.requestHeader );
			loader.setWithCredentials( scope.withCredentials );
			loader.load( url, function ( text ) {

				try {

					onLoad( scope.parse( text ) );

				} catch ( e ) {

					if ( onError ) {

						onError( e );

					} else {

						console.error( e );

					}

					scope.manager.itemError( url );

				}

			}, onProgress, onError );

		}

		parse( data ) {

			function loadDocument( data ) {

				let view = new DataView( data );
				const magic = String.fromCharCode( view.getUint8( 0 ), view.getUint8( 1 ) );

				if ( magic === 'PK' ) {

					let zip = null;
					let file = null;
					console.log( 'THREE.AMFLoader: Loading Zip' );

					try {

						zip = fflate.unzipSync( new Uint8Array( data ) ); // eslint-disable-line no-undef

					} catch ( e ) {

						if ( e instanceof ReferenceError ) {

							console.log( 'THREE.AMFLoader: fflate missing and file is compressed.' );
							return null;

						}

					}

					for ( file in zip ) {

						if ( file.toLowerCase().substr( - 4 ) === '.amf' ) {

							break;

						}

					}

					console.log( 'THREE.AMFLoader: Trying to load file asset: ' + file );
					view = new DataView( zip[ file ].buffer );

				}

				const fileText = THREE.LoaderUtils.decodeText( view );
				const xmlData = new DOMParser().parseFromString( fileText, 'application/xml' );

				if ( xmlData.documentElement.nodeName.toLowerCase() !== 'amf' ) {

					console.log( 'THREE.AMFLoader: Error loading AMF - no AMF document found.' );
					return null;

				}

				return xmlData;

			}

			function loadDocumentScale( node ) {

				let scale = 1.0;
				let unit = 'millimeter';

				if ( node.documentElement.attributes.unit !== undefined ) {

					unit = node.documentElement.attributes.unit.value.toLowerCase();

				}

				const scaleUnits = {
					millimeter: 1.0,
					inch: 25.4,
					feet: 304.8,
					meter: 1000.0,
					micron: 0.001
				};

				if ( scaleUnits[ unit ] !== undefined ) {

					scale = scaleUnits[ unit ];

				}

				console.log( 'THREE.AMFLoader: Unit scale: ' + scale );
				return scale;

			}

			function loadMaterials( node ) {

				let matName = 'AMF Material';
				const matId = node.attributes.id.textContent;
				let color = {
					r: 1.0,
					g: 1.0,
					b: 1.0,
					a: 1.0
				};
				let loadedMaterial = null;

				for ( let i = 0; i < node.childNodes.length; i ++ ) {

					const matChildEl = node.childNodes[ i ];

					if ( matChildEl.nodeName === 'metadata' && matChildEl.attributes.type !== undefined ) {

						if ( matChildEl.attributes.type.value === 'name' ) {

							matName = matChildEl.textContent;

						}

					} else if ( matChildEl.nodeName === 'color' ) {

						color = loadColor( matChildEl );

					}

				}

				loadedMaterial = new THREE.MeshPhongMaterial( {
					flatShading: true,
					color: new THREE.Color( color.r, color.g, color.b ),
					name: matName
				} );

				if ( color.a !== 1.0 ) {

					loadedMaterial.transparent = true;
					loadedMaterial.opacity = color.a;

				}

				return {
					id: matId,
					material: loadedMaterial
				};

			}

			function loadColor( node ) {

				const color = {
					r: 1.0,
					g: 1.0,
					b: 1.0,
					a: 1.0
				};

				for ( let i = 0; i < node.childNodes.length; i ++ ) {

					const matColor = node.childNodes[ i ];

					if ( matColor.nodeName === 'r' ) {

						color.r = matColor.textContent;

					} else if ( matColor.nodeName === 'g' ) {

						color.g = matColor.textContent;

					} else if ( matColor.nodeName === 'b' ) {

						color.b = matColor.textContent;

					} else if ( matColor.nodeName === 'a' ) {

						color.a = matColor.textContent;

					}

				}

				return color;

			}

			function loadMeshVolume( node ) {

				const volume = {
					name: '',
					triangles: [],
					materialid: null
				};
				let currVolumeNode = node.firstElementChild;

				if ( node.attributes.materialid !== undefined ) {

					volume.materialId = node.attributes.materialid.nodeValue;

				}

				while ( currVolumeNode ) {

					if ( currVolumeNode.nodeName === 'metadata' ) {

						if ( currVolumeNode.attributes.type !== undefined ) {

							if ( currVolumeNode.attributes.type.value === 'name' ) {

								volume.name = currVolumeNode.textContent;

							}

						}

					} else if ( currVolumeNode.nodeName === 'triangle' ) {

						const v1 = currVolumeNode.getElementsByTagName( 'v1' )[ 0 ].textContent;
						const v2 = currVolumeNode.getElementsByTagName( 'v2' )[ 0 ].textContent;
						const v3 = currVolumeNode.getElementsByTagName( 'v3' )[ 0 ].textContent;
						volume.triangles.push( v1, v2, v3 );

					}

					currVolumeNode = currVolumeNode.nextElementSibling;

				}

				return volume;

			}

			function loadMeshVertices( node ) {

				const vertArray = [];
				const normalArray = [];
				let currVerticesNode = node.firstElementChild;

				while ( currVerticesNode ) {

					if ( currVerticesNode.nodeName === 'vertex' ) {

						let vNode = currVerticesNode.firstElementChild;

						while ( vNode ) {

							if ( vNode.nodeName === 'coordinates' ) {

								const x = vNode.getElementsByTagName( 'x' )[ 0 ].textContent;
								const y = vNode.getElementsByTagName( 'y' )[ 0 ].textContent;
								const z = vNode.getElementsByTagName( 'z' )[ 0 ].textContent;
								vertArray.push( x, y, z );

							} else if ( vNode.nodeName === 'normal' ) {

								const nx = vNode.getElementsByTagName( 'nx' )[ 0 ].textContent;
								const ny = vNode.getElementsByTagName( 'ny' )[ 0 ].textContent;
								const nz = vNode.getElementsByTagName( 'nz' )[ 0 ].textContent;
								normalArray.push( nx, ny, nz );

							}

							vNode = vNode.nextElementSibling;

						}

					}

					currVerticesNode = currVerticesNode.nextElementSibling;

				}

				return {
					'vertices': vertArray,
					'normals': normalArray
				};

			}

			function loadObject( node ) {

				const objId = node.attributes.id.textContent;
				const loadedObject = {
					name: 'amfobject',
					meshes: []
				};
				let currColor = null;
				let currObjNode = node.firstElementChild;

				while ( currObjNode ) {

					if ( currObjNode.nodeName === 'metadata' ) {

						if ( currObjNode.attributes.type !== undefined ) {

							if ( currObjNode.attributes.type.value === 'name' ) {

								loadedObject.name = currObjNode.textContent;

							}

						}

					} else if ( currObjNode.nodeName === 'color' ) {

						currColor = loadColor( currObjNode );

					} else if ( currObjNode.nodeName === 'mesh' ) {

						let currMeshNode = currObjNode.firstElementChild;
						const mesh = {
							vertices: [],
							normals: [],
							volumes: [],
							color: currColor
						};

						while ( currMeshNode ) {

							if ( currMeshNode.nodeName === 'vertices' ) {

								const loadedVertices = loadMeshVertices( currMeshNode );
								mesh.normals = mesh.normals.concat( loadedVertices.normals );
								mesh.vertices = mesh.vertices.concat( loadedVertices.vertices );

							} else if ( currMeshNode.nodeName === 'volume' ) {

								mesh.volumes.push( loadMeshVolume( currMeshNode ) );

							}

							currMeshNode = currMeshNode.nextElementSibling;

						}

						loadedObject.meshes.push( mesh );

					}

					currObjNode = currObjNode.nextElementSibling;

				}

				return {
					'id': objId,
					'obj': loadedObject
				};

			}

			const xmlData = loadDocument( data );
			let amfName = '';
			let amfAuthor = '';
			const amfScale = loadDocumentScale( xmlData );
			const amfMaterials = {};
			const amfObjects = {};
			const childNodes = xmlData.documentElement.childNodes;
			let i, j;

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

				const child = childNodes[ i ];

				if ( child.nodeName === 'metadata' ) {

					if ( child.attributes.type !== undefined ) {

						if ( child.attributes.type.value === 'name' ) {

							amfName = child.textContent;

						} else if ( child.attributes.type.value === 'author' ) {

							amfAuthor = child.textContent;

						}

					}

				} else if ( child.nodeName === 'material' ) {

					const loadedMaterial = loadMaterials( child );
					amfMaterials[ loadedMaterial.id ] = loadedMaterial.material;

				} else if ( child.nodeName === 'object' ) {

					const loadedObject = loadObject( child );
					amfObjects[ loadedObject.id ] = loadedObject.obj;

				}

			}

			const sceneObject = new THREE.Group();
			const defaultMaterial = new THREE.MeshPhongMaterial( {
				color: 0xaaaaff,
				flatShading: true
			} );
			sceneObject.name = amfName;
			sceneObject.userData.author = amfAuthor;
			sceneObject.userData.loader = 'AMF';

			for ( const id in amfObjects ) {

				const part = amfObjects[ id ];
				const meshes = part.meshes;
				const newObject = new THREE.Group();
				newObject.name = part.name || '';

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

					let objDefaultMaterial = defaultMaterial;
					const mesh = meshes[ i ];
					const vertices = new THREE.Float32BufferAttribute( mesh.vertices, 3 );
					let normals = null;

					if ( mesh.normals.length ) {

						normals = new THREE.Float32BufferAttribute( mesh.normals, 3 );

					}

					if ( mesh.color ) {

						const color = mesh.color;
						objDefaultMaterial = defaultMaterial.clone();
						objDefaultMaterial.color = new THREE.Color( color.r, color.g, color.b );

						if ( color.a !== 1.0 ) {

							objDefaultMaterial.transparent = true;
							objDefaultMaterial.opacity = color.a;

						}

					}

					const volumes = mesh.volumes;

					for ( j = 0; j < volumes.length; j ++ ) {

						const volume = volumes[ j ];
						const newGeometry = new THREE.BufferGeometry();
						let material = objDefaultMaterial;
						newGeometry.setIndex( volume.triangles );
						newGeometry.setAttribute( 'position', vertices.clone() );

						if ( normals ) {

							newGeometry.setAttribute( 'normal', normals.clone() );

						}

						if ( amfMaterials[ volume.materialId ] !== undefined ) {

							material = amfMaterials[ volume.materialId ];

						}

						newGeometry.scale( amfScale, amfScale, amfScale );
						newObject.add( new THREE.Mesh( newGeometry, material.clone() ) );

					}

				}

				sceneObject.add( newObject );

			}

			return sceneObject;

		}

	}

	THREE.AMFLoader = AMFLoader;

} )();
