"use strict";
var __extends = (this && this.__extends) || (function () {
    var extendStatics = function (d, b) {
        extendStatics = Object.setPrototypeOf ||
            ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
            function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };
        return extendStatics(d, b);
    };
    return function (d, b) {
        if (typeof b !== "function" && b !== null)
            throw new TypeError("Class extends value " + String(b) + " is not a constructor or null");
        extendStatics(d, b);
        function __() { this.constructor = d; }
        d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
    };
})();
var __assign = (this && this.__assign) || function () {
    __assign = Object.assign || function(t) {
        for (var s, i = 1, n = arguments.length; i < n; i++) {
            s = arguments[i];
            for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
                t[p] = s[p];
        }
        return t;
    };
    return __assign.apply(this, arguments);
};
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.formatFile = exports.formatStream = exports.formatSync = exports.parseStringSync = exports.parseStream = void 0;
var stream_1 = require("stream");
var string_decoder_1 = require("string_decoder");
var parse_1 = __importDefault(require("./parse"));
var util_1 = require("./util");
// call a callback on the next process tick if running in
// an environment that supports it
function _callback(callback) {
    if (process && process.nextTick)
        process.nextTick(callback);
    else
        callback();
}
// shared arg processing for the parse routines
function _processParseOptions(options) {
    var out = __assign({ encoding: 'utf8', parseFeatures: true, parseDirectives: false, parseSequences: true, parseComments: false, bufferSize: 1000 }, options);
    if (options.parseAll) {
        out.parseFeatures = true;
        out.parseDirectives = true;
        out.parseComments = true;
        out.parseSequences = true;
    }
    return out;
}
var GFFTransform = /** @class */ (function (_super) {
    __extends(GFFTransform, _super);
    function GFFTransform(inputOptions) {
        if (inputOptions === void 0) { inputOptions = {}; }
        var _this = _super.call(this, { objectMode: true }) || this;
        _this.textBuffer = '';
        var options = _processParseOptions(inputOptions);
        _this.encoding = inputOptions.encoding || 'utf8';
        _this.decoder = new string_decoder_1.StringDecoder();
        var push = _this.push.bind(_this);
        _this.parser = new parse_1.default({
            featureCallback: options.parseFeatures ? push : undefined,
            directiveCallback: options.parseDirectives ? push : undefined,
            commentCallback: options.parseComments ? push : undefined,
            sequenceCallback: options.parseSequences ? push : undefined,
            errorCallback: function (err) { return _this.emit('error', err); },
            bufferSize: options.bufferSize,
        });
        return _this;
    }
    GFFTransform.prototype._addLine = function (data) {
        if (data) {
            this.parser.addLine(data);
        }
    };
    GFFTransform.prototype._nextText = function (buffer) {
        var _this = this;
        var pieces = (this.textBuffer + buffer).split(/\r?\n/);
        this.textBuffer = pieces.pop() || '';
        pieces.forEach(function (piece) { return _this._addLine(piece); });
    };
    GFFTransform.prototype._transform = function (chunk, _encoding, callback) {
        this._nextText(this.decoder.write(chunk));
        _callback(callback);
    };
    GFFTransform.prototype._flush = function (callback) {
        if (this.decoder.end)
            this._nextText(this.decoder.end());
        if (this.textBuffer != null)
            this._addLine(this.textBuffer);
        this.parser.finish();
        _callback(callback);
    };
    return GFFTransform;
}(stream_1.Transform));
/**
 * Parse a stream of text data into a stream of feature, directive, comment,
 * an sequence objects.
 *
 * @param options - Parsing options
 * @returns stream (in objectMode) of parsed items
 */
function parseStream(options) {
    if (options === void 0) { options = {}; }
    return new GFFTransform(options);
}
exports.parseStream = parseStream;
function parseStringSync(str, inputOptions) {
    if (inputOptions === void 0) { inputOptions = {}; }
    if (!str)
        return [];
    var options = _processParseOptions(inputOptions);
    var items = [];
    var push = items.push.bind(items);
    var parser = new parse_1.default({
        featureCallback: options.parseFeatures ? push : undefined,
        directiveCallback: options.parseDirectives ? push : undefined,
        commentCallback: options.parseComments ? push : undefined,
        sequenceCallback: options.parseSequences ? push : undefined,
        bufferSize: Infinity,
        errorCallback: function (err) {
            throw err;
        },
    });
    str.split(/\r?\n/).forEach(parser.addLine.bind(parser));
    parser.finish();
    return items;
}
exports.parseStringSync = parseStringSync;
/**
 * Format an array of GFF3 items (features,directives,comments) into string of
 * GFF3. Does not insert synchronization (###) marks.
 *
 * @param items - Array of features, directives, comments and/or sequences
 * @returns the formatted GFF3
 */
