"use strict";
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 = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype);
    return g.next = verb(0), g["throw"] = verb(1), g["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 (g && (g = 0, op[0] && (_ = 0)), _) 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 };
    }
};
var __values = (this && this.__values) || function(o) {
    var s = typeof Symbol === "function" && Symbol.iterator, m = s && o[s], i = 0;
    if (m) return m.call(o);
    if (o && typeof o.length === "number") return {
        next: function () {
            if (o && i >= o.length) o = void 0;
            return { value: o && o[i++], done: !o };
        }
    };
    throw new TypeError(s ? "Object is not iterable." : "Symbol.iterator is not defined.");
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.CronManager = void 0;
var cron_job_history_collection_1 = require("../collections/cron-job-history.collection");
var cron_job_collection_1 = require("../collections/cron-job.collection");
var cron_1 = require("../cron/cron");
var resolveio_server_app_1 = require("../resolveio-server-app");
var common_1 = require("../util/common");
var CronManager = /** @class */ (function () {
    function CronManager() {
        this._jobs = [];
    }
    CronManager.create = function () {
        var _this = this;
        var cronManager = new CronManager();
        setImmediate(function () { return __awaiter(_this, void 0, void 0, function () { return __generator(this, function (_a) {
            switch (_a.label) {
                case 0: return [4 /*yield*/, cronManager.initialize()];
                case 1: return [2 /*return*/, _a.sent()];
            }
        }); }); });
        return cronManager;
    };
    CronManager.prototype.initialize = function () {
        return __awaiter(this, void 0, void 0, function () {
            return __generator(this, function (_a) {
                switch (_a.label) {
                    case 0:
                        this._cronManager = new cron_1.CronJobManager();
                        return [4 /*yield*/, this.watchCrons()];
                    case 1:
                        _a.sent();
                        return [2 /*return*/];
                }
            });
        });
    };
    CronManager.prototype.watchCrons = function () {
        return __awaiter(this, void 0, void 0, function () {
            var res, _a, _b, job, e_1_1;
            var e_1, _c;
            var _this = this;
            return __generator(this, function (_d) {
                switch (_d.label) {
                    case 0:
                        if (!(this._watchCrons$ && !this._watchCrons$.closed)) return [3 /*break*/, 2];
                        this._watchCrons$.removeAllListeners();
                        return [4 /*yield*/, this._watchCrons$.close()];
                    case 1:
                        _d.sent();
                        this._watchCrons$ = null;
                        _d.label = 2;
                    case 2: 
                    // eslint-disable-next-line no-restricted-syntax
                    return [4 /*yield*/, new Promise(function (resolve) { return setTimeout(resolve, 1000); })];
                    case 3:
                        // eslint-disable-next-line no-restricted-syntax
                        _d.sent();
                        if (!(!this._watchCrons$ || this._watchCrons$.closed)) return [3 /*break*/, 14];
                        return [4 /*yield*/, cron_job_collection_1.CronJobs.find()];
                    case 4:
                        res = _d.sent();
                        this._jobs = res;
                        _d.label = 5;
                    case 5:
                        _d.trys.push([5, 11, 12, 13]);
                        _a = __values(this._jobs), _b = _a.next();
                        _d.label = 6;
                    case 6:
                        if (!!_b.done) return [3 /*break*/, 10];
                        job = _b.value;
                        return [4 /*yield*/, this.addCronJob(job)];
                    case 7:
                        _d.sent();
                        if (!job.running) return [3 /*break*/, 9];
                        return [4 /*yield*/, cron_job_collection_1.CronJobs.updateOne({ _id: job._id }, { $set: { running: false } })];
                    case 8:
                        _d.sent();
                        _d.label = 9;
                    case 9:
                        _b = _a.next();
                        return [3 /*break*/, 6];
                    case 10: return [3 /*break*/, 13];
                    case 11:
                        e_1_1 = _d.sent();
                        e_1 = { error: e_1_1 };
                        return [3 /*break*/, 13];
                    case 12:
                        try {
                            if (_b && !_b.done && (_c = _a.return)) _c.call(_a);
                        }
                        finally { if (e_1) throw e_1.error; }
                        return [7 /*endfinally*/];
                    case 13:
                        this._watchCrons$ = cron_job_collection_1.CronJobs.watchCollection([], { fullDocument: 'updateLookup' });
                        this._watchCrons$.on('change', function (doc) { return __awaiter(_this, void 0, void 0, function () {
                            var oldDoc, job, job;
                            return __generator(this, function (_a) {
                                switch (_a.label) {
                                    case 0:
                                        if (!(doc.operationType === 'insert')) return [3 /*break*/, 3];
                                        if (!doc.fullDocument) return [3 /*break*/, 2];
                                        if (!this._jobs.some(function (a) { return a._id === doc.documentKey['_id']; })) {
                                            this._jobs.push(doc.fullDocument);
                                        }
                                        return [4 /*yield*/, this.addCronJob(doc.fullDocument)];
                                    case 1:
                                        _a.sent();
                                        _a.label = 2;
                                    case 2: return [3 /*break*/, 17];
                                    case 3:
                                        if (!(doc.operationType === 'replace' || doc.operationType === 'update')) return [3 /*break*/, 15];
                                        if (!doc.fullDocument) return [3 /*break*/, 12];
                                        oldDoc = this._jobs.find(function (a) { return a._id === doc.documentKey['_id']; });
                                        if (!oldDoc) return [3 /*break*/, 9];
                                        this._jobs.splice(this._jobs.map(function (a) { return a._id; }).indexOf(doc.documentKey['_id']), 1, doc.fullDocument);
                                        if (!(oldDoc.name !== doc.fullDocument.name)) return [3 /*break*/, 6];
                                        return [4 /*yield*/, this.removeCronJob(oldDoc.name)];
                                    case 4:
                                        _a.sent();
                                        return [4 /*yield*/, this.addCronJob(doc.fullDocument)];
                                    case 5:
                                        _a.sent();
                                        return [3 /*break*/, 8];
                                    case 6:
                                        if (!(typeof oldDoc.time_to_run !== typeof doc.fullDocument.time_to_run || (typeof oldDoc.time_to_run === 'string' && oldDoc.time_to_run !== doc.fullDocument.time_to_run) ||
                                            (oldDoc.time_to_run instanceof Date && oldDoc.time_to_run.getTime() !== doc.fullDocument.time_to_run.getTime()) ||
                                            oldDoc.timezone !== doc.fullDocument.timezone)) return [3 /*break*/, 8];
                                        return [4 /*yield*/, this.updateCronJob(doc.fullDocument)];
                                    case 7:
                                        _a.sent();
                                        _a.label = 8;
                                    case 8: return [3 /*break*/, 11];
                                    case 9:
                                        this._jobs.push(doc.fullDocument);
                                        return [4 /*yield*/, this.addCronJob(doc.fullDocument)];
                                    case 10:
                                        _a.sent();
                                        _a.label = 11;
                                    case 11: return [3 /*break*/, 14];
                                    case 12:
                                        if (!this._jobs.some(function (a) { return a._id === doc.documentKey['_id']; })) return [3 /*break*/, 14];
                                        job = this._jobs.find(function (a) { return a._id === doc.documentKey['_id']; });
                                        return [4 /*yield*/, this.removeCronJob(job.name)];
                                    case 13:
                                        _a.sent();
                                        this._jobs.splice(this._jobs.map(function (a) { return a._id; }).indexOf(doc.documentKey['_id']), 1);
                                        _a.label = 14;
                                    case 14: return [3 /*break*/, 17];
                                    case 15:
                                        if (!(doc.operationType === 'delete')) return [3 /*break*/, 17];
                                        if (!this._jobs.some(function (a) { return a._id === doc.documentKey['_id']; })) return [3 /*break*/, 17];
                                        job = this._jobs.find(function (a) { return a._id === doc.documentKey['_id']; });
                                        return [4 /*yield*/, this.removeCronJob(job.name)];
                                    case 16:
                                        _a.sent();
                                        this._jobs.splice(this._jobs.map(function (a) { return a._id; }).indexOf(doc.documentKey['_id']), 1);
                                        _a.label = 17;
                                    case 17: return [2 /*return*/];
                                }
                            });
                        }); })
                            .on('error', function () { return __awaiter(_this, void 0, void 0, function () {
                            return __generator(this, function (_a) {
                                switch (_a.label) {
                                    case 0: return [4 /*yield*/, this._watchCrons$.close()];
                                    case 1:
                                        _a.sent();
                                        return [2 /*return*/];
                                }
                            });
                        }); })
                            .on('end', function () { return __awaiter(_this, void 0, void 0, function () {
                            return __generator(this, function (_a) {
                                switch (_a.label) {
                                    case 0: return [4 /*yield*/, this._watchCrons$.close()];
                                    case 1:
                                        _a.sent();
                                        return [2 /*return*/];
                                }
                            });
                        }); })
                            .on('close', function () { return __awaiter(_this, void 0, void 0, function () {
                            return __generator(this, function (_a) {
                                switch (_a.label) {
                                    case 0:
                                        this._watchCrons$.removeAllListeners();
                                        this._watchCrons$ = null;
                                        return [4 /*yield*/, this.watchCrons()];
                                    case 1:
                                        _a.sent();
                                        return [2 /*return*/];
                                }
                            });
                        }); });
                        _d.label = 14;
                    case 14: return [2 /*return*/];
                }
            });
        });
    };
    CronManager.prototype.doesCronJobExist = function (cron) {
        return this._cronManager.exists(cron.name);
    };
    CronManager.prototype.doesCronJobNameExist = function (cron_name) {
        return this._cronManager.exists(cron_name);
    };
    CronManager.prototype.addCronJob = function (cron) {
        return __awaiter(this, void 0, void 0, function () {
            var nextDate, e_2;
            var _this = this;
            return __generator(this, function (_a) {
                switch (_a.label) {
                    case 0:
                        if (!!this.doesCronJobExist(cron)) return [3 /*break*/, 8];
                        _a.label = 1;
                    case 1:
                        _a.trys.push([1, 5, , 8]);
                        return [4 /*yield*/, this._cronManager.add(cron.name, cron.time_to_run, function () { return _this.runCronJob(cron); }, null, true, cron.timezone, null, false)];
                    case 2:
                        _a.sent();
                        nextDate = new Date(this._cronManager.getJob(cron.name).nextDate()['ts']);
                        if (!(cron.running || (nextDate && !cron.next_run || nextDate.getTime() !== cron.next_run.getTime()))) return [3 /*break*/, 4];
                        return [4 /*yield*/, cron_job_collection_1.CronJobs.updateOne({ _id: cron._id }, { $set: { running: false, next_run: nextDate || null } })];
                    case 3:
                        _a.sent();
                        _a.label = 4;
                    case 4: return [3 /*break*/, 8];
                    case 5:
                        e_2 = _a.sent();
                        console.log('Cron error', e_2);
                        if (!(cron.time_to_run instanceof Date)) return [3 /*break*/, 7];
                        return [4 /*yield*/, this.runCronJob(cron)];
                    case 6:
                        _a.sent();
                        _a.label = 7;
                    case 7: return [3 /*break*/, 8];
                    case 8: return [2 /*return*/];
                }
            });
        });
    };
    CronManager.prototype.updateCronJob = function (cron) {
        return __awaiter(this, void 0, void 0, function () {
            var nextDate;
            var _this = this;
            return __generator(this, function (_a) {
                switch (_a.label) {
                    case 0:
                        if (!this.doesCronJobExist(cron)) return [3 /*break*/, 3];
                        return [4 /*yield*/, this._cronManager.update(cron.name, cron.time_to_run, function () { return _this.runCronJob(cron); }, null, true, cron.timezone)];
                    case 1:
                        _a.sent();
                        nextDate = new Date(this._cronManager.getJob(cron.name).nextDate()['ts']);
                        if (!(nextDate && (!cron.next_run || nextDate.getTime() !== cron.next_run.getTime()))) return [3 /*break*/, 3];
                        return [4 /*yield*/, cron_job_collection_1.CronJobs.updateOne({ _id: cron._id }, { $set: { next_run: nextDate } })];
                    case 2:
                        _a.sent();
                        _a.label = 3;
                    case 3: return [2 /*return*/];
                }
            });
        });
    };
    CronManager.prototype.removeCronJob = function (cron_name) {
        return __awaiter(this, void 0, void 0, function () {
            return __generator(this, function (_a) {
                switch (_a.label) {
                    case 0:
                        if (!this.doesCronJobNameExist(cron_name)) return [3 /*break*/, 2];
                        return [4 /*yield*/, this._cronManager.delete(cron_name)];
                    case 1:
                        _a.sent();
                        _a.label = 2;
                    case 2: return [2 /*return*/];
                }
            });
        });
    };
    CronManager.prototype.startCronJob = function (cron) {
        if (this.doesCronJobExist(cron)) {
            this._cronManager.start(cron.name);
        }
    };
    CronManager.prototype.stopCronJob = function (cron) {
        return __awaiter(this, void 0, void 0, function () {
            return __generator(this, function (_a) {
                switch (_a.label) {
                    case 0:
                        if (!this.doesCronJobExist(cron)) return [3 /*break*/, 2];
                        return [4 /*yield*/, this._cronManager.stop(cron.name)];
                    case 1:
                        _a.sent();
                        _a.label = 2;
                    case 2: return [2 /*return*/];
                }
            });
        });
    };
    CronManager.prototype.stopAllCronJobs = function () {
        return __awaiter(this, void 0, void 0, function () {
            return __generator(this, function (_a) {
                switch (_a.label) {
                    case 0: return [4 /*yield*/, this._cronManager.stopAll()];
                    case 1:
                        _a.sent();
                        return [2 /*return*/];
                }
            });
        });
    };
    CronManager.prototype.runCronJob = function (cron) {
        return __awaiter(this, void 0, void 0, function () {
            var now, res, saveHistoryOverride, saveHistory, cronHistory_1, timeoutHandle, err_1, nextDate, nextDate;
            var _this = this;
            return __generator(this, function (_a) {
                switch (_a.label) {
                    case 0:
                        now = new Date();
                        return [4 /*yield*/, cron_job_collection_1.CronJobs.findOneAndUpdate({
                                $and: [
                                    { _id: cron._id },
                                    { running: false },
                                    {
                                        $or: [
                                            { next_run: { $exists: false } },
                                            { next_run: null },
                                            { next_run: { $lte: now } }
                                        ]
                                    }
                                ]
                            }, { $set: { running: true } })];
                    case 1:
                        res = _a.sent();
                        if (!res) return [3 /*break*/, 25];
                        saveHistoryOverride = !!(res.method_run_data && res.method_run_data._cron_save_history === false);
                        saveHistory = !saveHistoryOverride && res.save_history !== false;
                        cronHistory_1 = null;
                        if (!saveHistory) return [3 /*break*/, 3];
                        cronHistory_1 = {
                            _id: (0, common_1.objectIdHexString)(),
                            name: res.name,
                            reoccuring: res.repeat,
                            time_to_run: res.time_to_run,
                            method_name: res.method_run,
                            id_cronjob: res._id,
                            server_restart: true,
                            passed: true,
                            error: '',
                            date_scheduled: res.next_run,
                            date_start: new Date(),
                            date_end: null,
                            date_next: null
                        };
                        return [4 /*yield*/, cron_job_history_collection_1.CronJobHistories.create(cronHistory_1)];
                    case 2:
                        _a.sent();
                        _a.label = 3;
                    case 3:
                        timeoutHandle = setTimeout(function () { return __awaiter(_this, void 0, void 0, function () {
                            var nextDate;
                            return __generator(this, function (_a) {
                                switch (_a.label) {
                                    case 0:
                                        console.log(new Date(), 'Cron Timeout', this.safeStringify(cron));
                                        if (!res.repeat) return [3 /*break*/, 2];
                                        nextDate = new Date(this._cronManager.getJob(res.name).nextDate()['ts']);
                                        if (cronHistory_1) {
                                            cronHistory_1.date_next = nextDate;
                                        }
                                        return [4 /*yield*/, cron_job_collection_1.CronJobs.updateOne({ _id: res._id }, { $set: { running: false, next_run: nextDate } })];
                                    case 1:
                                        _a.sent();
                                        return [3 /*break*/, 4];
                                    case 2: return [4 /*yield*/, cron_job_collection_1.CronJobs.deleteOne({ _id: res._id })];
                                    case 3:
                                        _a.sent();
                                        _a.label = 4;
                                    case 4: return [2 /*return*/];
                                }
                            });
                        }); }, 5 * 60 * 1000);
                        _a.label = 4;
                    case 4:
                        _a.trys.push([4, 13, 18, 19]);
                        if (!res.method_run_data) return [3 /*break*/, 6];
                        return [4 /*yield*/, resolveio_server_app_1.ResolveIOServer.getMainServer().getMethodManager().callMethodCron(res.method_run, res.method_run_data)];
                    case 5:
                        _a.sent();
                        return [3 /*break*/, 8];
                    case 6: return [4 /*yield*/, resolveio_server_app_1.ResolveIOServer.getMainServer().getMethodManager().callMethodCron(res.method_run)];
                    case 7:
                        _a.sent();
                        _a.label = 8;
                    case 8:
                        if (!res.method_complete) return [3 /*break*/, 12];
                        if (!res.method_complete_data) return [3 /*break*/, 10];
                        return [4 /*yield*/, resolveio_server_app_1.ResolveIOServer.getMainServer().getMethodManager().callMethodCron(res.method_complete, res.method_complete_data)];
                    case 9:
                        _a.sent();
                        return [3 /*break*/, 12];
                    case 10: return [4 /*yield*/, resolveio_server_app_1.ResolveIOServer.getMainServer().getMethodManager().callMethodCron(res.method_complete)];
                    case 11:
                        _a.sent();
                        _a.label = 12;
                    case 12: return [3 /*break*/, 19];
                    case 13:
                        err_1 = _a.sent();
                        console.log(new Date(), 'Cron Error', this.safeStringify(err_1));
                        if (cronHistory_1) {
                            cronHistory_1.error = this.safeStringify(err_1);
                            cronHistory_1.passed = false;
                        }
                        if (!res.repeat) return [3 /*break*/, 15];
                        nextDate = new Date(this._cronManager.getJob(res.name).nextDate()['ts']);
                        if (cronHistory_1) {
                            cronHistory_1.date_next = nextDate;
                        }
                        return [4 /*yield*/, cron_job_collection_1.CronJobs.updateOne({ _id: res._id }, { $set: { running: false, next_run: nextDate } })];
                    case 14:
                        _a.sent();
                        return [3 /*break*/, 17];
                    case 15: return [4 /*yield*/, cron_job_collection_1.CronJobs.deleteOne({ _id: res._id })];
                    case 16:
                        _a.sent();
                        _a.label = 17;
                    case 17: return [3 /*break*/, 19];
                    case 18:
                        // Clear the timeout if the job completes or fails before the timeout
                        clearTimeout(timeoutHandle);
                        return [7 /*endfinally*/];
                    case 19:
                        if (!res.repeat) return [3 /*break*/, 21];
                        nextDate = new Date(this._cronManager.getJob(res.name).nextDate()['ts']);
                        if (cronHistory_1) {
                            cronHistory_1.date_next = nextDate;
                        }
                        return [4 /*yield*/, cron_job_collection_1.CronJobs.updateOne({ _id: res._id }, { $set: { running: false, next_run: nextDate } })];
                    case 20:
                        _a.sent();
                        return [3 /*break*/, 23];
                    case 21: return [4 /*yield*/, cron_job_collection_1.CronJobs.deleteOne({ _id: res._id })];
                    case 22:
                        _a.sent();
                        _a.label = 23;
                    case 23:
                        if (!cronHistory_1) return [3 /*break*/, 25];
                        return [4 /*yield*/, cron_job_history_collection_1.CronJobHistories.updateOne({ _id: cronHistory_1._id }, { $set: {
                                    server_restart: false,
                                    passed: cronHistory_1.passed,
                                    error: cronHistory_1.error,
                                    date_end: new Date(),
                                    date_next: cronHistory_1.date_next
                                } })];
                    case 24:
                        _a.sent();
                        _a.label = 25;
                    case 25: return [2 /*return*/];
                }
            });
        });
    };
    CronManager.prototype.safeStringify = function (obj) {
        try {
            return JSON.stringify(obj, this.getCircularReplacer(), 2);
        }
        catch (e) {
            return "Error stringifying object: ".concat(e.message);
        }
    };
    CronManager.prototype.getCircularReplacer = function () {
        var seen = new WeakSet();
        return function (key, value) {
            if (typeof value === 'object' && value !== null) {
                if (seen.has(value)) {
                    return '[Circular]';
                }
                seen.add(value);
            }
            return value;
        };
    };
    return CronManager;
}());
exports.CronManager = CronManager;

//# sourceMappingURL=cron.manager.js.map
