/**
 * Data structure for the renderer. It allows defining values
 * with chained, hierarchical keys. Keys are meant to be
 * objects since the module internally works with Weak Maps
 * for performance reasons.
 *
 * @private
 */
class ChainMap {

	/**
	 * Constructs a new Chain Map.
	 */
	constructor() {

		/**
		 * A map of Weak Maps by their key length.
		 *
		 * @type {Object<number, WeakMap>}
		 */
		this.weakMaps = {};


	}

	/**
	 * Returns the Weak Map for the given keys.
	 *
	 * @param {Array<Object>} keys - List of keys.
	 * @return {WeakMap} The weak map.
	 */
	_getWeakMap( keys ) {

		const length = keys.length;

		let weakMap = this.weakMaps[ length ];

		if ( weakMap === undefined ) {

			weakMap = new WeakMap();
			this.weakMaps[ length ] = weakMap;

		}

		return weakMap;

	}

	/**
	 * Returns the value for the given array of keys.
	 *
	 * @param {Array<Object>} keys - List of keys.
	 * @return {any} The value. Returns `undefined` if no value was found.
	 */
	get( keys ) {

		let map = this._getWeakMap( keys );

		for ( let i = 0; i < keys.length - 1; i ++ ) {

			map = map.get( keys[ i ] );

			if ( map === undefined ) return undefined;

		}

		return map.get( keys[ keys.length - 1 ] );

	}

	/**
	 * Sets the value for the given keys.
	 *
	 * @param {Array<Object>} keys - List of keys.
	 * @param {any} value - The value to set.
	 * @return {ChainMap} A reference to this Chain Map.
	 */
	set( keys, value ) {

		let map = this._getWeakMap( keys );

		for ( let i = 0; i < keys.length - 1; i ++ ) {

			const key = keys[ i ];

			if ( map.has( key ) === false ) map.set( key, new WeakMap() );

			map = map.get( key );

		}

		map.set( keys[ keys.length - 1 ], value );

		return this;

	}

	/**
	 * Deletes a value for the given keys.
	 *
	 * @param {Array<Object>} keys - The keys.
	 * @return {boolean} Returns `true` if the value has been removed successfully and `false` if the value has not be found.
	 */
	delete( keys ) {

		let map = this._getWeakMap( keys );

		for ( let i = 0; i < keys.length - 1; i ++ ) {

			map = map.get( keys[ i ] );

			if ( map === undefined ) return false;

		}

		return map.delete( keys[ keys.length - 1 ] );

	}

}

export default ChainMap;
