"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 _akutils = require("akutils");

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

var _es6Symbol2 = _interopRequireDefault(_es6Symbol);

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

var _lodash2 = _interopRequireDefault(_lodash);

var _IsolatedProperty = require("./IsolatedProperty");

var _IsolatedProperty2 = _interopRequireDefault(_IsolatedProperty);

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

var _reshadow2 = _interopRequireDefault(_reshadow);

var _tick = require("./tick");

var _tick2 = _interopRequireDefault(_tick);

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 _findRecord = (0, _es6Symbol2.default)("findRecord");
var _makeRecord = (0, _es6Symbol2.default)("makeRecord");

var _records = (0, _es6Symbol2.default)("records");
var _store = (0, _es6Symbol2.default)("store");
/*
	Questions:
	 	* How integrate with time travel debugging
*/

var IsolatedApi = function () {
	function IsolatedApi(store) {
		_classCallCheck(this, IsolatedApi);

		this[_store] = store;
		this[_records] = {};
	}

	_createClass(IsolatedApi, [{
		key: "all",
		value: function all(owner) {
			var record = this[_findRecord](owner);

			return record ? record.all() : [];
		}
	}, {
		key: "count",
		value: function count(owner) {
			var record = this[_findRecord](owner);

			return record ? record.count() : 0;
		}
	}, {
		key: "entries",
		value: function entries(owner) {
			var record = this[_findRecord](owner);

			return record ? record.entries() : {};
		}
	}, {
		key: "get",
		value: function get(key, owner) {
			var record = this[_findRecord](owner);

			return record && record.get(key);
		}
	}, {
		key: "has",
		value: function has(key, owner) {
			var record = this[_findRecord](owner);

			return record && !!record.get(key);
		}
	}, {
		key: "invalidated",
		value: function invalidated(iso) {
			var record = this[_findRecord](iso.owner());

			record && record.invalidated(iso);
		}
	}, {
		key: "keys",
		value: function keys(owner) {
			var record = this[_findRecord](owner);

			return record ? record.keys() : [];
		}
	}, {
		key: "remove",
		value: function remove(key, owner) {
			var record = this[_findRecord](owner);

			record && record.remove(key);
		}
	}, {
		key: "removeAllFor",
		value: function removeAllFor(owner) {
			var record = this[_findRecord](owner);

			record && record.removeAll();
		}
	}, {
		key: "removeOwner",
		value: function removeOwner(owner) {
			delete this[_records][owner.dotPath()];
		}
	}, {
		key: "reset",
		value: function reset() {
			this[_records] = {};
		}
	}, {
		key: "restore",
		value: function restore(state) {
			this[_records] = {};

			if (!state) {
				return;
			}

			var store = this[_store];
			var keys = Object.keys(state);
			var time = (0, _tick2.default)();
			var key, owner, path, record;

			for (var i = 0, len = keys.length; i < len; i++) {
				key = keys[i];
				path = key === "root" ? [] : key.split(".");
				owner = store.findPropertyByPath(path);

				if (!owner) {
					continue;
				}

				record = this[_records][key] = OwnerRecord.restore(owner, state[key], time);

				record.update();
			}
		}
	}, {
		key: "serialize",
		value: function serialize() {
			var keys = Object.keys(this[_records]);
			var state = {};
			var key, owner, path;

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

				state[key] = this[_records][key].serialize();
			}

			return state;
		}
	}, {
		key: "set",
		value: function set(key, data, owner) {
			var time = arguments.length <= 3 || arguments[3] === undefined ? (0, _tick2.default)() : arguments[3];

			var record = this[_makeRecord](owner);

			record.set(key, data);
		}

		/**
  	Invoked by {@link IsolatedObjectShadowImpl#defineChildProperties} to perform updates based
  	on processed actions.
  		@param {Property} owner - the property managing isolated properties.
  */

	}, {
		key: "update",
		value: function update(owner) {
			var record = this[_findRecord](owner);

			record && record.update();
		}
	}, {
		key: _findRecord,
		value: function value(owner) {
			var records = this[_records];
			var record = records[owner.dotPath()];
			var state;

			// create new record if owner state has keys implying a reset()
			if (!record && owner.isActive() && (0, _lodash2.default)(state = owner.state())) {
				var stateKeys = Object.keys(owner.state());
				var rid = owner.dotPath();

				if (stateKeys.length) {
					record = records[rid] = new OwnerRecord(owner);
				}
			}

			return record;
		}
	}, {
		key: _makeRecord,
		value: function value(owner) {
			var records = this[_records];
			var rid = owner.dotPath();

			if (!records[rid]) {
				records[rid] = new OwnerRecord(owner);
			}

			return records[rid];
		}
	}]);

	return IsolatedApi;
}();

