"use strict";
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 __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 __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const body_parser_1 = __importDefault(require("body-parser"));
const config_1 = __importDefault(require("config"));
const express_1 = __importDefault(require("express"));
const BaseService_1 = __importDefault(require("../common/BaseService"));
const Logger_1 = __importStar(require("../common/Logger"));
const ErrorCode_1 = __importDefault(require("./ErrorCode"));
const Datastore_1 = __importDefault(require("../common/Datastore"));
const mongodb_1 = require("mongodb");
const ipfs_client_1 = require("ipfs-client");
const path_1 = __importDefault(require("path"));
class IndexService extends BaseService_1.default {
    constructor() {
        super(Logger_1.Category.IndexService);
        this.app = (0, express_1.default)();
        this.createIndexRequest = this.createIndexRequest.bind(this);
        if (!this.enabled) {
            this.logger.warn('Service is not enabled. Exit now...');
            return;
        }
        this.app.use(Logger_1.default.getExpressLogger(Logger_1.Category.IndexService));
        this.app.use(body_parser_1.default.urlencoded({ extended: false }));
        this.app.use(body_parser_1.default.json());
        this.app.use(function (_req, res, next) {
            res.setHeader('Content-Type', 'application/json');
            next();
        });
        this.app.get('/create/:id', this.createIndexRequest);
        this.ipfsClient = (0, ipfs_client_1.create)({
            http: config_1.default.get('index_service.ipfs_http')
        });
    }
    async pinIndex(dir) {
        for (const [name, entry] of dir.entries.entries()) {
            const type = entry.type;
            if (type === 'dir') {
                dir.entries.set(name, await this.pinIndex(entry));
            }
            else if (type === 'file') {
                entry.sourcesMap = null;
            }
        }
        dir.sourcesMap = null;
        return this.ipfsClient.dag.put(dir, {
            pin: true
        });
    }
    async createIndexRequest(request, response) {
        const id = request.params['id'];
        this.logger.info(`Creating index for dataset`, { id });
        const found = mongodb_1.ObjectId.isValid(id) ? await Datastore_1.default.ScanningRequestModel.findById(id) : await Datastore_1.default.ScanningRequestModel.findOne({ name: id });
        if (!found) {
            this.sendError(response, ErrorCode_1.default.DATASET_NOT_FOUND);
            return;
        }
        if (found.status !== 'completed') {
            this.sendError(response, ErrorCode_1.default.SCANNING_INCOMPLETE);
            return;
        }
        const unfinishedGenerations = await Datastore_1.default.GenerationRequestModel.count({ datasetId: found.id, status: { $ne: 'completed' } });
        const root = {
            entries: new Map(),
            name: '',
            sourcesMap: new Map(),
            sources: [],
            type: 'dir'
        };
        for await (const generation of Datastore_1.default.GenerationRequestModel.find({ datasetId: found.id, status: 'completed' }).sort({ index: 1 })) {
            const dataCid = generation.dataCid;
            const pieceCid = generation.pieceCid;
            for (const file of generation.generatedFileList) {
                let node = root;
                const segments = file.path.split(path_1.default.sep);
                // Enter directories
                for (let i = 0; i < segments.length - 1; ++i) {
                    const source = {
                        dataCid, pieceCid, selector: file.selector.slice(0, i)
                    };
                    if (!node.sourcesMap.has(dataCid)) {
                        node.sourcesMap.set(dataCid, source);
                        node.sources.push(source);
                    }
                    if (!node.entries.has(segments[i])) {
                        const entry = {
                            entries: new Map(),
                            sourcesMap: new Map(),
                            sources: [],
                            type: 'dir',
                            name: segments[i]
                        };
                        node.entries.set(segments[i], entry);
                    }
                    node = node.entries.get(segments[i]);
                }
                // Handle file node
                const segment = segments[segments.length - 1];
                const source = {
                    dataCid, pieceCid, selector: file.selector.slice(0, file.selector.length - 1)
                };
                if (!node.sourcesMap.has(dataCid)) {
                    node.sourcesMap.set(dataCid, source);
                    node.sources.push(source);
                }
                if (!node.entries.has(segment)) {
                    node.entries.set(segment, {
                        name: segment,
                        size: file.size,
                        sourcesMap: new Map(),
                        sources: [],
                        type: 'file'
                    });
                }
                const fileSource = {
                    dataCid, pieceCid, selector: file.selector, from: file.start, to: file.end
                };
                const sourceMap = node.entries.get(segment).sourcesMap;
                if (!sourceMap.has(dataCid)) {
                    sourceMap.set(dataCid, fileSource);
                    node.entries.get(segment).sources.push(fileSource);
                }
            }
        }
        // PIN to IPFS
        const rootCid = await this.pinIndex(root);
        const result = {
            rootCid: rootCid.toString()
        };
        if (unfinishedGenerations > 0) {
            result.warning = `There are still ${unfinishedGenerations} incomplete generation requests`;
        }
        response.end(JSON.stringify(result));
    }
    start() {
        const bind = config_1.default.get('index_service.bind');
        const port = config_1.default.get('index_service.port');
        this.app.listen(port, bind, () => {
            this.logger.info(`Index Service started listening at http://${bind}:${port}`);
        });
    }
    sendError(response, error) {
        this.logger.warn(`Error code`, { error });
        response.status(400);
        response.end(JSON.stringify({ error }));
    }
}
exports.default = IndexService;
//# sourceMappingURL=IndexService.js.map