"use strict";

exports.__esModule = true;
exports.default = undefined;

var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();

var _akutils = require("akutils");

var _es6Symbol = require("es6-symbol");

var _es6Symbol2 = _interopRequireDefault(_es6Symbol);

var _invariant = require("invariant");

var _invariant2 = _interopRequireDefault(_invariant);

var _AutoShader = require("./AutoShader");

var _AutoShader2 = _interopRequireDefault(_AutoShader);

var _noParentStateErrorMsg = require("./noParentStateErrorMsg");

var _noParentStateErrorMsg2 = _interopRequireDefault(_noParentStateErrorMsg);

var _PropertyFactoryShader = require("./PropertyFactoryShader");

var _PropertyFactoryShader2 = _interopRequireDefault(_PropertyFactoryShader);

var _shadowProperty2 = require("./shadowProperty");

var _shadowProperty3 = _interopRequireDefault(_shadowProperty2);

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }

var readonlyAutoShader = new _AutoShader2.default(true);
var readWriteAutoShader = new _AutoShader2.default(false);

/*
	Mappings for property names that always have the same type, such as create_on dates.
*/
var defaultShaders = {};

// Instnace variable names
var _automount = (0, _es6Symbol2.default)('_autoMount');
var _automountAll = (0, _es6Symbol2.default)('automountAll');
var _autoshadow = (0, _es6Symbol2.default)('autoShadow');
var _elementShader = (0, _es6Symbol2.default)('elementShader');
var _hasJitProperties = (0, _es6Symbol2.default)('hasJitProperties');
var _elementShaderAutomount = (0, _es6Symbol2.default)('elementShaderAutomount');
var _property = (0, _es6Symbol2.default)('property');
var _shaders = (0, _es6Symbol2.default)('shaders');

/**
	Shaders implement the shadowing process by which the shadow state proxies the actual state.
	Each {@link Property} maintains a shader used for shadowing its associated state property.

	The {@link Shader#shadowProperty} method is responsible for setting up the `Shadow` proxy
	for the state.

	Shaders are setup through {@link StateType} instances associated with `Property` objects
	and there is rarely a need to directly interact with them.

	@see {@link Property}
	@see {@link StateType}
*/