exports.default = IsolatedApi;

var OwnerRecord = function () {
	function OwnerRecord(owner) {
		_classCallCheck(this, OwnerRecord);

		this._owner = owner;
		this._invalidated = false;
		this._kv = {};
		this._nextKv = {};

		// create k/v entries for each existing key
		var state = owner.state();
		var keys = Object.keys(state);
		var time = owner.__().time();
		var key;

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

				this.set(key, state[keys], time);
			}

			this.update();
		}
	}

	_createClass(OwnerRecord, [{
		key: "all",
		value: function all() {
			return Object.values(this._kv);
		}
	}, {
		key: "count",
		value: function count() {
			return Object.keys(this._kv).length;
		}
	}, {
		key: "entries",
		value: function entries() {
			return _extends({}, this._kv);
		}
	}, {
		key: "get",
		value: function get(key) {
			return this._kv[key];
		}
	}, {
		key: "invalidated",
		value: function invalidated(iso) {
			this._invalidated = true;
		}
	}, {
		key: "keys",
		value: function keys() {
			return Object.keys(this._kv);
		}
	}, {
		key: "remove",
		value: function remove(key) {
			var iso = this._kv[key];
			var impl = iso && iso.__();

			if (impl) {
				impl.invalidate();
				impl.obsoleteTree();
				impl.blockFurtherUpdates(false);
			}
		}
	}, {
		key: "removeAll",
		value: function removeAll() {
			var kv = this._kv;

			for (var key in kv) {
				this.remove(key);
			}
		}
	}, {
		key: "serialize",
		value: function serialize() {
			var kv = this._kv;
			var keys = Object.keys(kv);
			var state = {};
			var iso, key;

			for (var i = 0, len = keys.length; i < len; i++) {
				key = keys[i];
				iso = kv[key];
				state[key] = iso.state().data;
			}

			return state;
		}
	}, {
		key: "set",
		value: function set(key, data, time) {
			var owner = this._owner;
			var prevIso = this._kv[key];
			var name = "<iso:" + owner.dotPath() + ":" + key + ">";
			var ownerShader = owner.shader();
			var dataType = ownerShader.typeFor(key, true);
			var isoType = _IsolatedProperty2.default.type.properties({ data: dataType });
			var iso = new _IsolatedProperty2.default(isoType);

			this._nextKv[key] = { iso: iso, data: data };

			iso.setKey(key);
			iso.setOwner(owner);
			iso.setStore(owner.store());

			// kill prevIso
			if (prevIso) {
				prevIso.__().blockFurtherUpdates(true);
			}
		}
	}, {
		key: "update",
		value: function update() {
			if (!this._invalidated && Object.keys(this._nextKv).length === 0) {
				return;
			}

			var kv = this._kv;
			var nextKv = this._nextKv;
			var time = this._owner.__().time();
			var iso, impl;

			// resahdow live properties
			for (var name in kv) {
				iso = kv[name];
				impl = iso.__();

				if (impl.replaced() || !impl.isActive()) {
					impl.obsoleteTree();
					delete kv[name];
				} else if (!impl.isValid()) {
					impl.willShadow(false);

					(0, _reshadow2.default)(time, impl.nextState(), impl);
				}
			}

			// shadow the new iso properties
			for (var _name in nextKv) {
				var next = nextKv[_name];
				var nextIso = next.iso;
				var data = next.data;
				//			const nextImpl = nextIso.shader().shadowProperty(time, name, { data });

				var state = this._owner.state()[_name];
				var nextImpl = nextIso.shader().shadowProperty(time, _name, state);

				// console.log("api update - add", name, state, this._owner.state())
				this._kv[_name] = nextIso;
			}

			this._nextKv = {};
			this._invalidated = false;
		}
	}], [{
		key: "restore",
		value: function restore(owner, state, time) {
			var record = new OwnerRecord(owner);
			var keys = Object.keys(state);
			var key;

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

				record.set(key, state[key], time);
			}

			return record;
		}
	}]);

	return OwnerRecord;
}();