"use strict";
var __extends = (this && this.__extends) || (function () {
    var extendStatics = function (d, b) {
        extendStatics = Object.setPrototypeOf ||
            ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
            function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
        return extendStatics(d, b);
    };
    return function (d, b) {
        extendStatics(d, b);
        function __() { this.constructor = d; }
        d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
    };
})();
var __assign = (this && this.__assign) || function () {
    __assign = Object.assign || function(t) {
        for (var s, i = 1, n = arguments.length; i < n; i++) {
            s = arguments[i];
            for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
                t[p] = s[p];
        }
        return t;
    };
    return __assign.apply(this, arguments);
};
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 __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
var __importStar = (this && this.__importStar) || function (mod) {
    if (mod && mod.__esModule) return mod;
    var result = {};
    if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k];
    result["default"] = mod;
    return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
var Utils_1 = __importDefault(require("../Utils"));
var _ = __importStar(require("underscore"));
var io = __importStar(require("socket.io-client"));
var Emitter_1 = __importDefault(require("../Emitter"));
var Content_1 = require("./Content");
var Provider_1 = require("./Provider");
var Provider_2 = require("./Provider");
var Provider_3 = require("./Provider");
var Range_1 = require("./Range");
var Range_2 = require("./Range");
var bluebird_1 = __importDefault(require("bluebird"));
var Actions_1 = require("../Actions/Actions");
var __1 = require("..");
var Helpers_1 = __importDefault(require("../Helpers"));
var helpers = new Helpers_1.default();
function staticImplements() {
    return function (constructor) {
        constructor;
    };
}
// Main/public page service
var api, auth, storage, crypto, config, utils, keys, providers, ranges;
var PageWrap = /** @class */ (function () {
    function PageWrap(
    // q: IQService,
    // timeout: ITimeoutService,
    // interval: IIntervalService,
    ippApi, ippAuth, ippStorage, ippKeys, ippCrypto, ippConf) {
        api = ippApi;
        auth = ippAuth;
        storage = ippStorage;
        keys = ippKeys;
        crypto = ippCrypto;
        config = ippConf;
        utils = new Utils_1.default();
        providers = new Provider_1.Providers(api, auth, storage, config);
        ranges = new Range_1.RangesWrap(api);
        // PageWrap.Page = Page;
    }
    return PageWrap;
}());
exports.PageWrap = PageWrap;
// export default PageWrap;
var Page = /** @class */ (function (_super) {
    __extends(Page, _super);
    /**
     * Starts new page object
     *
     * @param pageId
     * @param folderId
     */
    function Page(pageId, folderId, uuid) {
        var _this = _super.call(this) || this;
        /**
         * Indicates when page is ready (both content and settings/meta are loaded)
         * @type {boolean}
         */
        _this.ready = false;
        /**
         * Indicates if page is decrypted.
         * @type {boolean}
         */
        _this.decrypted = true;
        /**
         * Indicates if page updates are on - page is requesting/receiving new updates
         * @type {boolean}
         */
        _this.updatesOn = true; // @todo I dont like this...
        // public freeze: IPageFreezeRange = {
        //     valid: false,
        //     row: -1,
        //     col: -1
        // };
        /**
         * Indicates if client supports websockets
         * @type {boolean}
         * @private
         */
        _this._supportsWS = true; // let's be optimistic by default
        _this._wsDisabled = false;
        _this._checkAccess = true;
        // Ouch... but what else can I do....
        _this._contentLoaded = false;
        _this._metaLoaded = false;
        _this._accessLoaded = false;
        _this._hasAccess = false;
        _this._error = false;
        _this._destroyed = false;
        _this._encryptionKeyPull = {
            name: '',
            passphrase: ''
        };
        _this._encryptionKeyPush = {
            name: '',
            passphrase: ''
        };
        _this.onPageError = function (err) {
            if (!err) {
                return;
            }
            err.code = err.httpCode || err.code;
            err.message =
                err.httpText ||
                    err.message ||
                    (typeof err.data === 'string' ? err.data : 'Page not found');
            _this.emit(_this.EVENT_ERROR, err);
            if (err.code === 404) {
                _this.destroy();
            }
            // fallback to REST
            if (err.type === 'redirect') {
                _this._wsDisabled = true;
                _this.init(true);
            }
            else {
                _this._error = true;
            }
        };
        // this._providers = providers;
        // Types
        _this.types = {
            regular: _this.TYPE_REGULAR,
            pageAccessReport: _this.TYPE_PAGE_ACCESS_REPORT,
            domainUsageReport: _this.TYPE_DOMAIN_USAGE_REPORT,
            globalUsageReport: _this.TYPE_GLOBAL_USAGE_REPORT,
            pageUpdateReport: _this.TYPE_PAGE_UPDATE_REPORT,
            alert: _this.TYPE_ALERT,
            pdf: _this.TYPE_PDF,
            liveUsage: _this.TYPE_LIVE_USAGE_REPORT
        };
        // Decide if client can use websockets
        _this._supportsWS = 'WebSocket' in window || 'MozWebSocket' in window;
        // Process page and folder id/name
        _this._folderId = !isNaN(+folderId) ? folderId : 0;
        _this._pageId = !isNaN(+pageId) ? pageId : 0;
        _this._folderName = isNaN(+folderId) ? folderId : '';
        _this._pageName = isNaN(+pageId) ? pageId : '';
        _this._uuid = uuid || '';
        if (!_this._pageId && !_this._uuid) {
            // If we get folder name and page name, first get page id from REST and then continue with sockets - fiddly, but only way around it at the moment
            _this.getPageId(_this._folderName, _this._pageName).then(function (res) {
                if (!res.pageId) {
                    _this.onPageError({
                        code: 404,
                        message: 'Page not found'
                    });
                    return;
                }
                _this._pageId = res.pageId;
                _this._folderId = res.folderId;
                _this.init();
            }, function (err) {
                _this.onPageError(err || {
                    code: 404,
                    message: 'Page not found'
                });
            });
        }
        else {
            _this.init(_this._uuid ? true : false);
        }
        return _this;
    }
    Page_1 = Page;
    Object.defineProperty(Page.prototype, "TYPE_REGULAR", {
        get: function () {
            return 0;
        },
        enumerable: true,
        configurable: true
    });
    Object.defineProperty(Page.prototype, "TYPE_ALERT", {
        get: function () {
            return 5;
        },
        enumerable: true,
        configurable: true
    });
    Object.defineProperty(Page.prototype, "TYPE_PDF", {
        get: function () {
            return 6;
        },
        enumerable: true,
        configurable: true
    });
    Object.defineProperty(Page.prototype, "TYPE_STRUCTURED", {
        get: function () {
            return 8;
        },
        enumerable: true,
        configurable: true
    });
    Object.defineProperty(Page.prototype, "TYPE_PAGE_ACCESS_REPORT", {
        get: function () {
            return 1001;
        },
        enumerable: true,
        configurable: true
    });
    Object.defineProperty(Page.prototype, "TYPE_DOMAIN_USAGE_REPORT", {
        get: function () {
            return 1002;
        },
        enumerable: true,
        configurable: true
    });
    Object.defineProperty(Page.prototype, "TYPE_GLOBAL_USAGE_REPORT", {
        get: function () {
            return 1003;
        },
        enumerable: true,
        configurable: true
    });
    Object.defineProperty(Page.prototype, "TYPE_PAGE_UPDATE_REPORT", {
        get: function () {
            return 1004;
        },
        enumerable: true,
        configurable: true
    });
    Object.defineProperty(Page.prototype, "TYPE_LIVE_USAGE_REPORT", {
        get: function () {
            return 1007;
        },
        enumerable: true,
        configurable: true
    });
    Object.defineProperty(Page.prototype, "EVENT_READY", {
        get: function () {
            return 'ready';
        },
        enumerable: true,
        configurable: true
    });
    Object.defineProperty(Page.prototype, "EVENT_DECRYPTED", {
        get: function () {
            return 'decrypted';
        },
        enumerable: true,
        configurable: true
    });
    Object.defineProperty(Page.prototype, "EVENT_NEW_CONTENT", {
        get: function () {
            return 'new_content';
        },
        enumerable: true,
        configurable: true
    });
    Object.defineProperty(Page.prototype, "EVENT_NEW_CONTENT_DELTA", {
        get: function () {
            return 'new_content_delta';
        },
        enumerable: true,
        configurable: true
    });
    Object.defineProperty(Page.prototype, "EVENT_EMPTY_UPDATE", {
        get: function () {
            return 'empty_update';
        },
        enumerable: true,
        configurable: true
    });
    Object.defineProperty(Page.prototype, "EVENT_NEW_META", {
        get: function () {
            return 'new_meta';
        },
        enumerable: true,
        configurable: true
    });
    Object.defineProperty(Page.prototype, "EVENT_RANGES_UPDATED", {
        get: function () {
            return 'ranges_updated';
        },
        enumerable: true,
        configurable: true
    });
    Object.defineProperty(Page.prototype, "EVENT_COLUMN_DEFS_UPDATED", {
        get: function () {
            return 'column_defs_updated';
        },
        enumerable: true,
        configurable: true
    });
    Object.defineProperty(Page.prototype, "EVENT_ACCESS_UPDATED", {
        get: function () {
            return 'access_updated';
        },
        enumerable: true,
        configurable: true
    });
    Object.defineProperty(Page.prototype, "EVENT_ERROR", {
        get: function () {
            return 'error';
        },
        enumerable: true,
        configurable: true
    });
    Object.defineProperty(Page.prototype, "EVENT_RESET", {
        get: function () {
            return 'reset';
        },
        enumerable: true,
        configurable: true
    });
    // private _providers: any;
    /**
     * Creates new page in the system
     *
     * @param folderId
     * @param name
     * @param type
     * @param template
     * @returns {Promise<IPageMain>}
     */
    Page.create = function (folderId, name, type, template, organization_public) {
        if (type === void 0) { type = 0; }
        if (organization_public === void 0) { organization_public = false; }
        var p = new bluebird_1.default(function (resolve, reject) {
            if (template) {
                var page_1 = new Page_1(template.id, template.domain_id);
                page_1.on(page_1.EVENT_READY, function () {
                    page_1
                        .clone(folderId, name)
                        .then(resolve, reject)
                        .finally(function () {
                        page_1.destroy();
                    });
                });
            }
            else {
                api
                    .createPage({
                    domainId: folderId,
                    data: {
                        name: name,
                        special_page_type: type,
                        organization_public: organization_public
                    }
                })
                    .then(function (res) {
                    // Start new page
                    // @todo Why ?
                    var page = new Page_1(res.data.id, folderId);
                    page.on(page.EVENT_READY, function () {
                        page.stop();
                        resolve(page);
                    });
                    page.on(page.EVENT_ERROR, function (err) {
                        page.stop();
                        reject(err);
                    });
                }, function (err) {
                    reject(err);
                });
            }
        });
        return p;
    };
    Object.defineProperty(Page.prototype, "encryptionKeyPull", {
        /**
         * Setter for pull encryption key
         * @param key
         */
        set: function (key) {
            this._encryptionKeyPull = key;
        },
        enumerable: true,
        configurable: true
    });
    Object.defineProperty(Page.prototype, "encryptionKeyPush", {
        /**
         * Setter for push encryption key
         * @param key
         */
        set: function (key) {
            this._encryptionKeyPush = key;
        },
        enumerable: true,
        configurable: true
    });
    Object.defineProperty(Page.prototype, "data", {
        /**
         * Getter for page data
         * @returns {IPage}
         */
        get: function () {
            return this._data;
        },
        enumerable: true,
        configurable: true
    });
    Object.defineProperty(Page.prototype, "access", {
        /**
         * Getter for page access
         * @returns {IUserPageAccess}
         */
        get: function () {
            return this._access;
        },
        enumerable: true,
        configurable: true
    });
    Object.defineProperty(Page.prototype, "error", {
        get: function () {
            return this._error;
        },
        enumerable: true,
        configurable: true
    });
    /**
     * Start page updates
     */
    Page.prototype.start = function () {
        if (!this.updatesOn) {
            this._provider.start();
            this.updatesOn = true;
        }
    };
    /**
     * Stop page updates
     */
    Page.prototype.stop = function () {
        if (this.updatesOn) {
            this._provider.stop();
            this.updatesOn = false;
        }
    };
    /**
     * Push new data to a page. This method accepts either full page content or delta content update
     * @param forceFull
     * @returns {Promise<any>}
     */
    Page.prototype.push = function (forceFull, otherRequestData) {
        var _this = this;
        if (forceFull === void 0) { forceFull = false; }
        if (otherRequestData === void 0) { otherRequestData = {}; }
        var p = new bluebird_1.default(function (resolve, reject) {
            // let currentData: any = _.clone(this.Content ? this.Content.original : undefined);
            // check if we have content
            if (!_this.data.content.length || !_this.data.content[0].length) {
                forceFull = true;
            }
            // this.Content.dirty = false;
            var deltas = _this.Content.deltas.concat();
            // let deltaUpdate: IPageDelta = <IPageDelta>(
            //   this.Content.getCellDeltas(
            //     this.data.special_page_type === this.TYPE_STRUCTURED
            //   )
            // );
            var onSuccess = function (data) {
                _this.Content.dirty = false;
                // this.Content.cleanDirty();
                // this.Content.update(this.Content.current, !this.Content.dirty); // @todo Ouch!
                _this._data.content_modified_timestamp = new Date();
                _this._data.content_modified_by = auth.user;
                // @todo this._data.content has old value
                data = __assign({}, _this._data, data.data);
                if (_this._provider instanceof Provider_2.ProviderREST) {
                    _this._provider.seqNo = data.seq_no;
                }
                // let diff: IUtilsDiff | false = currentData
                //   ? utils.comparePageContent(currentData, this.Content.current, true)
                //   : false;
                // if (diff) {
                //   data.diff = diff;
                // } else {
                //   data.diff = undefined;
                // }
                data._provider = false;
                if (deltas.length) {
                    data.diff = {
                        content_diff: deltas,
                        content: _this.Content.current,
                        colDiff: 0,
                        rowDiff: 0,
                        sizeDiff: 0
                    };
                }
                else {
                    data.diff = undefined;
                }
                // if (delta) {
                if (deltas.length && !forceFull) {
                    _this.Content.updateDelta(deltas);
                    _this.Functions.updateContentDelta(deltas);
                    _this.emit(_this.EVENT_NEW_CONTENT_DELTA, data);
                }
                if (_this._provider instanceof Provider_2.ProviderREST) {
                    _this._provider.requestOngoing = false;
                }
                // } else {
                // this.emit(this.EVENT_NEW_CONTENT, data);
                // }
                // this.start();
                resolve(data);
            };
            // structure pages
            if (_this.data.special_page_type === _this.TYPE_STRUCTURED) {
                var query = _this.Content.getQuery();
                if (query.error) {
                    reject({
                        code: 400,
                        message: query.error
                    });
                    return;
                }
                api.queryPageContent({ pageId: _this.data.id, data: query }).then(onSuccess).catch(reject);
                return;
            }
            var deltaUpdate = (_this.Content.getCellDeltas());
            if (!_this._data.encryption_type_to_use &&
                !_this._data.encryption_type_used &&
                _this.Content.canDoDelta &&
                !forceFull) {
                if (_this._provider instanceof Provider_2.ProviderREST) {
                    _this._provider.requestOngoing = true;
                }
                _this.pushDelta(deltaUpdate, otherRequestData)
                    .then(onSuccess)
                    .catch(reject);
            }
            else {
                _this.pushFull((_this.Content.getFull()), otherRequestData)
                    .then(onSuccess)
                    .catch(reject);
            }
        });
        return p;
    };
    /**
     * Save page settings/meta
     * @param data
     * @returns {Promise<any>}
     */
    Page.prototype.saveMeta = function (data) {
        var _this = this;
        var p = new bluebird_1.default(function (resolve, reject) {
            // Remove access rights (if any)
            // delete data.access_rights;
            // Just a small condition - this seems to be left behind quite often
            if (data.encryption_type_to_use === 0) {
                data.encryption_key_to_use = '';
            }
            // @todo Validation
            api
                .savePageSettings({
                domainId: _this._folderId,
                pageId: _this._pageId,
                data: data
            })
                .then(function (res) {
                // Apply data to current object
                _this._data = __assign({}, _this._data, res.data);
                _this.emit(_this.EVENT_NEW_META, res.data); // @todo I am not sure about this...
                resolve(res);
            }, function (err) {
                reject(utils.parseApiError(err, 'Could not save page settings'));
            });
        });
        return p;
    };
    /**
     * Sets current page as folders default for current user
     * @returns {Promise<IRequestResult>}
     */
    Page.prototype.setAsFoldersDefault = function () {
        var _this = this;
        var p = new bluebird_1.default(function (resolve, reject) {
            var requestData = {
                domainId: _this._folderId,
                data: {
                    default_page_id: _this._pageId
                }
            };
            api.setDomainDefault(requestData).then(function (res) {
                // @todo probably not a best way to do that, but is there any other option?
                _this._access.is_users_default_page = true;
                resolve(res);
            }, reject);
        });
        return p;
    };
    /**
     * Deletes current page
     * @returns {Promise<IRequestResult>}
     */
    Page.prototype.del = function () {
        var requestData = {
            domainId: this._folderId,
            pageId: this._pageId
        };
        return api.deletePage(requestData);
    };
    /**
     * Check if page is encrypted and decrypt it if it is
     * @param key
     */
    // @todo This is NOT good
    Page.prototype.decrypt = function (key) {
        // @todo Oh lord...
        if (!key) {
            key = this._encryptionKeyPull.name
                ? this._encryptionKeyPull
                : keys.getKey(this.access ? this.access.domain.name : '', this.data.encryption_key_used);
        }
        // Fail silently if we dont have passphrase
        // @todo oh sweet jesus...
        if (this._data.encryption_type_used && !key.passphrase) {
            this.decrypted = false;
            return;
        }
        // Check for encryption and decrypt
        if (this._data.encryption_type_used) {
            if (!crypto) {
                this.emit(this.EVENT_ERROR, new Error("Encrypted pages not supported"));
                this.decrypted = false;
                return;
            }
            var decrypted = crypto.decryptContent({
                name: key.name,
                passphrase: key.passphrase
            }, this._data.encrypted_content);
            if (decrypted) {
                this.decrypted = true;
                this._data.content = decrypted;
                this._encryptionKeyPull = key;
                this._encryptionKeyPush = key;
                keys.saveKey(this.access.domain.name, key);
            }
            else {
                this.decrypted = false;
                // @todo I am pretty sure we will want something more specific for decryption than just message
                this.emit(this.EVENT_ERROR, new Error("Could not decrypt page with key \"" + key.name + "\" and passphrase \"" + key.passphrase + "\""));
            }
        }
        else {
            this.decrypted = true;
        }
        // @todo ouch... should not be here
        if (this.decrypted) {
            this._error = false;
            if (this.Content) {
                this.Content.update(this._data.content, false, false);
                this.Functions.content = this.Content.current;
                // this.Actions.parse(this._data.action_definitions);
            }
            else {
                this.Content = new Content_1.PageContent(this._data.content);
                this.Functions = new __1.Functions(this.Content.current, {
                    id: auth.user.id,
                    username: auth.user.screen_name,
                    lastname: auth.user.last_name,
                    firstname: auth.user.first_name,
                    email: auth.user.email,
                    pageid: "" + this.data.id,
                    pagename: this.data.name,
                    pagedescription: this.data.description,
                    folderid: "" + this.data.domain_id,
                    foldername: this.data.domain_name,
                    folderdescription: this.access.domain.description
                });
                if (this.Actions) {
                    this.Actions.Functions = this.Functions;
                    this.Actions.Content = this.Content;
                }
                // this.Actions = new Actions(api, this.Content, this.Functions, this._folderId, this._pageId);
                // this.Actions.on(this.Actions.EVENT_UPDATED, () => {
                //   console.log('this.Actions.EVENT_UPDATED', 'decrypt');
                //   this.emit(this.EVENT_RANGES_UPDATED);
                // });
                // this.Actions.parse(this._data.action_definitions);
            }
            this.Content.structured = this.data.special_page_type === this.TYPE_STRUCTURED;
            // @todo Emitting Decrypted and New content events will lead to confusion. Eventually you will want to subscribe to both for rendering, so you will have double rendering
            this.emit(this.EVENT_DECRYPTED);
        }
    };
    /**
     * Destroy page object
     */
    Page.prototype.destroy = function () {
        if (this._provider) {
            this._provider.destroy();
        }
        // $interval.cancel(this._accessInterval);
        this.removeEvent();
        this._checkAccess = false;
        this.ready = false;
        this._destroyed = true;
    };
    Page.prototype.reset = function () {
        this.Content.reset();
        this.Functions.content = this.Content.current;
        this.emit(this.EVENT_RESET);
    };
    /**
     * Clone current page. Clones page content and some settings, can specify more options via options param.
     * @param folderId
     * @param name
     * @param options
     * @deprecated
     * @returns {Promise<IPageMain>}
     */
    Page.prototype.clone = function (folderId, name, options) {
        var _this = this;
        if (options === void 0) { options = {}; }
        var p = new bluebird_1.default(function (resolve, reject) {
            if (!_this.ready) {
                reject('Page is not ready');
                return;
            }
            // Prevent cloning ranges between folders
            // @todo This is done silently at the moment, should it reject the transaction?
            if (options.clone_ranges && _this._folderId * 1 !== folderId * 1) {
                options.clone_ranges = false;
            }
            // Create new page
            Page_1.create(folderId, name, _this._data.special_page_type).then(function (newPage) {
                newPage.Content = _this.Content;
                bluebird_1.default.all([newPage.push(true)]).then(function (res) {
                    if (options.clone_ranges) {
                        api
                            .savePageSettings({
                            domainId: folderId,
                            pageId: newPage.data.id,
                            data: {
                                access_rights: _this._data.access_rights,
                                action_definitions: _this._data.action_definitions
                            }
                        })
                            .finally(function () {
                            resolve(newPage);
                        });
                    }
                    else {
                        resolve(newPage);
                    }
                }, reject); // @todo Handle properly
            }, function (err) {
                reject(err);
            });
        });
        return p;
    };
    /**
     * Clone current page. Clones page content and some settings, can specify more options via options param.
     * @param folderId
     * @param name
     * @param copyContent
     * @param data
     * @returns {Promise<IPageMain>}
     */
    Page.prototype.copy = function (folderId, name, options) {
        var _this = this;
        if (options === void 0) { options = {}; }
        var p = new bluebird_1.default(function (resolve, reject) {
            if (!_this.ready) {
                reject('Page is not ready');
                return;
            }
            var data = {
                range_access: true,
                action_definitions: true,
                description: true,
                push_interval: true,
                pull_interval: true,
                is_public: true,
                encryption_type_to_use: true,
                encryption_key_to_use: true,
                background_color: true,
                is_obscured_public: true,
                record_history: true,
                symphony_sid: true
            };
            // Prevent cloning ranges between folders
            // @todo This is done silently at the moment, should it reject the transaction?
            if (!options.access_rights ||
                (options.access_rights && _this._folderId * 1 !== folderId * 1)) {
                data.range_access = false;
                data.action_definitions = false;
            }
            // Gather settings data
            var settingsData = {};
            for (var key in data) {
                if (!data[key] || !_this._data[key]) {
                    continue;
                }
                settingsData[key] = _this._data[key];
            }
            // Create new page
            Page_1.create(folderId, name, _this._data.special_page_type).then(function (newPage) {
                if (options.content && Object.keys(settingsData).length > 0) {
                    newPage.Content = _this.Content;
                    newPage.push(true).then(function () {
                        api
                            .savePageSettings({
                            domainId: folderId,
                            pageId: newPage.data.id,
                            data: settingsData
                        })
                            .then(function () {
                            resolve(newPage);
                        }, reject);
                    }, reject);
                }
                else if (options.content) {
                    newPage.Content = _this.Content;
                    newPage.push(true).then(function () {
                        resolve(newPage);
                    }, reject);
                }
                else if (Object.keys(settingsData).length > 0) {
                    api
                        .savePageSettings({
                        domainId: folderId,
                        pageId: newPage.data.id,
                        data: settingsData
                    })
                        .then(function () {
                        resolve(newPage);
                    }, reject);
                }
                else {
                    resolve(newPage);
                }
            }, function (err) {
                reject(err);
            });
        });
        return p;
    };
    Page.prototype.canEdit = function () {
        if (this._error)
            return false;
        if (this.data.organization_public === 'rw')
            return true;
        if (this.access && this.access.write_access)
            return true;
        // if (this.access && this.access.domain && !this.access.domain.current_user_domain_access) return false;
        // if (this.data.organization_public !== 'rw') return false;
        // if (this.access && this.access.domain && this.access.domain.current_user_domain_access.access_level !== 'RW') return false;
        return false;
    };
    Page.prototype.canEditSettings = function () {
        if (this._error)
            return false;
        return this._hasAccess;
    };
    Page.prototype.getColumnNames = function () {
        var names = [];
        if (!this.Content)
            return names;
        for (var i = 0; i < this.Content.current[0].length; i++) {
            names.push({
                index: i,
                column: helpers.toColumnName(i + 1),
                value: "" + (this.Content.current[0][i].formatted_value ||
                    this.Content.current[0][i].value)
            });
        }
        return names;
    };
    Page.prototype.getUniqueValuesForColumn = function (colIndex, type, headersRows) {
        if (type === void 0) { type = 'string'; }
        if (headersRows === void 0) { headersRows = 0; }
        var values = [];
        if (!this.Content)
            return values;
        for (var rowIndex = headersRows; rowIndex < this.Content.current.length; rowIndex++) {
            var value = type === 'string'
                ? "" + (this.Content.current[rowIndex][colIndex].formatted_value ||
                    this.Content.current[rowIndex][colIndex].value)
                : parseFloat("" + this.Content.current[rowIndex][colIndex].value);
            if (type === 'number' && isNaN(value)) {
                value = '';
            }
            if (values.indexOf(value) < 0) {
                values.push(value);
            }
        }
        return values.sort();
    };
    Page.prototype.getColumnDefs = function () {
        if (this.data &&
            this.data.page_schema &&
            this.data.page_schema.schema &&
            this.data.page_schema.schema.columns)
            return this.data.page_schema.schema.columns || [];
        return [];
    };
    Page.prototype.reloadActions = function () {
        this.Actions.parse(this.data.action_definitions);
    };
    Page.prototype.createProvider = function (ignoreWS) {
        if (this._provider) {
            this._provider.destroy();
        }
        this._wsDisabled =
            ignoreWS ||
                !this._supportsWS ||
                typeof io === 'undefined' ||
                config.transport === 'polling' ||
                !io.connect ||
                !this.access ||
                !this.access.ws_enabled;
        this._provider = this._wsDisabled
            ? new Provider_2.ProviderREST(this._pageId, this._folderId, this._uuid)
            : new Provider_3.ProviderSocket(this._pageId, this._folderId);
        this._checkAccess = true;
    };
    /**
     * Actual bootstrap of the page, starts page updates, registeres provider, starts page access updates
     */
    Page.prototype.init = function (ignoreWS) {
        var _this = this;
        if (!this._supportsWS || typeof io === 'undefined') {
            console.warn('[iPushPull] Cannot use websocket technology as it is not supported or websocket library is not included');
        }
        if (this._uuid) {
            this.createProvider(ignoreWS);
            this._accessLoaded = true;
            this.registerListeners();
            return;
        }
        // Start pulling page access
        this.getPageAccess().then(function () {
            if (_this._destroyed)
                return;
            _this.createProvider(ignoreWS);
            _this.registerListeners();
            _this.checkPageAccess();
        });
        // Create Ranges object
        // this.Ranges = new Ranges(this._folderId, this._pageId);
        // this.Ranges.on(this.Ranges.EVENT_UPDATED, () => {
        //   console.log('this.Ranges.EVENT_UPDATED', 'init');
        //   this.emit(this.EVENT_RANGES_UPDATED);
        // });
        // Create Actions object
        // this.setFreezeRange();
    };
    // private setFreezeRange(): void {
    //     this.Ranges.ranges.forEach(range => {
    //         if (range.name === "frozen_rows") {
    //             this.freeze.row = range.count;
    //         } else if (range.name === "frozen_cols") {
    //             this.freeze.col = range.count;
    //         }
    //     });
    //     this.freeze.valid = this.freeze.row > 0 || this.freeze.col > 0;
    // }
    Page.prototype.checkPageAccess = function () {
        var _this = this;
        if (!this._checkAccess) {
            return;
        }
        this.getPageAccess().finally(function () {
            setTimeout(function () {
                _this.checkPageAccess();
            }, 30000);
        });
    };
    /**
     * In case page is requested with name, get page ID from service
     * @param folderName
     * @param pageName
     * @returns {Promise<any>}
     */
    Page.prototype.getPageId = function (folderName, pageName) {
        var p = new bluebird_1.default(function (resolve, reject) {
            // @todo Need specific/lightweight endpoint - before arguing my way through this, I can use page detail (or write my own)
            api.getPageByName({ domainId: folderName, pageId: pageName }).then(function (res) {
                resolve({
                    pageId: res.data.id,
                    folderId: res.data.domain_id,
                    wsEnabled: res.data.ws_enabled
                });
            }, function (err) {
                // Convert it into socket error
                reject(err);
            });
        });
        return p;
    };
    /**
     * Load page access
     * @returns {Promise<any>}
     */
    Page.prototype.getPageAccess = function () {
        var _this = this;
        var p = new bluebird_1.default(function (resolve, reject) {
            api
                .getPageAccess({
                domainId: _this._folderId,
                pageId: _this._pageId
            })
                .then(function (res) {
                if (res.httpCode && res.httpCode >= 300) {
                    reject({
                        code: res.httpCode,
                        message: res.data ? res.data.detail : 'Unauthorized access'
                    });
                    throw 'return';
                }
                // auth recovery
                if (_this._error) {
                    _this._error = false;
                    _this.emit(_this.EVENT_READY, true);
                    resolve();
                    throw 'return';
                }
                var prevAccess = JSON.stringify(_this._access);
                var newAccess = JSON.stringify(res.data);
                _this._access = res.data;
                _this._accessLoaded = true;
                _this.checkReady(); // @todo ouch...
                if (prevAccess !== undefined && prevAccess !== newAccess) {
                    _this.emit(_this.EVENT_ACCESS_UPDATED, {
                        before: prevAccess,
                        after: newAccess
                    });
                    _this.Functions.updateVars({
                        id: auth.user.id,
                        username: auth.user.screen_name,
                        lastname: auth.user.last_name,
                        firstname: auth.user.first_name,
                        email: auth.user.email,
                        pageid: "" + _this.data.id,
                        pagename: _this.data.name,
                        pagedescription: _this.data.description,
                        folderid: "" + _this.data.domain_id,
                        foldername: _this.data.domain_name,
                        folderdescription: _this.access.domain.description
                    });
                }
                // resolve();
                api
                    .getPageById({
                    domainId: _this._folderId,
                    pageId: _this._pageId
                })
                    .then(function (res) {
                    _this._hasAccess = true;
                    _this.applyMetaUpdate(res.data);
                    resolve();
                })
                    .catch(function () {
                    _this._hasAccess = false;
                    // this.applyMetaUpdate(res.data);
                    resolve();
                });
            })
                .catch(function (err) {
                if (err === 'return')
                    return;
                _this.onPageError(err);
                reject();
            });
        });
        return p;
    };
    /**
     * Register listeners. THese are listeners on events emitted from page providers, which are processed and then re-emitted to public
     */
    Page.prototype.registerListeners = function () {
        var _this = this;
        // Setup listeners
        this._provider.on('content_update', function (data) {
            data.special_page_type = _this.updatePageType(data.special_page_type);
            var currentData = _.clone(_this.Content ? _this.Content.current : 0);
            _this._data = __assign({}, _this._data, data);
            _this.decrypt();
            _this._contentLoaded = true;
            // emit page if ready
            _this.checkReady();
            // @todo This should be emitted before decryption probably
            var diff = currentData
                ? utils.comparePageContent(currentData, _this.Content.current)
                : false;
            if (diff) {
                _this._data.diff = diff;
            }
            else {
                _this._data.diff = undefined;
            }
            _this._data._provider = true;
            _this.emit(_this.EVENT_NEW_CONTENT, _this._data);
        });
        this._provider.on('meta_update', function (data) {
            // if (this._metaLoaded) return;s
            _this.applyMetaUpdate(data);
            // Check if this page should be handled by websockets and switch
            if (data.ws_enabled && !(_this._provider instanceof Provider_3.ProviderSocket)) {
                if (_this._supportsWS && !_this._wsDisabled) {
                    _this._provider.destroy();
                    _this._provider = new Provider_3.ProviderSocket(_this._pageId, _this._folderId);
                    _this.registerListeners();
                }
            }
        });
        this._provider.on('error', this.onPageError);
        this._provider.on('empty_update', function () {
            _this._error = false;
            _this.emit(_this.EVENT_EMPTY_UPDATE, _this._data);
        });
        auth.on(auth.EVENT_LOGGED_IN, function () {
            console.warn('EVENT_LOGGED_IN');
            _this.getPageAccess();
        });
        auth.on(auth.EVENT_LOGGED_OUT, function () {
            console.warn('EVENT_LOGGED_OUT');
            _this.getPageAccess();
        });
    };
    /**
     * Push full content update
     * @param content
     * @returns {Promise<any>}
     */
    Page.prototype.pushFull = function (content, otherRequestData) {
        var _this = this;
        if (otherRequestData === void 0) { otherRequestData = {}; }
        var p = new bluebird_1.default(function (resolve, reject) {
            // If encrypted
            if (_this._data.encryption_type_to_use) {
                if (!_this._encryptionKeyPush ||
                    _this._data.encryption_key_to_use !== _this._encryptionKeyPush.name) {
                    // @todo Proper error
                    reject('None or wrong encryption key');
                    return;
                }
                var encrypted = _this.encrypt(_this._encryptionKeyPush, content);
                if (encrypted) {
                    _this._data.encrypted_content = encrypted;
                    _this._data.encryption_type_used = 1;
                    _this._data.encryption_key_used = _this._encryptionKeyPush.name;
                    // Ehm...
                    _this._encryptionKeyPull = _.clone(_this._encryptionKeyPush);
                }
                else {
                    // @todo proper error
                    reject('Encryption failed');
                    return;
                }
            }
            else {
                // @todo: webservice should do this automatically
                _this._data.encryption_key_used = '';
                _this._data.encryption_type_used = 0;
                _this._data.content = content;
            }
            var data = {
                content: !_this._data.encryption_type_used ? _this._data.content : '',
                encrypted_content: _this._data.encrypted_content,
                encryption_type_used: _this._data.encryption_type_used,
                encryption_key_used: _this._data.encryption_key_used
            };
            data = __assign({}, data, otherRequestData);
            var requestData = {
                domainId: _this._folderId,
                pageId: _this._pageId,
                data: data
            };
            api.savePageContent(requestData).then(function (res) {
                // @todo Do we need this? should be probably updated in rest - if not updated rest will load update even though it already has it
                _this._data.seq_no = res.data.seq_no;
                resolve(res);
            }, reject);
        });
        return p;
    };
    /**
     * Push delta content update
     * @param data
     * @returns {Promise<any>}
     */
    Page.prototype.pushDelta = function (data, otherRequestData) {
        var _this = this;
        if (otherRequestData === void 0) { otherRequestData = {}; }
        var p = new bluebird_1.default(function (resolve, reject) {
            // @todo Handle empty data/delta
            data = __assign({}, data, otherRequestData);
            var requestData = {
                domainId: _this._folderId,
                pageId: _this._pageId,
                data: data
            };
            api
                .savePageContentDelta(requestData)
                .then(resolve)
                .catch(reject);
        });
        return p;
    };
    /**
     * Check if page is considered to be ready
     */
    // @todo Not a great logic - When do we consider for a page to actually be ready?
    Page.prototype.checkReady = function () {
        if (this._contentLoaded &&
            this._metaLoaded &&
            this._accessLoaded &&
            !this.ready) {
            this.ready = true;
            this.emit(this.EVENT_READY);
        }
    };
    /**
     * Temporary fix to update page types. This will take any page report types and adds 1000 to them. This way can do easier filtering.
     * @param pageType
     * @returns {number}
     */
    Page.prototype.updatePageType = function (pageType) {
        if ((pageType > 0 && pageType < 5) || pageType === 7) {
            pageType += 1000;
        }
        return pageType;
    };
    /**
     * Encrypt page content with given key
     * @param key
     * @param content
     * @returns {string}
     */
    Page.prototype.encrypt = function (key, content) {
        // @todo: Handle encryption error
        return crypto.encryptContent(key, content);
    };
    Page.prototype.applyMetaUpdate = function (data) {
        var _this = this;
        data.special_page_type = this.updatePageType(data.special_page_type);
        // Remove content fields (should not be here and in the future will not be here)
        delete data.content;
        delete data.encrypted_content;
        // Process ranges
        // Create Ranges object
        if (!this.Ranges) {
            this.Ranges = new Range_2.Ranges(data.domain_id, data.id);
            this.Ranges.parse(data.range_access || '[]');
            this.Ranges.on(this.Ranges.EVENT_UPDATED, function () {
                console.log('this.Ranges.EVENT_UPDATED');
                _this.emit(_this.EVENT_RANGES_UPDATED);
            });
            // this.setFreezeRange();
        }
        if (!this.Actions) {
            this.Actions = new Actions_1.Actions(api, this.Content, this.Functions, this._folderId, this._pageId);
            this.Actions.parse(data.action_definitions);
            this.Actions.on(this.Actions.EVENT_UPDATED, function () {
                console.log('this.Actions.EVENT_UPDATED');
                _this.emit(_this.EVENT_RANGES_UPDATED);
            });
        }
        var rangesUpdates = false;
        if (data.range_access &&
            this._data &&
            this._data.range_access !== data.range_access) {
            this.Ranges.parse(data.range_access || '[]');
            data.access_rights = JSON.stringify(this.Ranges.ranges);
            rangesUpdates = true;
        }
        if (data.action_definitions &&
            this._data &&
            this._data.action_definitions !== data.action_definitions) {
            this.Actions.parse(data.action_definitions);
            rangesUpdates = true;
        }
        // has meta data changed
        if (!this._metaLoaded) {
            this._data = data;
            this._metaLoaded = true;
        }
        else {
            var metaChanged_1 = false;
            var columnDefsChanged_1 = false;
            Object.keys(data).forEach(function (key) {
                if (['range_access', 'action_definitions'].indexOf(key) > -1)
                    return;
                var changed = false;
                if (_this._data[key] === undefined) {
                    metaChanged_1 = true;
                    return;
                }
                if (data[key] !== null && typeof data[key] === 'object') {
                    if (JSON.stringify(data[key]) !== JSON.stringify(_this._data[key])) {
                        metaChanged_1 = true;
                        changed = true;
                    }
                }
                else if (data[key] !== _this._data[key]) {
                    metaChanged_1 = true;
                }
                if (key === 'page_schema' && changed) {
                    columnDefsChanged_1 = true;
                }
            });
            this._data = __assign({}, this._data, data);
            this.checkReady();
            if (!this.ready)
                return;
            if (metaChanged_1)
                this.emit(this.EVENT_NEW_META, data);
            if (rangesUpdates) {
                this.emit(this.EVENT_RANGES_UPDATED);
            }
            if (columnDefsChanged_1) {
                this.emit(this.EVENT_COLUMN_DEFS_UPDATED);
            }
        }
        // Check if this page should be handled by websockets and switch
        // if (data.ws_enabled && !(this._provider instanceof ProviderSocket)) {
        //   if (this._supportsWS && !this._wsDisabled) {
        //     this._provider.destroy();
        //     this._provider = new ProviderSocket(this._pageId, this._folderId);
        //     this.registerListeners();
        //   }
        // }
    };
    var Page_1;
    Page = Page_1 = __decorate([
        staticImplements()
    ], Page);
    return Page;
}(Emitter_1.default));
exports.Page = Page;
//# sourceMappingURL=Page.js.map