"use strict";
/*
* cluster web server that INGESTS new SUBMISSIONS into the database
* - inserts trials
* - inserts sources
* - inserts modlists
* - etc etc etc. Basically, the web server that takes in new data from the user and puts it into the database
*
* starting the injest server should also start database connections and ensure objects area valid
*
* */
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
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 __importStar = (this && this.__importStar) || function (mod) {
    if (mod && mod.__esModule) return mod;
    var result = {};
    if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
    __setModuleDefault(result, mod);
    return result;
};
var __metadata = (this && this.__metadata) || function (k, v) {
    if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
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 __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.FactoryApiIngestServer = exports.FactoryApiIngestQuick = exports.FactoryApiExecutionStatus = exports.FactoryApiExecutionRequest = exports.FactoryApiIngest = void 0;
const typeorm_1 = require("typeorm");
const express_1 = __importStar(require("express"));
const cluster_1 = __importDefault(require("cluster"));
const Source_1 = require("../Source");
const FactoryDatabase_1 = require("../FactoryDatabase");
const Trial_1 = require("../Trial");
const ModList_1 = require("../ModList");
const Dataset_1 = require("../Dataset");
// Template of what every single 'post' request should look like in the body here
/*
* Source - uploading a new blueprint. return the hash of the source after making sure it exists
* Modlist - uploading a modlist. Cannot be done alone, MUST reference a source to set to this modlist. Any modlists not set to a source are deleted after 24 hours
* Trial - submitting a new trial to run. Must reference a source. If the source does not exist or other checks fail, return errors. Else, return the trial id and execution id
* */
class FactoryApiIngest {
}
exports.FactoryApiIngest = FactoryApiIngest;
// represents a new trial to execute - should contain information about source automatically
// class needed to easily create database entities and entries.
let FactoryApiExecutionRequest = class FactoryApiExecutionRequest {
};
exports.FactoryApiExecutionRequest = FactoryApiExecutionRequest;
__decorate([
    (0, typeorm_1.PrimaryGeneratedColumn)('uuid'),
    __metadata("design:type", String)
], FactoryApiExecutionRequest.prototype, "execution_id", void 0);
__decorate([
    (0, typeorm_1.Column)({ name: 'trial_id' }),
    __metadata("design:type", String)
], FactoryApiExecutionRequest.prototype, "trialId", void 0);
__decorate([
    (0, typeorm_1.Column)({ name: 'logged_at' }),
    __metadata("design:type", Date)
], FactoryApiExecutionRequest.prototype, "loggedAt", void 0);
__decorate([
    (0, typeorm_1.Column)({
        name: 'allocated_at',
        nullable: true
    }),
    __metadata("design:type", Date)
], FactoryApiExecutionRequest.prototype, "allocatedAt", void 0);
exports.FactoryApiExecutionRequest = FactoryApiExecutionRequest = __decorate([
    (0, typeorm_1.Entity)('factory_request')
], FactoryApiExecutionRequest);
let FactoryApiExecutionStatus = class FactoryApiExecutionStatus {
};
exports.FactoryApiExecutionStatus = FactoryApiExecutionStatus;
__decorate([
    (0, typeorm_1.PrimaryColumn)(),
    __metadata("design:type", String)
], FactoryApiExecutionStatus.prototype, "execution_id", void 0);
__decorate([
    (0, typeorm_1.Column)({
        name: 'logged_at'
    }),
    __metadata("design:type", Date)
], FactoryApiExecutionStatus.prototype, "loggedAt", void 0);
__decorate([
    (0, typeorm_1.Column)(),
    __metadata("design:type", String)
], FactoryApiExecutionStatus.prototype, "worker", void 0);
__decorate([
    (0, typeorm_1.Column)({
        nullable: true
    }),
    __metadata("design:type", Boolean)
], FactoryApiExecutionStatus.prototype, "success", void 0);
exports.FactoryApiExecutionStatus = FactoryApiExecutionStatus = __decorate([
    (0, typeorm_1.Entity)('factory_status')
], FactoryApiExecutionStatus);
/*
* This class will represent a 'quick' source submission and trial.
* Returned will be the source ID and execution ID of the submission.
* Ideal for most basic things you want tested. For higher detail / custom params, use the FactoryApiIngest class
* */
class FactoryApiIngestQuick {
    constructor(bpStr, modList = []) {
        if (!bpStr)
            throw new Error('No blueprint string provided');
        if (!Source_1.Source.isBlueprintString(bpStr))
            throw new Error('Invalid blueprint provided - must be a blueprint string!');
        this.blueprintStr = bpStr;
        this.modList = modList;
    }
}
exports.FactoryApiIngestQuick = FactoryApiIngestQuick;
// STATIC CLASS - used to set up the web server and functionality to start/stop the cluster.
// script will use this to start the server
class FactoryApiIngestServer {
    //static app: Express;
    //static server: http.Server
    static startServer(port = 3001, instances = 8) {
        return __awaiter(this, void 0, void 0, function* () {
            if (cluster_1.default.isPrimary) {
                console.log(`Primary server starting on port ${port} with ${instances} instances`);
                for (let i = 0; i < instances; i++) {
                    cluster_1.default.fork();
                }
                cluster_1.default.on('exit', (worker) => {
                    console.log(`worker ${worker.process.pid} died`);
                });
            }
            else {
                yield FactoryDatabase_1.FactoryDatabase.initialize([
                    {
                        name: 'cache',
                        type: 'postgres',
                        host: process.env.PG_CACHE_HOST,
                        port: parseInt(process.env.PG_CACHE_PORT),
                        username: process.env.PG_CACHE_USER,
                        password: process.env.PG_CACHE_PASS,
                        poolSize: 4,
                        synchronize: true,
                        entities: [
                            Trial_1.Trial,
                            Source_1.Source,
                            ModList_1.ModList,
                            Dataset_1.GameFlowItemRecord,
                            Dataset_1.GameFlowElectricRecord,
                            Dataset_1.GameFlowCircuitRecord,
                            Dataset_1.GameFlowPollutionRecord,
                            Dataset_1.GameFlowSystemRecord,
                            FactoryApiExecutionRequest,
                            FactoryApiExecutionStatus
                        ]
                    }, /*{
                        name: 'storage',
                        type: 'postgres',
                        host: process.env.PG_STORAGE_HOST,
                        port: parseInt(process.env.PG_STORAGE_PORT),
                        username: process.env.PG_STORAGE_USER,
                        password: process.env.PG_STORAGE_PASS,
                        poolSize: 4,
                        synchronize: true,
                        entities: [
                            Trial,
                            Source,
                            ModList,
                            GameFlowItemRecord,
                            GameFlowElectricRecord,
                            GameFlowCircuitRecord,
                            GameFlowPollutionRecord,
                            GameFlowSystemRecord,
                            FactoryApiExecutionRequest,
                            FactoryApiExecutionStatus
                        ]
                    }*/
                ]);
                const ex = (0, express_1.default)();
                ex.use(express_1.default.json());
                ex.use((0, express_1.urlencoded)({ extended: true, limit: '300mb' }));
                ex.get('/', (req, res) => {
                    res.status(200).send('OK');
                });
                ex.post('/quickSubmit', (req, res) => {
                    let body = req.body;
                    if (Source_1.Source.isBlueprintString(body.blueprintStr)) {
                        let s = new Source_1.Source({
                            text: Source_1.Source.anonymizeBlueprintString(body.blueprintStr),
                            variant: 'blueprint',
                            modList: body.modList ? new ModList_1.ModList({ mods: body.modList }) : undefined
                        });
                        FactoryDatabase_1.FactoryDatabase.saveSource(s).then((s) => __awaiter(this, void 0, void 0, function* () {
                            // first, run a query to see if we've already run this default trial. If we have, just return the trial ID
                            let r = yield FactoryDatabase_1.FactoryDatabase.checkIfTrialExists(s.id, Trial_1.Trial.timeToTicks(15), Trial_1.Trial.secondsToTicks(5), true, true, true, true, true);
                            if (r) {
                                res.status(200).json({
                                    trialId: r
                                });
                                return;
                            }
                            let trial = new Trial_1.Trial({
                                source: s,
                                length: Trial_1.Trial.timeToTicks(15),
                                tickInterval: Trial_1.Trial.secondsToTicks(5),
                                initialBots: 200,
                                recordItems: true,
                                recordElectric: true,
                                recordCircuits: true,
                                recordPollution: true,
                                recordSystem: true,
                            });
                            FactoryDatabase_1.FactoryDatabase.saveTrial(trial, false).then((t) => {
                                let er = new FactoryApiExecutionRequest();
                                er.trialId = t.id;
                                er.loggedAt = new Date();
                                FactoryDatabase_1.FactoryDatabase.FactoryDB.getRepository('FactoryApiExecutionRequest').save(er).then((er) => {
                                    res.status(200).json(er);
                                });
                            }).catch((e) => {
                                res.status(500).send(e);
                            });
                        })).catch((e) => {
                            res.status(500).send(e);
                        });
                    }
                    else
                        res.status(400).send('Invalid blueprint string');
                });
                ex.post('/submit', (req, res) => {
                    let body = req.body;
                    if ((body === null || body === void 0 ? void 0 : body.variant) === 'source') {
                        if (body.source != undefined) {
                            let s = new Source_1.Source({
                                text: Source_1.Source.anonymizeBlueprintString(body.source),
                                variant: 'blueprint'
                            });
                            let sd = new Source_1.SourceBlueprintDetails(s);
                            if (!sd.hasRequiredEntities())
                                return res.status(400).send('Invalid blueprint string - must contain a combinator, infinity chest, or infinity pipe');
                            // save the source
                            FactoryDatabase_1.FactoryDatabase.saveSource(s).then((s) => {
                                // respond with s.id
                                res.status(200).send(s.id);
                            });
                        }
                        else
                            res.status(400).send('No source provided');
                        // handle source
                    }
                    else if ((body === null || body === void 0 ? void 0 : body.variant) === 'modlist') {
                        res.status(400).send('Modlist not yet supported on Public API');
                        // handle modlist
                    }
                    else if ((body === null || body === void 0 ? void 0 : body.variant) === 'trial') {
                        // handle trial
                        let er = new FactoryApiExecutionRequest();
                        let tin = body.trial;
                        if (tin && (tin === null || tin === void 0 ? void 0 : tin.source)) {
                            // First off, some initial filters.
                            /// tickInterval can be anything so long as its over 60
                            /// length is MAXIMUM of 216000 (1 hour), MINIMUM of 3600 (1 minute)
                            /// instead of rejecting, we will still run the trial, just with changes to keep values in limits
                            if (tin.tickInterval && tin.tickInterval < 60)
                                tin.tickInterval = 60;
                            if (tin.length && tin.length > 216000)
                                tin.length = 216000;
                            // if the source is over 40 characters and starts with 0e, make a source first
                            if (Source_1.Source.isBlueprintString(tin.source)) {
                                let bpStr = Source_1.Source.anonymizeBlueprintString(tin.source);
                                let s = new Source_1.Source({
                                    text: bpStr,
                                    variant: 'blueprint'
                                });
                                FactoryDatabase_1.FactoryDatabase.saveSource(s).then((s) => __awaiter(this, void 0, void 0, function* () {
                                    var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p;
                                    tin.source = s.id;
                                    // 1 more step is needed here. Check to see if the trial requested already exists. If it does, return the trial ID
                                    let r = yield FactoryDatabase_1.FactoryDatabase.checkIfTrialExists(s.id, (_a = tin.length) !== null && _a !== void 0 ? _a : Trial_1.Trial.timeToTicks(15), (_b = tin.tickInterval) !== null && _b !== void 0 ? _b : Trial_1.Trial.secondsToTicks(5), (_c = tin.recordItems) !== null && _c !== void 0 ? _c : true, (_d = tin.recordElectric) !== null && _d !== void 0 ? _d : true, (_e = tin.recordCircuit) !== null && _e !== void 0 ? _e : true, (_f = tin.recordPollution) !== null && _f !== void 0 ? _f : true, (_g = tin.recordSystem) !== null && _g !== void 0 ? _g : true);
                                    if (r) {
                                        res.status(200).json({
                                            trialId: r
                                        });
                                        return;
                                    }
                                    // create the trial we are going to run based on the settings here
                                    // NOTE - the defaults here should be suitable for MOST cases
                                    let trial = new Trial_1.Trial({
                                        source: s,
                                        length: (_h = body.trial.length) !== null && _h !== void 0 ? _h : Trial_1.Trial.timeToTicks(15),
                                        tickInterval: (_j = body.trial.tickInterval) !== null && _j !== void 0 ? _j : Trial_1.Trial.secondsToTicks(5),
                                        initialBots: 200,
                                        recordItems: (_k = body.trial.recordItems) !== null && _k !== void 0 ? _k : true,
                                        recordElectric: (_l = body.trial.recordElectric) !== null && _l !== void 0 ? _l : true,
                                        recordCircuits: (_m = body.trial.recordCircuit) !== null && _m !== void 0 ? _m : true,
                                        recordPollution: (_o = body.trial.recordPollution) !== null && _o !== void 0 ? _o : true,
                                        recordSystem: (_p = body.trial.recordSystem) !== null && _p !== void 0 ? _p : true,
                                    });
                                    FactoryDatabase_1.FactoryDatabase.saveTrial(trial, false).then((t) => {
                                        er.trialId = t.id;
                                        er.loggedAt = new Date();
                                        return FactoryDatabase_1.FactoryDatabase.FactoryDB.getRepository('FactoryApiExecutionRequest').save(er);
                                    }).then((er) => {
                                        res.status(200).json(er);
                                    }).catch((e) => {
                                        res.status(500).send(e);
                                    });
                                }));
                            }
                            else {
                                // source is likely an ID - find it first, then create the trial
                                FactoryDatabase_1.FactoryDatabase.loadSource(tin.source).then((s) => __awaiter(this, void 0, void 0, function* () {
                                    var _q, _r, _s, _t, _u, _v, _w, _x, _y, _z, _0, _1, _2, _3;
                                    tin.source = s.id;
                                    // 1 more step is needed here. Check to see if the trial requested already exists. If it does, return the trial ID
                                    let r = yield FactoryDatabase_1.FactoryDatabase.checkIfTrialExists(s.id, (_q = body.trial.length) !== null && _q !== void 0 ? _q : Trial_1.Trial.timeToTicks(15), (_r = body.trial.tickInterval) !== null && _r !== void 0 ? _r : Trial_1.Trial.secondsToTicks(5), (_s = body.trial.recordItems) !== null && _s !== void 0 ? _s : true, (_t = body.trial.recordElectric) !== null && _t !== void 0 ? _t : true, (_u = body.trial.recordCircuit) !== null && _u !== void 0 ? _u : true, (_v = body.trial.recordPollution) !== null && _v !== void 0 ? _v : true, (_w = body.trial.recordSystem) !== null && _w !== void 0 ? _w : true);
                                    if (r) {
                                        res.status(200).json({
                                            trialId: r
                                        });
                                        return;
                                    }
                                    // create the trial we are going to run based on the settings here
                                    // NOTE - the defaults here should be suitable for MOST cases
                                    let trial = new Trial_1.Trial({
                                        source: s,
                                        length: (_x = body.trial.length) !== null && _x !== void 0 ? _x : Trial_1.Trial.timeToTicks(15),
                                        tickInterval: (_y = body.trial.tickInterval) !== null && _y !== void 0 ? _y : Trial_1.Trial.secondsToTicks(5),
                                        initialBots: 200,
                                        recordItems: (_z = body.trial.recordItems) !== null && _z !== void 0 ? _z : true,
                                        recordElectric: (_0 = body.trial.recordElectric) !== null && _0 !== void 0 ? _0 : true,
                                        recordCircuits: (_1 = body.trial.recordCircuit) !== null && _1 !== void 0 ? _1 : true,
                                        recordPollution: (_2 = body.trial.recordPollution) !== null && _2 !== void 0 ? _2 : true,
                                        recordSystem: (_3 = body.trial.recordSystem) !== null && _3 !== void 0 ? _3 : true,
                                    });
                                    FactoryDatabase_1.FactoryDatabase.saveTrial(trial, false).then((t) => {
                                        er.trialId = t.id;
                                        er.loggedAt = new Date();
                                        return FactoryDatabase_1.FactoryDatabase.FactoryDB.getRepository('FactoryApiExecutionRequest').save(er);
                                    }).then((er) => {
                                        res.status(200).json(er);
                                    }).catch((e) => {
                                        res.status(500).send(e);
                                    });
                                }));
                            }
                        }
                        else
                            res.status(400).send('No source provided');
                    }
                    else
                        res.status(400).send('Invalid variant');
                });
                ex.listen(port, () => {
                    console.log(`Server instance started on port ${port}`);
                });
            }
        });
    }
}
exports.FactoryApiIngestServer = FactoryApiIngestServer;
//# sourceMappingURL=FactoryApiIngest.js.map