function formatSync(items) {
    // sort items into seq and other
    var other = [];
    var sequences = [];
    items.forEach(function (i) {
        if ('sequence' in i)
            sequences.push(i);
        else
            other.push(i);
    });
    var str = other.map(util_1.formatItem).join('');
    if (sequences.length) {
        str += '##FASTA\n';
        str += sequences.map(util_1.formatSequence).join('');
    }
    return str;
}
exports.formatSync = formatSync;
var FormattingTransform = /** @class */ (function (_super) {
    __extends(FormattingTransform, _super);
    function FormattingTransform(options) {
        if (options === void 0) { options = {}; }
        var _this = _super.call(this, Object.assign(options, { objectMode: true })) || this;
        _this.linesSinceLastSyncMark = 0;
        _this.haveWeEmittedData = false;
        _this.fastaMode = false;
        _this.minLinesBetweenSyncMarks = options.minSyncLines || 100;
        _this.insertVersionDirective = options.insertVersionDirective || false;
        return _this;
    }
    FormattingTransform.prototype._transform = function (chunk, _encoding, callback) {
        // if we have not emitted anything yet, and this first
        // chunk is not a gff-version directive, emit one
        var str;
        if (!this.haveWeEmittedData && this.insertVersionDirective) {
            var thisChunk = Array.isArray(chunk) ? chunk[0] : chunk;
            if ('directive' in thisChunk) {
                if (thisChunk.directive !== 'gff-version') {
                    this.push('##gff-version 3\n');
                }
            }
        }
        // if it's a sequence chunk coming down, emit a FASTA directive and
        // change to FASTA mode
        if ('sequence' in chunk && !this.fastaMode) {
            this.push('##FASTA\n');
            this.fastaMode = true;
        }
        if (Array.isArray(chunk))
            str = chunk.map(util_1.formatItem).join('');
        else
            str = (0, util_1.formatItem)(chunk);
        this.push(str);
        if (this.linesSinceLastSyncMark >= this.minLinesBetweenSyncMarks) {
            this.push('###\n');
            this.linesSinceLastSyncMark = 0;
        }
        else {
            // count the number of newlines in this chunk
            var count = 0;
            for (var i = 0; i < str.length; i += 1) {
                if (str[i] === '\n')
                    count += 1;
            }
            this.linesSinceLastSyncMark += count;
        }
        this.haveWeEmittedData = true;
        _callback(callback);
    };
    return FormattingTransform;
}(stream_1.Transform));
/**
 * Format a stream of features, directives, comments and/or sequences into a
 * stream of GFF3 text.
 *
 * Inserts synchronization (###) marks automatically.
 *
 * @param options - parser options
 */
function formatStream(options) {
    if (options === void 0) { options = {}; }
    return new FormattingTransform(options);
}
exports.formatStream = formatStream;
/**
 * Format a stream of features, directives, comments and/or sequences into a
 * GFF3 file and write it to the filesystem.

 * Inserts synchronization (###) marks and a ##gff-version
 * directive automatically (if one is not already present).
 *
 * @param stream - the stream to write to the file
 * @param filename - the file path to write to
 * @param options - parser options
 * @returns promise for null that resolves when the stream has been written
 */
function formatFile(stream, writeStream, options) {
    if (options === void 0) { options = {}; }
    var newOptions = __assign({ insertVersionDirective: true }, options);
    return new Promise(function (resolve, reject) {
        stream
            .pipe(new FormattingTransform(newOptions))
            .on('end', function () { return resolve(null); })
            .on('error', reject)
            .pipe(writeStream);
    });
}
exports.formatFile = formatFile;
//# sourceMappingURL=api.js.map