"use strict";

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

var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };

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 _get = function get(object, property, receiver) { if (object === null) object = Function.prototype; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { return get(parent, property, receiver); } } else if ("value" in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } };

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

var _es6Symbol2 = _interopRequireDefault(_es6Symbol);

var _lodash = require("lodash.flatten");

var _lodash2 = _interopRequireDefault(_lodash);

var _lodash3 = require("lodash.range");

var _lodash4 = _interopRequireDefault(_lodash3);

var _ShadowImpl2 = require("./ShadowImpl");

var _ShadowImpl3 = _interopRequireDefault(_ShadowImpl2);

var _modelizeArray = require("./modelizeArray");

var _modelizeArray2 = _interopRequireDefault(_modelizeArray);

var _reshadow = require("./reshadow");

var _reshadow2 = _interopRequireDefault(_reshadow);

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"); } }

function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }

function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }

var _automounted = (0, _es6Symbol2.default)('automounted');
var _impls = (0, _es6Symbol2.default)('impls');
var _mapped = (0, _es6Symbol2.default)('mapped');
var _length = (0, _es6Symbol2.default)('length');
var _nextMapping = (0, _es6Symbol2.default)('nextMapping');
var _shadow = (0, _es6Symbol2.default)('shadow');

var IndexedShadowImpl = function (_ShadowImpl) {
	_inherits(IndexedShadowImpl, _ShadowImpl);

	function IndexedShadowImpl(time, property, name, state, parent, shader, prev) {
		_classCallCheck(this, IndexedShadowImpl);

		var _this = _possibleConstructorReturn(this, Object.getPrototypeOf(IndexedShadowImpl).call(this, time, property, name, state, parent, shader, prev));

		_this[_mapped] = false;
		_this[_impls] = [];

		// setup the length property
		_this[_length] = state.length;

		// go ahead and map properties so can reuse valid properties without creating a closure reference.
		// Reusing properties allows for React components to do '===' to see if a property has changed.
		// if (prev) {
		// 	if (prev && prev[_mapped]) {
		// 		this.defineChildProperties(prev, true);
		// 	} else if (prev && Object.keys(prev[_impls]).length) {
		// 		this.automountChildren(prev);
		// 	}

		// 	// Kill invalid() nodes that are still active
		// 	prev._killInvalidButActiveChildren();
		// }
		return _this;
	}

	/*
 	Map properties so can reuse valid properties. Reusing properties allows for React components
 	to do '===' to see if a property has changed.
 */


	_createClass(IndexedShadowImpl, [{
		key: "onReshadow",
		value: function onReshadow(prev) {
			// Kill invalid() nodes that are still active
			prev._killInvalidButActiveChildren();
		}
	}, {
		key: "isMapped",
		value: function isMapped() {
			return this[_mapped];
		}
	}, {
		key: "addChildren",
		value: function addChildren(start, num) {
			this[_nextMapping] = this[_nextMapping] || [].concat(this[_impls]);

			var mapping = this[_nextMapping];
			var blanks = [];
			var count = num;

			while (count--) {
				blanks.push(null);
			} // rename children after newly added blanks
			for (var i = start, len = mapping.length; i < len; i++) {
				var idx = i + num;
				var child = mapping[i];

				if (child) {
					child.updateName(idx);
				}
			}

			mapping.splice.apply(mapping, [start, 0].concat(blanks));
		}

		/*
  	Its possible for children to move when array items are inserted or removed in update actions.
  */

	}, {
		key: "childMapping",
		value: function childMapping() {
			return this[_nextMapping] ? this[_nextMapping] : this[_impls];
		}
	}, {
		key: "moveChildren",
		value: function moveChildren(start, deleteCount, addCount) {
			this.removeChildren(start, deleteCount);
			this.addChildren(start, addCount);
		}
	}, {
		key: "removeChildren",
		value: function removeChildren(start, num) {
			this[_nextMapping] = this[_nextMapping] || [].concat(this[_impls]);

			var mapping = this[_nextMapping];

			// prevent changes in removed children
			for (var i = 0; i < num; i++) {
				var child = mapping[start + i];

				if (child) {
					child.blockFurtherUpdates(true);
				}
			}

			// remove children from mapping
			mapping.splice(start, num);

			// rename remaining children
			for (var _i = start, len = mapping.length; _i < len; _i++) {
				var _child = mapping[_i];

				if (_child) {
					_child.updateName(_i);
				}
			}
		}
	}, {
		key: "toJSON",
		value: function toJSON() {
			return _extends({}, _get(Object.getPrototypeOf(IndexedShadowImpl.prototype), "toJSON", this).call(this), {
				type: 'ArrayImpl',
				length: this.length,
				mapped: this[_mapped]
			});
		}

		//------------------------------------------------------------------------------------------------------
		//	Methods for access and manipulate subproperties - based on Array methods
		//------------------------------------------------------------------------------------------------------

	}, {
		key: "clear",


		/*
  	Removes all subproperties.
  */
		value: function clear() {
			this.assign([], "clear()");
		}
	}, {
		key: "childAt",
		value: function childAt(idx) {
			return this[_impls][idx];
		}

		// This method does not change this object's state.

	}, {
		key: "concat",
		value: function concat() {
			for (var _len = arguments.length, values = Array(_len), _key = 0; _key < _len; _key++) {
				values[_key] = arguments[_key];
			}

			return (0, _lodash2.default)(this.values(), values);
		}
	}, {
		key: "pop",
		value: function pop() {
			var _this2 = this;

			var result;

			this.update(function (state) {
				var lastIdx = state.length - 1;

				state = [].concat(state);
				result = state.pop();

				if (lastIdx != -1) {
					_this2.removeChildren(lastIdx, 1);
				}

				return { name: "pop()", nextState: state };
			});

			// value is no longer being managed so ok to return without cloning
			return result;
		}
	}, {
		key: "push",
		value: function push() {
			for (var _len2 = arguments.length, values = Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
				values[_key2] = arguments[_key2];
			}

			var result;

			(0, _modelizeArray2.default)(values);

			this.update(function (state) {
				var _state;

				state = [].concat(state);
				result = (_state = state).push.apply(_state, values);

				// no need to update children mapping only affecting tail

				return { name: "push()", nextState: state };
			});

			return result;
		}
	}, {
		key: "remove",
		value: function remove(idx) {
			var _this3 = this;

			var result;

			this.update(function (state) {
				state = [].concat(state);
				result = state.splice(idx, 1);

				_this3.removeChildren(idx, 1);

				return { name: "remove(" + idx + ")", nextState: state };
			});

			// value is no longer being managed so ok to return without cloning
			return result[0];
		}
	}, {
		key: "shift",
		value: function shift() {
			var _this4 = this;

			var result;

			this.update(function (state) {
				state = [].concat(state);
				result = state.shift();

				_this4.removeChildren(0, 1);

				return { name: "shift()", nextState: state };
			});

			// value is no longer being managed so ok to return without cloning
			return result;
		}
	}, {
		key: "splice",
		value: function splice(start, deleteCount) {
			for (var _len3 = arguments.length, newItems = Array(_len3 > 2 ? _len3 - 2 : 0), _key3 = 2; _key3 < _len3; _key3++) {
				newItems[_key3 - 2] = arguments[_key3];
			}

			var _this5 = this;

			var result;

			(0, _modelizeArray2.default)(newItems);

			this.update(function (state) {
				var _state2;

				state = [].concat(state);

				result = (_state2 = state).splice.apply(_state2, [start, deleteCount].concat(newItems));
				_this5.moveChildren(start, deleteCount, newItems.length);

				return { name: "splice(" + start + ", " + deleteCount + ", ...)", nextState: state };
			});

			// values is no longer being managed so ok to return without cloning
			return result;
		}
	}, {
		key: "unshift",
		value: function unshift() {
			var _this6 = this;

			for (var _len4 = arguments.length, values = Array(_len4), _key4 = 0; _key4 < _len4; _key4++) {
				values[_key4] = arguments[_key4];
			}

			var result;

			(0, _modelizeArray2.default)(values);

			this.update(function (state) {
				var _state3;

				state = [].concat(state);
				result = (_state3 = state).unshift.apply(_state3, values);

				_this6.addChildren(0, values.length);

				return { name: "unshift()", nextState: state };
			});

			// values are no longer being managed so ok to return without cloning
			return result;
		}
	}, {
		key: "values",
		value: function values() {
			return [].concat(this[_impls]);
		}

		//------------------------------------------------------------------------------------------------------
		//	Methods that must be implemented by subclasses
		//------------------------------------------------------------------------------------------------------

	}, {
		key: "defaults",
		value: function defaults(newItems) {
			var _this7 = this;

			if (!Array.isArray(newItems)) {
				// nothing to do
				return;
			}

			var impls = this.childMapping();
			var child, value;

			var _loop = function _loop(i, len) {
				child = impls[i];
				value = newItems[i];

				if (child) {
					child.defaults(value);
				} else {
					_this7.update(function (state) {
						state = [].concat(state);

						state.splice(i, 0, value);
						_this7.addChildren(i, newItems.length);

						return { nextState: state };
					});
				}
			};

			for (var i = 0, len = newItems.length; i < len; i++) {
				_loop(i, len);
			}
		}
	}, {
		key: "merge",
		value: function merge(newItems) {
			// original algorithm (commented out below) is adding duplicates where in most
			// cases what is really wanted is one of two things:
			//    1) add if no duplicate (exact deep compare)
			//    2) merge if object's 'ID' is matched and add otherwise
			//
			// So probably need multiple algorithms driven by a property setting. Would need to worry
			// about ordering, inserts, deletions,...
			//
			// In the meantime just going to do a full replace
			return this.assign(newItems);

			// Original algorithm (broken)
			// if (!Array.isArray(newItems)) {
			// 	return this.assign(newItems);
			// }

			// const impls = this.childMapping();
			// var child, value;

			// for (let i=0, len=newItems.length; i<len; i++) {
			// 	child = impls[i];
			// 	value = newItems[i];

			// 	if (child) {
			// 		child.merge(value);
			// 	} else {
			// 		this.update( state => {
			// 				state = [...state];

			// 				state.splice(i, 0, value);
			// 				this.addChildren(i, newItems.length);

			// 				return { nextState: state };
			// 			});
			// 	}
			// }
		}

		//------------------------------------------------------------------------------------------------------
		//	Methods that subclasses with children must implement
		//------------------------------------------------------------------------------------------------------

	}, {
		key: "automountChildren",
		value: function automountChildren(prev) {
			if (this[_mapped] || this[_automounted]) {
				return;
			}

			this[_automounted] = true;

			var state = this.state();
			var shader = this.shader(state);

			for (var i = 0, len = state.length; i < len; i++) {
				if (shader.isAutomount(i)) {
					this.defineChildProperty(i, shader, state, prev, true);
				}
			}
		}

		/*
  	Subclasses should implement this method in such a way as not to trigger a mapping.
  */

	}, {
		key: "childCount",
		value: function childCount() {
			return this[_length];
		}

		/*
  	Gets the implementation objects managed by this property.
  */

	}, {
		key: "children",
		value: function children() {
			return [].concat(this[_impls]);
		}
	}, {
		key: "getChild",
		value: function getChild(idx) {
			return this.childAt(idx);
		}

		/*
  	Gets the keys/indices for this property.
  		Implementation note: Subclasses should implement this method in such a way as not to trigger a mapping.
  */

	}, {
		key: "keys",
		value: function keys() {
			if (!this._keys) {
				this._keys = (0, _lodash4.default)(this.length);
			}

			return this._keys;
		}

		/*
  	Maps all child properties onto this property using Object.defineProperty().
  */

	}, {
		key: "defineChildProperties",
		value: function defineChildProperties(prev, inCtor) {
			var _this8 = this;

			if (this[_mapped]) {
				return;
			}

			this[_mapped] = true;

			var shader = this.shader(state);
			var state = this.state();
			var elementShader;

			for (var i = 0, len = state.length; i < len; i++) {
				elementShader = shader.shaderFor(i, state);

				this.defineChildProperty(i, elementShader, state, prev, inCtor);
			}

			if (state) {
				shader.shadowUndefinedProperties(state, this, function (name, shader) {
					_this8.defineChildProperty(name, shader, state, prev, inCtor);
				});
			}
		}
	}, {
		key: "defineChildProperty",
		value: function defineChildProperty(idx, elementShader, state, prev) {
			var inCtor = arguments.length <= 4 || arguments[4] === undefined ? false : arguments[4];

			// ensure not already defined
			if (this[_impls][idx]) {
				return;
			}

			var prevMapping = prev && prev.childMapping();
			var prevChild = prevMapping && prevMapping[idx];
			var child;

			if (prevChild && !prevChild.replaced()) {
				prevChild.switchName();

				child = (0, _reshadow2.default)(this.time(), state, prevChild, this);
			} else {
				child = elementShader.shadowProperty(this.time(), idx, state, this, this.store());
			}

			if (child) {
				this[_impls][idx] = child;

				if (!prevChild && !inCtor) {
					child.didShadow(this.time());
				}
			}
		}
	}, {
		key: "_killInvalidButActiveChildren",
		value: function _killInvalidButActiveChildren() {
			// kill any unvisited children
			var children = this.children();
			var child;

			for (var i = 0, len = children.length; i < len; i++) {
				child = children[i];

				if (!child.isValid() && child.isActive()) {
					child.obsoleteTree();
				}
			}
		}
	}, {
		key: "length",
		get: function get() {
			return this[_length];
		}
	}]);

	return IndexedShadowImpl;
}(_ShadowImpl3.default);

exports.default = IndexedShadowImpl;