import {
	DefaultLoadingManager,
	FileLoader
} from '../../../build/three.module.js';

import * as Nodes from '../nodes/Nodes.js';

var NodeMaterialLoader = function ( manager, library ) {

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

	this.nodes = {};
	this.materials = {};
	this.passes = {};
	this.names = {};
	this.library = library || {};

};

var NodeMaterialLoaderUtils = {

	replaceUUIDObject: function ( object, uuid, value, recursive ) {

		recursive = recursive !== undefined ? recursive : true;

		if ( typeof uuid === 'object' ) uuid = uuid.uuid;

		if ( typeof object === 'object' ) {

			var keys = Object.keys( object );

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

				var key = keys[ i ];

				if ( recursive ) {

					object[ key ] = this.replaceUUIDObject( object[ key ], uuid, value );

				}

				if ( key === uuid ) {

					object[ uuid ] = object[ key ];

					delete object[ key ];

				}

			}

		}

		return object === uuid ? value : object;

	},

	replaceUUID: function ( json, uuid, value ) {

		this.replaceUUIDObject( json, uuid, value, false );
		this.replaceUUIDObject( json.nodes, uuid, value );
		this.replaceUUIDObject( json.materials, uuid, value );
		this.replaceUUIDObject( json.passes, uuid, value );
		this.replaceUUIDObject( json.library, uuid, value, false );

		return json;

	}

};

Object.assign( NodeMaterialLoader.prototype, {

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

		var scope = this;

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

			onLoad( scope.parse( JSON.parse( text ) ) );

		}, onProgress, onError );

		return this;

	},

	setPath: function ( value ) {

		this.path = value;
		return this;

	},

	getObjectByName: function ( uuid ) {

		return this.names[ uuid ];

	},

	getObjectById: function ( uuid ) {

		return this.library[ uuid ] ||
			this.nodes[ uuid ] ||
			this.materials[ uuid ] ||
			this.passes[ uuid ] ||
			this.names[ uuid ];

	},

	getNode: function ( uuid ) {

		var object = this.getObjectById( uuid );

		if ( ! object ) {

			console.warn( 'Node "' + uuid + '" not found.' );

		}

		return object;

	},

	resolve: function ( json ) {

		switch ( typeof json ) {

			case 'boolean':
			case 'number':

				return json;

			case 'string':

				if ( /^\w{8}-\w{4}-\w{4}-\w{4}-\w{12}$/i.test( json ) || this.library[ json ] ) {

					return this.getNode( json );

				}

				return json;

			default:

				if ( Array.isArray( json ) ) {

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

						json[ i ] = this.resolve( json[ i ] );

					}

				} else {

					for ( var prop in json ) {

						if ( prop === 'uuid' ) continue;

						json[ prop ] = this.resolve( json[ prop ] );

					}

				}

		}

		return json;

	},

	declare: function ( json ) {

		var uuid, node, object;

		for ( uuid in json.nodes ) {

			node = json.nodes[ uuid ];

			object = new Nodes[ node.nodeType + 'Node' ]();

			if ( node.name ) {

				object.name = node.name;

				this.names[ object.name ] = object;

			}

			this.nodes[ uuid ] = object;

		}

		for ( uuid in json.materials ) {

			node = json.materials[ uuid ];

			object = new Nodes[ node.type ]();

			if ( node.name ) {

				object.name = node.name;

				this.names[ object.name ] = object;

			}

			this.materials[ uuid ] = object;

		}

		for ( uuid in json.passes ) {

			node = json.passes[ uuid ];

			object = new Nodes[ node.type ]();

			if ( node.name ) {

				object.name = node.name;

				this.names[ object.name ] = object;

			}

			this.passes[ uuid ] = object;

		}

		if ( json.material ) this.material = this.materials[ json.material ];

		if ( json.pass ) this.pass = this.passes[ json.pass ];

		return json;

	},

	parse: function ( json ) {

		var uuid;

		json = this.resolve( this.declare( json ) );

		for ( uuid in json.nodes ) {

			this.nodes[ uuid ].copy( json.nodes[ uuid ] );

		}

		for ( uuid in json.materials ) {

			this.materials[ uuid ].copy( json.materials[ uuid ] );

		}

		for ( uuid in json.passes ) {

			this.passes[ uuid ].copy( json.passes[ uuid ] );

		}

		return this.material || this.pass || this;

	}

} );

export { NodeMaterialLoader, NodeMaterialLoaderUtils };