var Shader = function () {
	function Shader(property) {
		_classCallCheck(this, Shader);

		var stateType = property.stateType();

		this[_automount] = [];
		this[_automountAll] = stateType._automountAll;
		this[_autoshadow] = stateType._autoshadow;
		this[_elementShader] = null;
		this[_hasJitProperties] = stateType.hasJitProperties();
		this[_property] = property;
		this[_shaders] = {};
	}

	_createClass(Shader, [{
		key: "add",
		value: function add(name, shader, automount) {
			this[_shaders][name] = shader;

			if (automount) {
				this[_automount].push(name);
			}

			return this;
		}
	}, {
		key: "automountAll",
		value: function automountAll() {
			// leaving implementation for now but automount (experimental feature) is no longer being
			// supported. Keeping in place for a bit to see if needed. (2/2/2017)
			return false;

			//return this[_automountAll];
		}
	}, {
		key: "addProperty",
		value: function addProperty(name, stateType) {
			var property = this[_property];
			var shader = stateType.factory(property);

			this.add(name, shader);
		}
	}, {
		key: "autoShader",
		value: function autoShader() {
			if (this[_autoshadow]) {
				return this[_property].isReadonly() ? readonlyAutoShader : readWriteAutoShader;
			} else {
				return null;
			}
		}
	}, {
		key: "autoShadow",
		value: function autoShadow() {
			return this[_autoshadow];
		}
	}, {
		key: "elementShader",
		value: function elementShader() {
			return this[_elementShader];
		}
	}, {
		key: "get",
		value: function get(name) {
			return this[_shaders][name];
		}
	}, {
		key: "has",
		value: function has(name) {
			return !!this[_shaders][name];
		}
	}, {
		key: "isAutomount",
		value: function isAutomount(name) {
			// leaving implementation for now but automount (experimental feature) is no longer being
			// supported. Keeping in place for a bit to see if needed. (2/2/2017)
			return false;

			// if (this[_automountAll]) {
			// 	return true;
			// } else if (this[_shaders][name]) {
			// 	return this[_shaders][name].shouldAutomount() || this[_automount].indexOf(name) !== -1;
			// } else if (this[_elementShader]) {
			// 	return this[_elementShaderAutomount] || this[_elementShader].shouldAutomount();
			// } else {
			// 	return false;
			// }
		}
	}, {
		key: "keys",
		value: function keys() {
			return Object.keys(this[_shaders]);
		}
	}, {
		key: "property",
		value: function property() {
			return this[_property];
		}
	}, {
		key: "remove",
		value: function remove(name) {
			delete this[_shaders][name];

			// remove the automount setting
			var automountIdx = this[_automount].indexOf(name);

			if (automountIdx != -1) {
				this[_automount].splice(automountIdx, 1);
			}

			return this;
		}
	}, {
		key: "setAutomountAll",
		value: function setAutomountAll(automount) {
			this[_automountAll] = automount;
		}
	}, {
		key: "setAutoshadow",
		value: function setAutoshadow(value) {
			this[_autoshadow] = !!value;

			return this;
		}
	}, {
		key: "setElementType",
		value: function setElementType(stateType) {
			var property = this[_property];
			var shader = stateType.factory(property);

			return this.setElementShader(shader);
		}
	}, {
		key: "setElementShader",
		value: function setElementShader(shader, automount) {
			if (this[_elementShader]) {
				console.warn("Property already has a child shader set - ignoring setElementShader() call - shader=%O", this);
				return;
			}

			this[_elementShader] = shader;
			this[_elementShaderAutomount] = automount;

			return this;
		}

		/*
  	Gets the shader for a child property. This method returns the first shader generated by checking:
  		1) shaders registered using add() or addPropertyClass() - named shaders
  		2) jit shaders from state type
  		3) child shader (registered using setElementShader() )
  		4) default shaders (regustered using the static addDefault() method)
  		5) auto shader (applicable if autoShadow set to true)
  		Parameters:
  		name - the name of the child property
  		sate - the property's current state (not the child state)
  */

	}, {
		key: "shaderFor",
		value: function shaderFor(name, state) {
			// priority order: named, child, default mapping, auto shading
			return this[_shaders][name] || this[_hasJitProperties] && this.stateType().jitShaderFor(name, state, this[_property]) || this[_elementShader] || defaultShaders[name] || this.autoShader();
		}

		/*
  	Creates the proxy (shadow) for a state property.
  */

	}, {
		key: "shadowProperty",
		value: function shadowProperty(time, name, parentState, parentImpl) {
			var IsolatedProperty = require("./IsolatedProperty").default;
			var property = this[_property];
			var isRoot = property.isRoot() || property instanceof IsolatedProperty;

			(0, _invariant2.default)(parentState || isRoot, (0, _noParentStateErrorMsg2.default)(name, parentImpl));

			// get the initial state for the property
			var currState = isRoot ? parentState : parentState[name];
			var state = property.getInitialState(currState);

			if (!isRoot) {
				parentState[name] = state;
			}

			// add mixins to the property (but only once)
			if (!property.__hasMixins()) {
				var proto = Object.getPrototypeOf(property);
				var mixins = proto.constructor.mixins;
				var mixin = void 0;

				if (mixins) {
					for (var i = 0, len = mixins.length; i < len; i++) {
						mixin = mixins[i](property);

						property.__addMixin(mixin);
					}
				}
			}

			// create the backing implementation instance
			var ImplClass = property.implementationClass();
			var impl = (0, _shadowProperty3.default)(time, ImplClass, property, name, state, parentImpl, this);

			return impl;
		}
	}, {
		key: "shadowUndefinedProperties",
		value: function shadowUndefinedProperties(state, impl, define, blacklist) {
			var keys = Object.keys(this[_shaders]);
			var propNames = blacklist || Object.getOwnPropertyNames(impl.shadow());
			var key, shader;

			for (var i = 0, len = keys.length; i < len; i++) {
				key = keys[i];
				shader = this[_shaders][key];

				if (propNames.indexOf(key) !== -1) {
					continue;
				}

				define(key, shader);
			}
		}

		/*
  	Automounting is an experimental, partially implemented feature.
  */

	}, {
		key: "shouldAutomount",
		value: function shouldAutomount() {
			var proto = Object.getPrototypeOf(this[_property]);

			return proto.constructor.shouldAutomount && proto.constructor.shouldAutomount();
		}
	}, {
		key: "stateType",
		value: function stateType() {
			return this[_property].stateType();
		}

		/**
  	Gets the shader for a child property. This method returns the first shader generated by checking:
  		1) shaders registered using add() or addPropertyClass() - named shaders
  		2) jit shaders from state type
  		3) child shader (registered using setElementShader() )
  		4) default shaders (regustered using the static addDefault() method)
  		5) managed type
  		6) auto shader (applicable if autoShadow set to true)
  		Parameters:
  		name - the name of the child property
  		sate - the property's current state (not the child state)
  */

	}, {
		key: "typeFor",
		value: function typeFor(name, state) {
			// priority order: named, child, default mapping, managed type, auto shading
			var type = this.stateType();
			var shader = this[_shaders][name] || this[_hasJitProperties] && this.stateType().jitShaderFor(name, state, this[_property]) || this[_elementShader] || defaultShaders[name];

			if (shader) {
				return shader.stateType();
			}

			return type.getManagedType() || _AutoShader2.default.typeFor(state);
		}
	}], [{
		key: "addDefault",
		value: function addDefault(name, shader) {
			defaultShaders[name] = shader;
		}
	}]);

	return Shader;
}();

exports.default = Shader;