# src/KeyedApi.js

    import has from "lodash.has";
    import {
    	assert,
    	doneIterator,
    	iteratorFor,
    	iterateOver,
    } from "akutils";

    /**
    	API implementation for properties that support keyed access to child properties.

    	@see {@link MapProperty}
    	@see {@link ObjectProperty}
    */
    export default class KeyedApi {
    	constructor(property) {
    		this._property = property;
    	}

    	impl() {
    		return this._property.__();
    	}

    	isActive() {
    		return this._property.isActive();
    	}

    	shadow() {
    		return this._property._();
    	}

    	addProperty(name, property, automount) {
    		property.setParent(this._property);

    		return this.addPropertyShader(name, property.shader(), property.getInitialState(), automount);
    	}

    	addPropertyType(name, stateType) {
    		return this.addPropertyShader(name, stateType.factory(this._property), stateType._initialState);
    	}

    	addPropertyShader(name, shader, initialState, automount) {
    		const property = this._property;
    		const propInitialState = property.initialState();

    		// initial state has two sources: parameter and the shader
    		const iState = initialState !== undefined
    				?initialState
    				:shader.initialState ?shader.initialState :propInitialState && propInitialState[name];

    		property.shader().add(name, shader, automount);

    		if (this.isActive()) {
    			if (iState !== undefined) {
    				this.set(name, iState);
    			}

    			property.touch();
    		} else if (propInitialState) {
    			propInitialState[name] = iState;
    		}

    		// return shader so can be further customized
    		return shader;
    	}

    	removeProperty(name) {
    		this._property.shader().remove(name);
    		this.delete(name);
    	}

    	//------------------------------------------------------------------------------------------------------
    	// Map and other useful data structure functions
    	//------------------------------------------------------------------------------------------------------

    	/*
    		Converting map size property to a function in keeping with current design philosophy of minimizing
    		internal object properties in favor of methods.
    	*/
    	size() {
    		return this.impl().size();
    	}

    	clear() {
    		if (this.isActive()) {
    			this.impl().clear();
    		}
    	}

    	delete(key) {
    		if (this.isActive()) {
    			const value = this.shadow()[key];

    			// need to perform action even if value is undefined because could be a queued action
    			// to add it.
    			this.impl().delete(key);

    			return value;
    		}
    	}

    	entries() {
    		if (!this.isActive()) { return doneIterator; }

    		return iterateOver(this.keysArray(), key => [key, this.get(key)] );
    	}

    	filter(callback, context) {
    		const keys = this.keysArray();
    		const shadow = this.shadow();
    		const acc = [];
    		var key, value;

    		for (let i=0, len=keys.length; i<len; i++) {
    			key = keys[i];
    			value = shadow[key];

    			if (callback.call(context, value, key, shadow)) {
    				acc.push(value);
    			}
    		}

    		return acc;
    	}

    	get(key) {
    		if (this.isActive()) {
    			return this.shadow()[key];
    		}
    	}

    	has(key) {
    		if (this.isActive()) {
    			return has(this.shadow(), key);
    		}
    	}

    	keys() {
    		if (!this.isActive()) { return doneIterator; }

    		return iteratorFor(this.keysArray());
    	}

    	keysArray() {
    		if (!this.isActive()) { return doneIterator }

    		// use Object.keys() so do not get non-enumerable properties
    		return Object.keys(this.shadow());
    	}

    	set(key, value) {
    		if (this.isActive()) {
    			this.impl().set(key, value);
    		}
    	}

    	valuesArray() {
    		if (!this.isActive()) { return doneIterator; }

    		const keys = this.keysArray();
    		const values = [];

    		for (let i=0, len=keys.length; i<len; i++) {
    			values.push( this[keys[i]] );
    		}

    		return values;
    	}

    	values() {
    		if (!this.isActive()) { return doneIterator; }

    		return iterateOver(this.keysArray(), key => this.get(key));
    	}

    	[Symbol.iterator]() { return this.entries() }
    }