"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const tslib_1 = require("tslib");
const fs_1 = tslib_1.__importDefault(require("fs"));
const path_1 = tslib_1.__importDefault(require("path"));
const stream_1 = require("stream");
const ixixx_1 = require("ixixx");
const command_1 = require("@oclif/command");
const gff3Adapter_1 = require("../types/gff3Adapter");
const vcfAdapter_1 = require("../types/vcfAdapter");
const base_1 = tslib_1.__importDefault(require("../base"));
const common_1 = require("../types/common");
const object_fromentries_1 = tslib_1.__importDefault(require("object.fromentries"));
if (!Object.fromEntries) {
    // @ts-ignore
    object_fromentries_1.default.shim();
}
function readConf(confFilePath) {
    return JSON.parse(fs_1.default.readFileSync(confFilePath, 'utf8'));
}
class TextIndex extends base_1.default {
    async run() {
        const { flags } = this.parse(TextIndex);
        const { perTrack, file } = flags;
        if (file) {
            await this.indexFileList();
        }
        else if (perTrack) {
            await this.perTrackIndex();
        }
        else {
            await this.aggregateIndex();
        }
        this.log('Finished!');
    }
    async aggregateIndex() {
        var _a;
        const { flags } = this.parse(TextIndex);
        const { out, target, tracks, assemblies, attributes, quiet, force, exclude, dryrun, prefixSize, } = flags;
        const outFlag = target || out || '.';
        const isDir = fs_1.default.lstatSync(outFlag).isDirectory();
        const confPath = isDir ? path_1.default.join(outFlag, 'config.json') : outFlag;
        const outDir = path_1.default.dirname(confPath);
        const config = readConf(confPath);
        const trixDir = path_1.default.join(outDir, 'trix');
        if (!fs_1.default.existsSync(trixDir)) {
            fs_1.default.mkdirSync(trixDir);
        }
        const aggregateAdapters = config.aggregateTextSearchAdapters || [];
        const asms = (assemblies === null || assemblies === void 0 ? void 0 : assemblies.split(',')) ||
            ((_a = config.assemblies) === null || _a === void 0 ? void 0 : _a.map(a => a.name)) ||
            (config.assembly ? [config.assembly.name] : []);
        if (!(asms === null || asms === void 0 ? void 0 : asms.length)) {
            throw new Error('No assemblies found');
        }
        for (const asm of asms) {
            const configs = await this.getTrackConfigs(confPath, tracks === null || tracks === void 0 ? void 0 : tracks.split(','), asm);
            this.log('Indexing assembly ' + asm + '...');
            if (!configs.length) {
                continue;
            }
            if (dryrun) {
                this.log(configs.map(e => `${e.trackId}\t${e.adapter.type}`).join('\n'));
            }
            else {
                const id = asm + '-index';
                const foundIdx = aggregateAdapters.findIndex(x => x.textSearchAdapterId === id);
                if (foundIdx !== -1 && !force) {
                    this.log(`Note: ${asm} has already been indexed with this configuration, use --force to overwrite this assembly. Skipping for now`);
                    continue;
                }
                await this.indexDriver({
                    configs,
                    outDir,
                    quiet,
                    name: asm,
                    attributes: attributes.split(','),
                    exclude: exclude.split(','),
                    assemblyNames: [asm],
                    prefixSize,
                });
                const trixConf = {
                    type: 'TrixTextSearchAdapter',
                    textSearchAdapterId: id,
                    ixFilePath: {
                        uri: `trix/${asm}.ix`,
                        locationType: 'UriLocation',
                    },
                    ixxFilePath: {
                        uri: `trix/${asm}.ixx`,
                        locationType: 'UriLocation',
                    },
                    metaFilePath: {
                        uri: `trix/${asm}_meta.json`,
                        locationType: 'UriLocation',
                    },
                    assemblyNames: [asm],
                };
                if (foundIdx === -1) {
                    aggregateAdapters.push(trixConf);
                }
                else {
                    aggregateAdapters[foundIdx] = trixConf;
                }
            }
        }
        if (!dryrun) {
            fs_1.default.writeFileSync(confPath, JSON.stringify(Object.assign(Object.assign({}, config), { aggregateTextSearchAdapters: aggregateAdapters }), null, 2));
        }
    }
    async perTrackIndex() {
        const { flags } = this.parse(TextIndex);
        const { out, target, tracks, assemblies, attributes, quiet, force, exclude, prefixSize, } = flags;
        const outFlag = target || out || '.';
        const isDir = fs_1.default.lstatSync(outFlag).isDirectory();
        const confFilePath = isDir ? path_1.default.join(outFlag, 'config.json') : outFlag;
        const outDir = path_1.default.dirname(confFilePath);
        const config = readConf(confFilePath);
        const configTracks = config.tracks || [];
        const trixDir = path_1.default.join(outDir, 'trix');
        if (!fs_1.default.existsSync(trixDir)) {
            fs_1.default.mkdirSync(trixDir);
        }
        if (assemblies) {
            throw new Error(`Can't specify assemblies when indexing per track, remove assemblies flag to continue.`);
        }
        const confs = await this.getTrackConfigs(confFilePath, tracks === null || tracks === void 0 ? void 0 : tracks.split(','));
        if (!confs.length) {
            throw new Error(`Tracks not found in config.json, please add track configurations before indexing.`);
        }
        for (const trackConfig of confs) {
            const { textSearching, trackId, assemblyNames } = trackConfig;
            if ((textSearching === null || textSearching === void 0 ? void 0 : textSearching.textSearchAdapter) && !force) {
                this.log(`Note: ${trackId} has already been indexed with this configuration, use --force to overwrite this track. Skipping for now`);
                continue;
            }
            this.log('Indexing track ' + trackId + '...');
            const id = trackId + '-index';
            await this.indexDriver({
                configs: [trackConfig],
                attributes: attributes.split(','),
                outDir,
                quiet,
                name: trackId,
                exclude: exclude.split(','),
                assemblyNames,
                prefixSize,
            });
            if (!textSearching || !(textSearching === null || textSearching === void 0 ? void 0 : textSearching.textSearchAdapter)) {
                const newTrackConfig = Object.assign(Object.assign({}, trackConfig), { textSearching: Object.assign(Object.assign({}, textSearching), { textSearchAdapter: {
                            type: 'TrixTextSearchAdapter',
                            textSearchAdapterId: id,
                            ixFilePath: {
                                uri: `trix/${trackId}.ix`,
                                locationType: 'UriLocation',
                            },
                            ixxFilePath: {
                                uri: `trix/${trackId}.ixx`,
                                locationType: 'UriLocation',
                            },
                            metaFilePath: {
                                uri: `trix/${trackId}_meta.json`,
                                locationType: 'UriLocation',
                            },
                            assemblyNames: assemblyNames,
                        } }) });
                // modifies track with new text search adapter
                const index = configTracks.findIndex(track => trackId === track.trackId);
                configTracks[index] = newTrackConfig;
            }
            fs_1.default.writeFileSync(confFilePath, JSON.stringify(Object.assign(Object.assign({}, config), { tracks: configTracks }), null, 2));
        }
    }
    async indexFileList() {
        const { flags } = this.parse(TextIndex);
        const { out, target, file, attributes, quiet, exclude, prefixSize } = flags;
        const outFlag = target || out || '.';
        const trixDir = path_1.default.join(outFlag, 'trix');
        if (!fs_1.default.existsSync(trixDir)) {
            fs_1.default.mkdirSync(trixDir);
        }
        const configs = file
            .map(file => (0, common_1.guessAdapterFromFileName)(file))
            .filter(fileConfig => (0, common_1.supported)(fileConfig.adapter.type));
        await this.indexDriver({
            configs,
            outDir: outFlag,
            name: configs.length > 1 ? 'aggregate' : path_1.default.basename(file[0]),
            quiet,
            attributes: attributes.split(','),
            exclude: exclude.split(','),
            assemblyNames: [],
            prefixSize,
        });
        this.log('Successfully created index for these files. See https://jbrowse.org/storybook/lgv/main/?path=/story/text-searching--page for info about usage');
    }
    async indexDriver({ configs, attributes, outDir, name, quiet, exclude, assemblyNames, prefixSize, }) {
        const readable = stream_1.Readable.from(this.indexFiles(configs, attributes, outDir, quiet, exclude));
        const ixIxxStream = await this.runIxIxx(readable, outDir, name, prefixSize);
        await (0, common_1.generateMeta)({
            configs,
            attributes,
            outDir,
            name,
            exclude,
            assemblyNames,
        });
        return ixIxxStream;
    }
    getLoc(elt) {
        if (elt.locationType === 'LocalPathLocation') {
            return elt.localPath;
        }
        return elt.uri;
    }
    indexFiles(trackConfigs, attributes, outLocation, quiet, typesToExclude) {
        return tslib_1.__asyncGenerator(this, arguments, function* indexFiles_1() {
            for (const config of trackConfigs) {
                const { adapter, textSearching } = config;
                const { type } = adapter;
                const { indexingFeatureTypesToExclude: types = typesToExclude, indexingAttributes: attrs = attributes, } = textSearching || {};
                if (type === 'Gff3TabixAdapter') {
                    yield tslib_1.__await(yield* tslib_1.__asyncDelegator(tslib_1.__asyncValues((0, gff3Adapter_1.indexGff3)(config, attrs, this.getLoc(adapter.gffGzLocation), outLocation, types, quiet))));
                }
                else if (type === 'Gff3Adapter') {
                    yield tslib_1.__await(yield* tslib_1.__asyncDelegator(tslib_1.__asyncValues((0, gff3Adapter_1.indexGff3)(config, attrs, this.getLoc(adapter.gffLocation), outLocation, types, quiet))));
                }
                else if (type === 'VcfTabixAdapter') {
                    yield tslib_1.__await(yield* tslib_1.__asyncDelegator(tslib_1.__asyncValues((0, vcfAdapter_1.indexVcf)(config, attrs, this.getLoc(adapter.vcfGzLocation), outLocation, types, quiet))));
                }
                else if (type === 'VcfAdapter') {
                    yield tslib_1.__await(yield* tslib_1.__asyncDelegator(tslib_1.__asyncValues((0, vcfAdapter_1.indexVcf)(config, attrs, this.getLoc(adapter.vcfLocation), outLocation, types, quiet))));
                }
                // gtf unused currently
            }
        });
    }
    runIxIxx(readStream, outLocation, name, prefixSize) {
        const ixFilename = path_1.default.join(outLocation, 'trix', `${name}.ix`);
        const ixxFilename = path_1.default.join(outLocation, 'trix', `${name}.ixx`);
        return (0, ixixx_1.ixIxxStream)(readStream, ixFilename, ixxFilename, prefixSize);
    }
    async getTrackConfigs(configPath, trackIds, assemblyName) {
        const { tracks } = readConf(configPath);
        if (!tracks) {
            return [];
        }
        const trackIdsToIndex = trackIds || (tracks === null || tracks === void 0 ? void 0 : tracks.map(track => track.trackId));
        return trackIdsToIndex
            .map(trackId => {
            const currentTrack = tracks.find(t => trackId === t.trackId);
            if (!currentTrack) {
                throw new Error(`Track not found in config.json for trackId ${trackId}, please add track configuration before indexing.`);
            }
            return currentTrack;
        })
            .filter(track => { var _a; return (0, common_1.supported)((_a = track.adapter) === null || _a === void 0 ? void 0 : _a.type); })
            .filter(track => assemblyName ? track.assemblyNames.includes(assemblyName) : true);
    }
}
exports.default = TextIndex;
TextIndex.description = 'Make a text-indexing file for any given track(s).';
TextIndex.examples = [
    "# indexes all tracks that it can find in the current directory's config.json",
    '$ jbrowse text-index',
    '',
    "# indexes specific trackIds that it can find in the current directory's config.json",
    '$ jbrowse text-index --tracks=track1,track2,track3',
    '',
    "# indexes all tracks in a directory's config.json or in a specific config file",
    '$ jbrowse text-index --out /path/to/jb2/',
    '',
    '# indexes only a specific assembly, and overwrite what was previously there using force (which is needed if a previous index already existed)',
    '$ jbrowse text-index -a hg19 --force',
    '',
    '# create index for some files for use in @jbrowse/react-linear-genome-view or similar',
    '$ jbrowse text-index --file myfile.gff3.gz --file myfile.vcfgz --out indexes',
];
TextIndex.flags = {
    help: command_1.flags.help({ char: 'h' }),
    tracks: command_1.flags.string({
        description: `Specific tracks to index, formatted as comma separated trackIds. If unspecified, indexes all available tracks`,
    }),
    target: command_1.flags.string({
        description: 'Path to config file in JB2 installation directory to read from.',
    }),
    out: command_1.flags.string({
        description: 'Synonym for target',
    }),
    attributes: command_1.flags.string({
        description: 'Comma separated list of attributes to index',
        default: 'Name,ID',
    }),
    assemblies: command_1.flags.string({
        char: 'a',
        description: 'Specify the assembl(ies) to create an index for. If unspecified, creates an index for each assembly in the config',
    }),
    force: command_1.flags.boolean({
        default: false,
        description: 'Overwrite previously existing indexes',
    }),
    quiet: command_1.flags.boolean({
        char: 'q',
        default: false,
        description: 'Hide the progress bars',
    }),
    perTrack: command_1.flags.boolean({
        default: false,
        description: 'If set, creates an index per track',
    }),
    exclude: command_1.flags.string({
        description: 'Adds gene type to list of excluded types',
        default: 'CDS,exon',
    }),
    prefixSize: command_1.flags.integer({
        description: 'Specify the prefix size for the ixx index, increase size if many of your gene IDs have same prefix e.g. Z000000001, Z000000002',
        default: 6,
    }),
    file: command_1.flags.string({
        description: 'File or files to index (can be used to create trix indexes for embedded component use cases not using a config.json for example)',
        multiple: true,
    }),
    dryrun: command_1.flags.boolean({
        description: 'Just print out tracks that will be indexed by the process, without doing any indexing',
    }),
};
