"use strict";
/*
* Each class should work like this....
*
* Take 'source' as an example
*
* 1. class called 'Source' - this is the CORE class. It will have functionality that should USE the data and provide info
*   - REQUIRED to be creatable on the frontend easily too - nothing fancy in constructors or for setup
* 2. class called 'SavedSource' - this is the class that will be derived from Source, and has the ability to save data
*   - should NEVER EVER be used on the frontend, only on the backend
*   - Any sources sent back/forth will be as a Source, not SavedSource
*   - needs to have a constructor that can take a Source and create a SavedSource from it, or the same params as Source
*   - include a 'save' method that will save the data to the database in the class itself
*   - static load and loadMany functions as well - make all functionality derived from here
*
* */
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 __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
var Source_1;
Object.defineProperty(exports, "__esModule", { value: true });
exports.SourceBlueprintDetails = exports.Source = exports.ISource = void 0;
const ModList_1 = require("./ModList");
const crypto_1 = __importDefault(require("crypto"));
const typeorm_1 = require("typeorm");
const zlib = __importStar(require("zlib"));
const Logging_1 = require("./Logging");
const lodash_1 = __importDefault(require("lodash"));
/*
* Having either blueprint or saveGamePath is  required. one or the other! If both are passed in, an error will be thrown
* */
class ISource {
}
exports.ISource = ISource;
let Source = Source_1 = class Source {
    static ensureObject(source) {
        let a = source;
        if (a === null || a === void 0 ? void 0 : a.updateHash)
            return a;
        else if (source)
            return new Source_1(source);
        else
            return undefined;
    }
    constructor(params = null) {
        this.tags = [];
        // if no params, just return empty. Used as template for Object.assign to take over
        if (!params)
            return;
        this.name = params.name;
        this.desc = params.desc;
        this.tags = params.tags ? params.tags : [];
        this.modList = params.modList ? ModList_1.ModList.ensureObject(params.modList) : undefined;
        // only need 'text' if the 'id' is not set. if id is already set, dont need text here
        if (!params.variant || (!params.text && !params.id))
            throw new Error('Need both variant and text fields to create a Source');
        if (params.text) {
            if (params.variant == 'blueprint') {
                this.variant = params.variant;
                this.text = params.text.replaceAll('\n', '');
            }
            else if (params.variant == 'savegame') {
                this.variant = params.variant;
                this.text = params.text;
            }
            else
                throw new Error('Must have either blueprint or savegame variant specified in creating a Source');
            this.updateHash();
        }
        else if (params.id) {
            this.id = params.id;
        }
        else
            throw new Error('Need either text or id to create a Source');
    }
    updateHash() {
        var _a;
        if (this.text) {
            // Whether it be savegame path or blueprint string, we need to hash it
            /// BUT, if it's a blueprint string, we first need to parse it and remove the icons, label, and description fields if they exist
            /// this will truly anonymize the source - then after we turn back into a blueprint string, we hash and save it
            /// 4/15 note - What if there's a modlist incorrectly uploaded with a source? Need to make modlist in the key as well
            /// this could make it more difficult to query for a specific source if small mods are added.
            //// Adding another field for 'raw_bp'
            this.bpKey = crypto_1.default.createHash('sha256').update(this.text).digest('hex');
            this.id = crypto_1.default.createHash('sha256').update(this.text + (((_a = this === null || this === void 0 ? void 0 : this.modList) === null || _a === void 0 ? void 0 : _a.mods) ? this.modList.mods.join(',') : '')).digest('hex');
            return true;
        }
        else {
            // need to return false here - we are likely on the web!!
            return false;
        }
    }
    addTag(tag) {
        if (!this.tags.includes(tag)) {
            this.tags.push(tag);
        }
    }
    static isBlueprintString(bpStr) {
        if (!bpStr || typeof bpStr !== 'string')
            return false;
        return (bpStr.length > 40 && bpStr.startsWith('0e'));
    }
    removeTag(tag) {
        if (this.tags.includes(tag)) {
            this.tags.splice(this.tags.indexOf(tag), 1);
        }
    }
    // Load blueprint from an existing one
    static blueprintStringToObject(bpStr) {
        const version = bpStr.slice(0, 1);
        if (version !== '0') {
            throw new Error('Unknown Blueprint Prefix, blueprint may be partial or too old/new');
        }
        let data = null;
        try {
            data = JSON.parse(zlib
                .inflateSync(Buffer.from(bpStr.slice(1), 'base64'))
                .toString('utf8'));
        }
        catch (e) {
            Logging_1.Logging.log('error', e);
            throw e;
        }
        return data;
    }
    static anonymizeBlueprintString(bpStr) {
        var _a, _b, _c;
        let bpOBj = Source_1.blueprintStringToObject(bpStr);
        if ((_a = bpOBj === null || bpOBj === void 0 ? void 0 : bpOBj.blueprint) === null || _a === void 0 ? void 0 : _a.label) {
            delete bpOBj.blueprint.label;
            bpOBj.blueprint.label = 'Blueprint Submission';
        }
        if ((_b = bpOBj === null || bpOBj === void 0 ? void 0 : bpOBj.blueprint) === null || _b === void 0 ? void 0 : _b.description) {
            delete bpOBj.blueprint.description;
            bpOBj.blueprint.description = 'This blueprint was submitted for benchmarking';
        }
        if ((_c = bpOBj === null || bpOBj === void 0 ? void 0 : bpOBj.blueprint) === null || _c === void 0 ? void 0 : _c.icons) {
            delete bpOBj.blueprint.icons;
            bpOBj.blueprint.icons = [];
        }
        return Source_1.objectToBlueprintString(bpOBj);
    }
    static objectToBlueprintString(bpObj) {
        let json = JSON.stringify(bpObj);
        let compressed = zlib.deflateSync(json);
        return '0' + compressed.toString('base64');
    }
};
exports.Source = Source;
__decorate([
    (0, typeorm_1.PrimaryColumn)(),
    __metadata("design:type", String)
], Source.prototype, "id", void 0);
__decorate([
    (0, typeorm_1.Column)({
        nullable: false,
    }),
    __metadata("design:type", String)
], Source.prototype, "variant", void 0);
__decorate([
    (0, typeorm_1.Column)({
        nullable: true,
        type: 'varchar'
    }),
    __metadata("design:type", String)
], Source.prototype, "name", void 0);
__decorate([
    (0, typeorm_1.Column)({
        nullable: true,
        type: 'varchar'
    }),
    __metadata("design:type", String)
], Source.prototype, "desc", void 0);
__decorate([
    (0, typeorm_1.Column)({
        nullable: false,
        type: 'simple-array'
    }),
    __metadata("design:type", Array)
], Source.prototype, "tags", void 0);
__decorate([
    (0, typeorm_1.ManyToOne)(() => ModList_1.ModList),
    __metadata("design:type", ModList_1.ModList
    // rawId is the 'blueprint' string hashed WITHOUT any mods. Helps with querying
    )
], Source.prototype, "modList", void 0);
__decorate([
    (0, typeorm_1.Column)({
        name: 'bp_key',
        nullable: false,
        type: 'varchar'
    }),
    __metadata("design:type", String)
], Source.prototype, "bpKey", void 0);
__decorate([
    (0, typeorm_1.Column)({
        nullable: false,
        type: 'varchar'
    }),
    __metadata("design:type", String)
], Source.prototype, "text", void 0);
exports.Source = Source = Source_1 = __decorate([
    (0, typeorm_1.Entity)('source'),
    __metadata("design:paramtypes", [ISource])
], Source);
/*
* Class intended for getting more details about a blueprint. Caches the parsed data and has computed / functions available
* to access data about the blueprint
* */
class SourceBlueprintDetails extends Source {
    constructor(params = null) {
        super(params);
        if (this.variant != 'blueprint')
            throw new Error('Blueprint details can only be created from a blueprint source');
        let pdata = Source.blueprintStringToObject(this.text);
        if (pdata === null || pdata === void 0 ? void 0 : pdata.blueprint)
            this.data = pdata.blueprint;
        else if (pdata === null || pdata === void 0 ? void 0 : pdata.blueprint_book)
            throw new Error('Cannot use Blueprint Book - only single blueprints');
        else
            throw new Error('Blueprint data not found in blueprint string');
        // now need to offset the coordinates so that the top left is 0,0
        // positive goes down and right, negative goes up and left
        this.normalizeCoordinates();
    }
    normalizeCoordinates() {
        let minX = lodash_1.default.minBy(this.data.entities, (e) => e.position.x).position.x;
        let minY = lodash_1.default.minBy(this.data.entities, (e) => e.position.y).position.y;
        // go through all entities and tiles, subtract minX and minY from each coordinate
        if (this.data.entities)
            for (let e of this.data.entities) {
                e.position.x -= minX;
                e.position.y -= minY;
            }
        if (this.data.tiles)
            for (let t of this.data.tiles) {
                t.position.x -= minX;
                t.position.y -= minY;
            }
    }
    hasRequiredEntities() {
        // check to make sure we have at LEAST 1 entity of type 'infinity-chest','infinity-pipe', or 'combinator'. If not, return false
        let e = lodash_1.default.find(this.data.entities, (e) => e.name == 'infinity-chest' || e.name == 'infinity-pipe' || e.name == 'combinator');
        return (e != undefined);
    }
    /*need separate class altogether for 'challenge' setup
    * define...
    * - allowedItems (label names) - if set, ONLY these items are allowed, else all are allowed
    * - bannedItems (label names) - if set, these items are banned, else none are banned
    * Can set up both or either, rules are combined. In some cases you may want to just ban 1 item, or only allow specific items
    * - scoreFunction - function that will take in a COMPLETED trial and return a score
    *   - expected to be a user-set function.
    * - scoreExplainedFunction - function that will take in a COMPLETED trial and return a string explaining the score breakdown
    *
    * */
    get entityCounts() {
        let counts = {};
        for (let e of this.data.entities) {
            if (!counts[e.name])
                counts[e.name] = 0;
            counts[e.name] += 1;
        }
        return counts;
    }
    get gridRange() {
        return {
            minX: lodash_1.default.minBy(this.data.entities, (e) => e.position.x).position.x,
            maxX: lodash_1.default.maxBy(this.data.entities, (e) => e.position.x).position.x,
            minY: lodash_1.default.minBy(this.data.entities, (e) => e.position.y).position.y,
            maxY: lodash_1.default.maxBy(this.data.entities, (e) => e.position.y).position.y
        };
    }
    get gridSize() {
        // grab min and max of x,y
        let gr = this.gridRange;
        return { x: Math.abs(gr.maxX - gr.minX) + 1, y: Math.abs(gr.maxY - gr.minY) + 1 };
    }
}
exports.SourceBlueprintDetails = SourceBlueprintDetails;
//# sourceMappingURL=Source.js.map