"use strict";
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 };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.Storage = exports.StorageService = void 0;
const events_1 = require("events");
const mongodb_1 = require("mongodb");
const mongodb_2 = require("mongodb");
const Loggify_1 = require("../decorators/Loggify");
const logger_1 = __importDefault(require("../logger"));
require("../models");
const utils_1 = require("../utils");
const config_1 = require("./config");
let StorageService = class StorageService {
    constructor({ configService = config_1.Config } = {}) {
        this.connected = false;
        this.connection = new events_1.EventEmitter();
        this.modelsConnected = new Array();
        this.configService = configService;
        this.connection.setMaxListeners(30);
    }
    start(args = {}) {
        return new Promise((resolve, reject) => {
            const options = Object.assign({}, this.configService.get(), args);
            const { dbUrl, dbName, dbHost, dbPort, dbUser, dbPass, dbReadPreference } = options;
            const auth = dbUser !== '' && dbPass !== '' ? `${dbUser}:${dbPass}@` : '';
            const connectUrl = dbUrl
                ? dbUrl
                : `mongodb://${auth}${dbHost}:${dbPort}/${dbName}?socketTimeoutMS=3600000&noDelay=true${dbReadPreference ? `?readPreference=${dbReadPreference}` : ''}`;
            const attemptConnect = async () => {
                return mongodb_2.MongoClient.connect(connectUrl, {
                    keepAlive: true,
                    poolSize: options.maxPoolSize,
                    useNewUrlParser: true
                });
            };
            let attempted = 0;
            const attemptConnectId = setInterval(() => {
                (async () => {
                    try {
                        this.client = await attemptConnect();
                        this.db = this.client.db(dbName);
                        this.connected = true;
                        clearInterval(attemptConnectId);
                        this.connection.emit('CONNECTED');
                        resolve(this.client);
                    }
                    catch (err) {
                        logger_1.default.error('%o', err);
                        attempted++;
                        if (attempted > 5) {
                            clearInterval(attemptConnectId);
                            reject(new Error('Failed to connect to database'));
                        }
                    }
                })();
            }, 5000);
        });
    }
    async stop() {
        if (this.client) {
            logger_1.default.info('Stopping Storage Service');
            await (0, utils_1.wait)(5000);
            this.connected = false;
            await Promise.all(this.modelsConnected);
            await this.client.close();
            this.connection.emit('DISCONNECTED');
        }
    }
    validPagingProperty(model, property) {
        const defaultCase = property === '_id';
        return defaultCase || model.allowedPaging.some(prop => prop.key === property);
    }
    /**
     * castForDb
     *
     * For a given model, return the typecasted value based on a key and the type associated with that key
     */
    typecastForDb(model, modelKey, modelValue) {
        let typecastedValue = modelValue;
        if (modelKey) {
            const oldValue = modelValue;
            const optionsType = model.allowedPaging.find(prop => prop.key === modelKey);
            if (optionsType) {
                switch (optionsType.type) {
                    case 'number':
                        typecastedValue = Number(oldValue);
                        break;
                    case 'string':
                        typecastedValue = (oldValue || '').toString();
                        break;
                    case 'date':
                        typecastedValue = new Date(oldValue);
                        break;
                }
            }
            else if (modelKey == '_id') {
                typecastedValue = new mongodb_1.ObjectID(oldValue);
            }
        }
        return typecastedValue;
    }
    stream(input, req, res) {
        let closed = false;
        req.on('close', function () {
            closed = true;
        });
        res.on('close', function () {
            closed = true;
        });
        input.on('error', function (err) {
            if (!closed) {
                closed = true;
                return res.status(500).end(err.message);
            }
            return;
        });
        let isFirst = true;
        res.type('json');
        input.on('data', function (data) {
            if (!closed) {
                if (isFirst) {
                    res.write('[\n');
                    isFirst = false;
                }
                else {
                    res.write(',\n');
                }
                res.write(JSON.stringify(data));
            }
        });
        input.on('end', function () {
            if (!closed) {
                if (isFirst) {
                    // there was no data
                    res.write('[]');
                }
                else {
                    res.write('\n]');
                }
                res.end();
            }
        });
    }
    apiStream(cursor, req, res) {
        let closed = false;
        req.on('close', function () {
            closed = true;
            cursor.close();
        });
        res.on('close', function () {
            closed = true;
            cursor.close();
        });
        cursor.on('error', function (err) {
            if (!closed) {
                closed = true;
                return res.status(500).end(err.message);
            }
            return;
        });
        let isFirst = true;
        res.type('json');
        cursor.on('data', function (data) {
            if (!closed) {
                if (isFirst) {
                    res.write('[\n');
                    isFirst = false;
                }
                else {
                    res.write(',\n');
                }
                res.write(data);
            }
            else {
                cursor.close();
            }
        });
        cursor.on('end', function () {
            if (!closed) {
                if (isFirst) {
                    // there was no data
                    res.write('[]');
                }
                else {
                    res.write('\n]');
                }
                res.end();
            }
        });
    }
    getFindOptions(model, originalOptions) {
        const query = {};
        let since = null;
        const options = {};
        if (originalOptions.sort) {
            options.sort = originalOptions.sort;
        }
        if (originalOptions.paging && this.validPagingProperty(model, originalOptions.paging)) {
            if (originalOptions.since !== undefined) {
                since = this.typecastForDb(model, originalOptions.paging, originalOptions.since);
            }
            if (originalOptions.direction && Number(originalOptions.direction) === 1) {
                if (since) {
                    query[originalOptions.paging] = { $gt: since };
                }
                options.sort = Object.assign({}, originalOptions.sort, { [originalOptions.paging]: 1 });
            }
            else {
                if (since) {
                    query[originalOptions.paging] = { $lt: since };
                }
                options.sort = Object.assign({}, originalOptions.sort, { [originalOptions.paging]: -1 });
            }
        }
        if (originalOptions.limit) {
            options.limit = Number(originalOptions.limit);
        }
        return { query, options };
    }
    apiStreamingFind(model, originalQuery, originalOptions, req, res, transform) {
        const { query, options } = this.getFindOptions(model, originalOptions);
        const finalQuery = Object.assign({}, originalQuery, query);
        let cursor = model.collection
            .find(finalQuery, options)
            .addCursorFlag('noCursorTimeout', true)
            .stream({
            transform: transform || model._apiTransform.bind(model)
        });
        if (options.sort) {
            cursor = cursor.sort(options.sort);
        }
        return this.apiStream(cursor, req, res);
    }
};
exports.StorageService = StorageService;
exports.StorageService = StorageService = __decorate([
    Loggify_1.LoggifyClass
], StorageService);
exports.Storage = new StorageService();
//# sourceMappingURL=storage.js.map