var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
    return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
    if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
var __param = (this && this.__param) || function (paramIndex, decorator) {
    return function (target, key) { decorator(target, key, paramIndex); }
};
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
var __generator = (this && this.__generator) || function (thisArg, body) {
    var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
    return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
    function verb(n) { return function (v) { return step([n, v]); }; }
    function step(op) {
        if (f) throw new TypeError("Generator is already executing.");
        while (_) try {
            if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
            if (y = 0, t) op = [op[0] & 2, t.value];
            switch (op[0]) {
                case 0: case 1: t = op; break;
                case 4: _.label++; return { value: op[1], done: false };
                case 5: _.label++; y = op[1]; op = [0]; continue;
                case 7: op = _.ops.pop(); _.trys.pop(); continue;
                default:
                    if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
                    if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
                    if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
                    if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
                    if (t[2]) _.ops.pop();
                    _.trys.pop(); continue;
            }
            op = body.call(thisArg, _);
        } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
        if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
    }
};
(function (factory) {
    if (typeof module === "object" && typeof module.exports === "object") {
        var v = factory(require, exports);
        if (v !== undefined) module.exports = v;
    }
    else if (typeof define === "function" && define.amd) {
        define(["require", "exports", "../App", "../Atom", "../core/AtomBinder", "../core/AtomDisposableList", "../core/AtomWatcher", "../core/BindableProperty", "../core/PropertyBinding", "../di/Inject", "./baseTypes"], factory);
    }
})(function (require, exports) {
    "use strict";
    Object.defineProperty(exports, "__esModule", { value: true });
    var App_1 = require("../App");
    var Atom_1 = require("../Atom");
    var AtomBinder_1 = require("../core/AtomBinder");
    var AtomDisposableList_1 = require("../core/AtomDisposableList");
    var AtomWatcher_1 = require("../core/AtomWatcher");
    var BindableProperty_1 = require("../core/BindableProperty");
    var PropertyBinding_1 = require("../core/PropertyBinding");
    var Inject_1 = require("../di/Inject");
    var baseTypes_1 = require("./baseTypes");
    function runDecoratorInits() {
        var v = this.constructor.prototype;
        if (!v) {
            return;
        }
        var ris = v._$_inits;
        if (ris) {
            for (var _i = 0, ris_1 = ris; _i < ris_1.length; _i++) {
                var ri = ris_1[_i];
                ri.call(this, this);
            }
        }
    }
    function privateInit() {
        return __awaiter(this, void 0, void 0, function () {
            var _i, _a, i, pi, _b, pi_1, iterator;
            var _this = this;
            return __generator(this, function (_c) {
                switch (_c.label) {
                    case 0:
                        _c.trys.push([0, , 3, 4]);
                        return [4 /*yield*/, Atom_1.Atom.postAsync(this.app, function () { return __awaiter(_this, void 0, void 0, function () {
                                return __generator(this, function (_a) {
                                    runDecoratorInits.apply(this);
                                    return [2 /*return*/];
                                });
                            }); })];
                    case 1:
                        _c.sent();
                        return [4 /*yield*/, Atom_1.Atom.postAsync(this.app, function () { return __awaiter(_this, void 0, void 0, function () {
                                return __generator(this, function (_a) {
                                    switch (_a.label) {
                                        case 0: return [4 /*yield*/, this.init()];
                                        case 1:
                                            _a.sent();
                                            this.onReady();
                                            return [2 /*return*/];
                                    }
                                });
                            }); })];
                    case 2:
                        _c.sent();
                        if (this.postInit) {
                            for (_i = 0, _a = this.postInit; _i < _a.length; _i++) {
                                i = _a[_i];
                                i();
                            }
                            this.postInit = null;
                        }
                        return [3 /*break*/, 4];
                    case 3:
                        pi = this.pendingInits;
                        this.pendingInits = null;
                        for (_b = 0, pi_1 = pi; _b < pi_1.length; _b++) {
                            iterator = pi_1[_b];
                            iterator();
                        }
                        return [7 /*endfinally*/];
                    case 4: return [2 /*return*/];
                }
            });
        });
    }
    /**
     * Useful only for Unit testing, this function will await till initialization is
     * complete and all pending functions are executed
     */
    function waitForReady(vm) {
        return __awaiter(this, void 0, void 0, function () {
            return __generator(this, function (_a) {
                switch (_a.label) {
                    case 0:
                        if (!vm.pendingInits) return [3 /*break*/, 2];
                        return [4 /*yield*/, Atom_1.Atom.delay(100)];
                    case 1:
                        _a.sent();
                        return [3 /*break*/, 0];
                    case 2: return [2 /*return*/];
                }
            });
        });
    }
    exports.waitForReady = waitForReady;
    /**
     *
     *
     * @export
     * @class AtomViewModel
     * @extends {AtomModel}
     */
    var AtomViewModel = /** @class */ (function () {
        function AtomViewModel(app) {
            var _this = this;
            this.app = app;
            this.disposables = null;
            this.validations = [];
            this.pendingInits = [];
            this.mShouldValidate = false;
            this.app.runAsync(function () { return privateInit.apply(_this); });
        }
        Object.defineProperty(AtomViewModel.prototype, "isReady", {
            get: function () {
                return this.pendingInits === null;
            },
            enumerable: true,
            configurable: true
        });
        Object.defineProperty(AtomViewModel.prototype, "errors", {
            get: function () {
                var e = [];
                if (!this.mShouldValidate) {
                    return e;
                }
                for (var _i = 0, _a = this.validations; _i < _a.length; _i++) {
                    var v = _a[_i];
                    if (!v.initialized) {
                        return e;
                    }
                    var error = this[v.name];
                    if (error) {
                        e.push({ name: v.name, error: error });
                    }
                }
                return e;
            },
            enumerable: true,
            configurable: true
        });
        Object.defineProperty(AtomViewModel.prototype, "parent", {
            get: function () {
                return this.mParent;
            },
            set: function (v) {
                var _this = this;
                if (this.mParent && this.mParent.mChildren) {
                    this.mParent.mChildren.remove(this);
                }
                this.mParent = v;
                if (v) {
                    var c_1 = v.mChildren || (v.mChildren = []);
                    c_1.add(this);
                    this.registerDisposable({
                        dispose: function () {
                            c_1.remove(_this);
                        }
                    });
                }
            },
            enumerable: true,
            configurable: true
        });
        Object.defineProperty(AtomViewModel.prototype, "isValid", {
            get: function () {
                var valid = true;
                this.mShouldValidate = true;
                for (var _i = 0, _a = this.validations; _i < _a.length; _i++) {
                    var v = _a[_i];
                    if (!v.initialized) {
                        v.watcher.init(true);
                        v.initialized = true;
                    }
                    if (this[v.name]) {
                        valid = false;
                    }
                }
                if (this.mChildren) {
                    for (var _b = 0, _c = this.mChildren; _b < _c.length; _b++) {
                        var child = _c[_b];
                        if (!child.isValid) {
                            valid = false;
                        }
                    }
                }
                AtomBinder_1.AtomBinder.refreshValue(this, "errors");
                return valid;
            },
            enumerable: true,
            configurable: true
        });
        /**
         * Resets validations and all errors are removed.
         * @param resetChildren reset child view models as well. Default is true.
         */
        AtomViewModel.prototype.resetValidations = function (resetChildren) {
            if (resetChildren === void 0) { resetChildren = true; }
            this.mShouldValidate = false;
            for (var _i = 0, _a = this.validations; _i < _a.length; _i++) {
                var v = _a[_i];
                this.refresh(v.name);
            }
            if (resetChildren && this.mChildren) {
                for (var _b = 0, _c = this.mChildren; _b < _c.length; _b++) {
                    var iterator = _c[_b];
                    iterator.resetValidations(resetChildren);
                }
            }
        };
        /**
         * Runs function after initialization is complete.
         * @param f function to execute
         */
        AtomViewModel.prototype.runAfterInit = function (f) {
            if (this.pendingInits) {
                this.pendingInits.push(f);
                return;
            }
            f();
        };
        /**
         * Binds source property to target property with optional two ways
         * @param target target whose property will be set
         * @param propertyName name of target property
         * @param source source to read property from
         * @param path property path of source
         * @param twoWays optional, two ways IValueConverter
         */
        AtomViewModel.prototype.bind = function (target, propertyName, source, path, twoWays) {
            var pb = new PropertyBinding_1.PropertyBinding(target, null, propertyName, path, (twoWays && typeof twoWays !== "function") ? true : false, twoWays, source);
            return this.registerDisposable(pb);
        };
        /**
         * Refreshes bindings associated with given property name
         * @param name name of property
         */
        AtomViewModel.prototype.refresh = function (name) {
            AtomBinder_1.AtomBinder.refreshValue(this, name);
        };
        /**
         * Put your asynchronous initialization here
         *
         * @returns {Promise<any>}
         * @memberof AtomViewModel
         */
        // tslint:disable-next-line:no-empty
        AtomViewModel.prototype.init = function () {
            return __awaiter(this, void 0, void 0, function () {
                return __generator(this, function (_a) {
                    return [2 /*return*/];
                });
            });
        };
        /**
         * dispose method will be called when attached view will be disposed or
         * when a new view model will be assigned to view, old view model will be disposed.
         *
         * @memberof AtomViewModel
         */
        AtomViewModel.prototype.dispose = function () {
            if (this.disposables) {
                this.disposables.dispose();
            }
        };
        // /**
        //  * Internal method, do not use, instead use errors.hasErrors()
        //  *
        //  * @memberof AtomViewModel
        //  */
        // public runValidation(): void {
        //     for (const v of this.validations) {
        //         v.watcher.evaluate(true);
        //     }
        // }
        /**
         * Register a disposable to be disposed when view model will be disposed.
         *
         * @protected
         * @param {IDisposable} d
         * @memberof AtomViewModel
         */
        AtomViewModel.prototype.registerDisposable = function (d) {
            this.disposables = this.disposables || new AtomDisposableList_1.AtomDisposableList();
            return this.disposables.add(d);
        };
        // tslint:disable-next-line:no-empty
        AtomViewModel.prototype.onReady = function () { };
        /**
         * Execute given expression whenever any bindable expression changes
         * in the expression.
         *
         * For correct generic type resolution, target must always be `this`.
         *
         *      this.setupWatch(() => {
         *          if(!this.data.fullName){
         *              this.data.fullName = `${this.data.firstName} ${this.data.lastName}`;
         *          }
         *      });
         *
         * @protected
         * @template T
         * @param {() => any} ft
         * @returns {IDisposable}
         * @memberof AtomViewModel
         */
        AtomViewModel.prototype.setupWatch = function (ft, proxy, forValidation, name) {
            var d = new AtomWatcher_1.AtomWatcher(this, ft, proxy);
            if (forValidation) {
                this.validations = this.validations || [];
                this.validations.push({ name: name, watcher: d, initialized: false });
            }
            else {
                d.init();
            }
            return this.registerDisposable(d);
        };
        // tslint:disable-next-line:no-empty
        AtomViewModel.prototype.onPropertyChanged = function (name) { };
        AtomViewModel = __decorate([
            __param(0, Inject_1.Inject),
            __metadata("design:paramtypes", [App_1.App])
        ], AtomViewModel);
        return AtomViewModel;
    }());
    exports.AtomViewModel = AtomViewModel;
    /**
     * Receive messages for given channel
     * @param {(string | RegExp)} channel
     * @returns {Function}
     */
    function Receive() {
        var channel = [];
        for (var _i = 0; _i < arguments.length; _i++) {
            channel[_i] = arguments[_i];
        }
        return function (target, key) {
            baseTypes_1.registerInit(target, function (vm) {
                // tslint:disable-next-line:ban-types
                var fx = vm[key];
                var a = function (ch, d) {
                    var p = fx.call(vm, ch, d);
                    if (p && p.then && p.catch) {
                        p.catch(function (e) {
                            // tslint:disable-next-line: no-console
                            console.warn(e);
                        });
                    }
                };
                var ivm = vm;
                for (var _i = 0, channel_1 = channel; _i < channel_1.length; _i++) {
                    var c = channel_1[_i];
                    ivm.registerDisposable(ivm.app.subscribe(c, a));
                }
            });
        };
    }
    exports.Receive = Receive;
    function BindableReceive() {
        var channel = [];
        for (var _i = 0; _i < arguments.length; _i++) {
            channel[_i] = arguments[_i];
        }
        return function (target, key) {
            var bp = BindableProperty_1.BindableProperty(target, key);
            baseTypes_1.registerInit(target, function (vm) {
                var fx = function (cx, m) {
                    vm[key] = m;
                };
                var ivm = vm;
                for (var _i = 0, channel_2 = channel; _i < channel_2.length; _i++) {
                    var c = channel_2[_i];
                    ivm.registerDisposable(ivm.app.subscribe(c, fx));
                }
            });
            return bp;
        };
    }
    exports.BindableReceive = BindableReceive;
    function BindableBroadcast() {
        var channel = [];
        for (var _i = 0; _i < arguments.length; _i++) {
            channel[_i] = arguments[_i];
        }
        return function (target, key) {
            var bp = BindableProperty_1.BindableProperty(target, key);
            baseTypes_1.registerInit(target, function (vm) {
                var fx = function (t) {
                    var v = vm[key];
                    for (var _i = 0, channel_3 = channel; _i < channel_3.length; _i++) {
                        var c = channel_3[_i];
                        vm.app.broadcast(c, v);
                    }
                };
                var d = new AtomWatcher_1.AtomWatcher(vm, [key.split(".")], fx);
                d.init();
                vm.registerDisposable(d);
            });
            return bp;
        };
    }
    exports.BindableBroadcast = BindableBroadcast;
    function Watch(target, key, descriptor) {
        baseTypes_1.registerInit(target, function (vm) {
            var ivm = vm;
            if (descriptor && descriptor.get) {
                ivm.setupWatch(descriptor.get, function () {
                    vm.refresh(key.toString());
                });
                return;
            }
            ivm.setupWatch(vm[key]);
        });
    }
    exports.Watch = Watch;
    /**
     * Cached watch must be used with async getters to avoid reloading of
     * resources unless the properties referenced are changed
     * @param target ViewModel
     * @param key name of property
     * @param descriptor descriptor of property
     */
    function CachedWatch(target, key, descriptor) {
        var getMethod = descriptor.get;
        descriptor.get = (function () { return null; });
        baseTypes_1.registerInit(target, function (vm) {
            var ivm = vm;
            var fieldName = "_" + key;
            Object.defineProperty(ivm, key, {
                enumerable: true,
                configurable: true,
                get: function () {
                    var c = ivm[fieldName] || (ivm[fieldName] = {
                        value: getMethod.apply(ivm)
                    });
                    return c.value;
                }
            });
            ivm.setupWatch(getMethod, function () {
                ivm[fieldName] = null;
                AtomBinder_1.AtomBinder.refreshValue(ivm, key);
            });
        });
    }
    exports.CachedWatch = CachedWatch;
    function Validate(target, key, descriptor) {
        // tslint:disable-next-line:ban-types
        var getMethod = descriptor.get;
        // // trick is to change property descriptor...
        // delete target[key];
        descriptor.get = function () { return null; };
        // // replace it with dummy descriptor...
        // Object.defineProperty(target, key, descriptor);
        baseTypes_1.registerInit(target, function (vm) {
            var initialized = { i: false };
            var ivm = vm;
            Object.defineProperty(ivm, key, {
                enumerable: true,
                configurable: true,
                get: function () {
                    if (vm.mShouldValidate && initialized.i) {
                        return getMethod.apply(this);
                    }
                    return null;
                }
            });
            ivm.setupWatch(getMethod, function () {
                // descriptor.get = getMethod;
                // Object.defineProperty(target, key, descriptor);
                initialized.i = true;
                vm.refresh(key.toString());
            }, true, key.toString());
            return;
        });
    }
    exports.Validate = Validate;
});
//# sourceMappingURL=AtomViewModel.js.map