"use strict";
/* TODO auto-generated eslint ignore, please fix! */
/* eslint no-var: "off" */
/**
 * Token state management
 *
 *  Copyright (C) 2010-2019 R-T Specialty, LLC.
 *
 *  This file is part of the Liza Data Collection Framework.
 *
 *  liza is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU Affero General Public License as
 *  published by the Free Software Foundation, either version 3 of the
 *  License, or (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU Affero General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
Object.defineProperty(exports, "__esModule", { value: true });
exports.MongoTokenDao = void 0;
var UnknownTokenError_1 = require("./UnknownTokenError");
var ContextError_1 = require("../../error/ContextError");
/**
 * Manages token updates
 *
 * This uses MongoDB as the underlying database.
 */
var MongoTokenDao = /** @class */ (function () {
    /**
     * Initialize connection
     *
     * @param _collection Mongo collection
     * @param _root_field topmost field in mongo document
     * @param _date_ctor  Date constructor
     */
    function MongoTokenDao(_collection, _root_field, _getTimestamp) {
        this._collection = _collection;
        this._root_field = _root_field;
        this._getTimestamp = _getTimestamp;
    }
    /**
     * Create or update a token record
     *
     * The token entry is entered in the token log, and then the current
     * entry is updated to reflect the changes.  The operation is atomic.
     *
     * @param doc_id   unique document identifier
     * @param ns       token namespace
     * @param token    token value
     * @param data     token data, if any
     * @param status   arbitrary token type
     *
     * @return token data
     */
    MongoTokenDao.prototype.updateToken = function (doc_id, ns, token_id, type, data) {
        var _a, _b;
        var _this = this;
        var root = this._genRoot(ns) + '.';
        var token_entry = {
            type: type,
            timestamp: this._getTimestamp(),
            data: data,
        };
        var token_data = (_a = {},
            _a[root + 'last'] = token_id,
            _a[root + 'lastState.' + type] = token_id,
            _a[root + 'lastStatus'] = token_entry,
            _a[root + token_id + '.status'] = token_entry,
            _a);
        var token_log = (_b = {},
            _b[root + token_id + '.statusLog'] = token_entry,
            _b);
        return new Promise(function (resolve, reject) {
            var _a;
            _this._collection.findAndModify({ id: +doc_id }, [], {
                $set: token_data,
                $push: token_log,
            }, {
                upsert: true,
                new: false,
                fields: (_a = {},
                    _a[root + 'last'] = 1,
                    _a[root + 'lastState'] = 1,
                    _a[root + 'lastStatus'] = 1,
                    _a[root + token_id + '.status'] = 1,
                    _a),
            }, function (err, prev_data) {
                if (err) {
                    reject(err);
                    return;
                }
                var prev_result = prev_data[_this._root_field] || {};
                var prev_ns = prev_result[ns];
                resolve({
                    id: token_id,
                    status: token_entry,
                    prev_status: _this._getPrevStatus(prev_ns, token_id),
                    prev_last: _this._getPrevLast(prev_ns),
                    prev_state: _this._getPrevState(prev_ns),
                });
            });
        });
    };
    /**
     * Determine previous token status, or produce `null`
     *
     * @param prev_ns  previous namespace data
     * @param token_id token identifier
     *
     * @return previous token status
     */
    MongoTokenDao.prototype._getPrevStatus = function (prev_ns, token_id) {
        if (prev_ns === undefined) {
            return null;
        }
        var entry = prev_ns[token_id];
        return entry === undefined ? null : entry.status;
    };
    /**
     * Determine previous last updated token for namespace, otherwise `null`
     *
     * @param prev_ns previous namespace data
     *
     * @return previous last token data
     */
    MongoTokenDao.prototype._getPrevLast = function (prev_ns) {
        if (prev_ns === undefined || (prev_ns || {}).last === undefined) {
            return null;
        }
        return {
            id: prev_ns.last,
            status: prev_ns.lastStatus,
            prev_status: null,
            prev_last: null,
            prev_state: {},
        };
    };
    /**
     * Retrieve previous token states
     *
     * If token state information is missing, an empty object will be
     * returned.
     *
     * @param prev_ns previous namespace data
     *
     * @return previous token states
     */
    MongoTokenDao.prototype._getPrevState = function (prev_ns) {
        return !prev_ns || prev_ns.lastState === undefined ? {} : prev_ns.lastState;
    };
    /**
     * Retrieve existing token under the namespace NS, if any, for the doc
     * identified by DOC_ID
     *
     * If a TOKEN_ID is provided, only that token will be queried; otherwise,
     * the most recently created token will be the subject of the query.
     *
     * @param doc_id   document identifier
     * @param ns       token namespace
     * @param token_id token identifier (unique to NS)
     *
     * @return token data
     */
    MongoTokenDao.prototype.getToken = function (doc_id, ns, token_id) {
        var _this = this;
        var root = this._genRoot(ns) + '.';
        var fields = {};
        fields[root + 'last'] = 1;
        fields[root + 'lastState'] = 1;
        fields[root + 'lastStatus'] = 1;
        if (token_id) {
            // XXX: injectable
            fields[root + token_id] = 1;
        }
        return new Promise(function (resolve, reject) {
            _this._collection.findOne({ id: +doc_id }, { fields: fields }, function (err, data) {
                if (err || !data) {
                    reject(err);
                    return;
                }
                var field = data[_this._root_field] || {};
                var ns_data = field[ns];
                if (!ns_data) {
                    reject(ContextError_1.context(new UnknownTokenError_1.UnknownTokenError("Unknown token namespace '" + ns + "' for document '" + doc_id), {
                        doc_id: doc_id,
                        quote_id: doc_id,
                        ns: ns,
                    }));
                    return;
                }
                resolve(token_id
                    ? _this._getRequestedToken(doc_id, ns, token_id, ns_data)
                    : _this._getLatestToken(doc_id, ns, ns_data));
            });
        });
    };
    /**
     * Retrieve latest token data
     *
     * @param doc_id  document id
     * @param ns      token namespace
     * @param ns_data namespace data
     *
     * @return data of latest token in namespace
     *
     * @throws UnknownTokenError if last token data is missing
     */
    MongoTokenDao.prototype._getLatestToken = function (doc_id, ns, ns_data) {
        var last = ns_data.last;
        if (!last) {
            throw ContextError_1.context(new UnknownTokenError_1.UnknownTokenError("Failed to locate last token for namespace '" + ns + "'" +
                ("on document '" + doc_id + "'")), {
                doc_id: doc_id,
                quote_id: doc_id,
                ns: ns,
            });
        }
        return {
            id: last,
            status: ns_data.lastStatus,
            prev_status: ns_data.lastStatus,
            prev_last: this._getPrevLast(ns_data),
            prev_state: this._getPrevState(ns_data),
        };
    };
    /**
     * Retrieve latest token data
     *
     * @param doc_id   document id
     * @param ns       token namespace
     * @param token_id token identifier for namespace associated with NS_DATA
     * @param ns_data  namespace data
     *
     * @return data of requested token
     *
     * @throws UnknownTokenError if token data is missing
     */
    MongoTokenDao.prototype._getRequestedToken = function (doc_id, ns, token_id, ns_data) {
        var reqtok = ns_data[token_id];
        if (!reqtok) {
            throw ContextError_1.context(new UnknownTokenError_1.UnknownTokenError("Missing data for requested token '" + ns + "." + token_id + "'" +
                ("for document '" + doc_id + "'")), {
                doc_id: doc_id,
                quote_id: doc_id,
                ns: ns,
                token_id: token_id,
            });
        }
        return {
            id: token_id,
            status: reqtok.status,
            prev_status: reqtok.status,
            prev_last: this._getPrevLast(ns_data),
            prev_state: this._getPrevState(ns_data),
        };
    };
    /**
     * Determine token root for the given namespace
     *
     * @param ns token namespace
     *
     * @return token root for namespace NS
     */
    MongoTokenDao.prototype._genRoot = function (ns) {
        // XXX: injectable
        return this._root_field + '.' + ns;
    };
    return MongoTokenDao;
}());
exports.MongoTokenDao = MongoTokenDao;
//# sourceMappingURL=data:application/json;base64, 