"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const bluebird_1 = __importDefault(require("bluebird"));
const protocol_1 = __importDefault(require("./protocol"));
class FailError extends Error {
    constructor(message) {
        super(`Failure: '${message}'`);
        Object.setPrototypeOf(this, FailError.prototype);
        this.name = 'FailError';
        Error.captureStackTrace(this, FailError);
    }
}
class PrematureEOFError extends Error {
    constructor(howManyMissing) {
        super(`Premature end of stream, needed ${howManyMissing} more bytes`);
        Object.setPrototypeOf(this, PrematureEOFError.prototype);
        this.name = 'PrematureEOFError';
        this.missingBytes = howManyMissing;
        Error.captureStackTrace(this, PrematureEOFError);
    }
}
class UnexpectedDataError extends Error {
    constructor(unexpected, expected) {
        super(`Unexpected '${unexpected}', was expecting ${expected}`);
        this.unexpected = unexpected;
        this.expected = expected;
        Object.setPrototypeOf(this, UnexpectedDataError.prototype);
        this.name = 'UnexpectedDataError';
        Error.captureStackTrace(this, UnexpectedDataError);
    }
}
class TimeoutError extends Error {
    constructor() {
        super(`Socket timeout reached.`);
        Object.setPrototypeOf(this, TimeoutError.prototype);
        this.name = 'TimeoutError';
        Error.captureStackTrace(this, TimeoutError);
    }
}
bluebird_1.default.config({
    // Enable warnings
    // warnings: true,
    // Enable long stack traces
    // longStackTraces: true,
    // Enable cancellation
    cancellation: true,
    // Enable monitoring
    // monitoring: true,
});
class Parser {
    constructor(stream) {
        this.stream = stream;
        this.ended = false;
        // empty
    }
    end() {
        if (this.ended) {
            return bluebird_1.default.resolve(true);
        }
        let tryRead;
        let errorListener;
        let endListener;
        let timeoutListener;
        return new bluebird_1.default((resolve, reject, onCancel) => {
            tryRead = () => {
                while (this.stream.read()) {
                    // ignore
                }
            };
            errorListener = function (err) {
                return reject(err);
            };
            endListener = () => {
                this.ended = true;
                return resolve(true);
            };
            timeoutListener = () => {
                this.stream.end();
                return reject(new Parser.TimeoutError());
            };
            this.stream.on('readable', tryRead);
            this.stream.on('error', errorListener);
            this.stream.on('end', endListener);
            this.stream.on('timeout', timeoutListener);
            this.stream.read(0);
            this.stream.end();
            onCancel(() => {
                // console.log('1-onCanceled');
            });
        }).finally(() => {
            this.stream.removeListener('readable', tryRead);
            this.stream.removeListener('error', errorListener);
            this.stream.removeListener('end', endListener);
            this.stream.removeListener('timeout', timeoutListener);
            // return r;
        });
    }
    raw() {
        return this.stream;
    }
    readAll() {
        let all = Buffer.alloc(0);
        let tryRead;
        let errorListener;
        let endListener;
        let timeoutListener;
        return new bluebird_1.default((resolve, reject, onCancel) => {
            tryRead = () => {
                let chunk;
                while ((chunk = this.stream.read())) {
                    all = Buffer.concat([all, chunk]);
                }
                if (this.ended) {
                    return resolve(all);
                }
            };
            errorListener = function (err) {
                return reject(err);
            };
            endListener = () => {
                this.ended = true;
                return resolve(all);
            };
            timeoutListener = () => {
                this.stream.end();
                return reject(new Parser.TimeoutError());
            };
            this.stream.on('readable', tryRead);
            this.stream.on('error', errorListener);
            this.stream.on('end', endListener);
            this.stream.on('timeout', timeoutListener);
            tryRead();
            onCancel(() => {
                // console.log('2-onCanceled');
            });
        }).finally(() => {
            this.stream.removeListener('readable', tryRead);
            this.stream.removeListener('error', errorListener);
            this.stream.removeListener('end', endListener);
            this.stream.removeListener('timeout', timeoutListener);
        });
    }
    readAscii(howMany) {
        return this.readBytes(howMany).then((chunk) => chunk.toString('ascii'));
    }
    readBytes(howMany) {
        let tryRead;
        let errorListener;
        let endListener;
        let timeoutListener;
        return new bluebird_1.default((resolve, reject /*, onCancel*/) => {
            tryRead = () => {
                if (howMany) {
                    const chunk = this.stream.read(howMany);
                    if (chunk) {
                        // If the stream ends while still having unread bytes, the read call
                        // will ignore the limit and just return what it's got.
                        howMany -= chunk.length;
                        if (howMany === 0) {
                            return resolve(chunk);
                        }
                    }
                    if (this.ended) {
                        return reject(new Parser.PrematureEOFError(howMany));
                    }
                }
                else {
                    return resolve(Buffer.alloc(0));
                }
            };
            endListener = () => {
                this.ended = true;
                return reject(new Parser.PrematureEOFError(howMany));
            };
            timeoutListener = () => {
                this.stream.end();
                return reject(new Parser.TimeoutError());
            };
            errorListener = (err) => reject(err);
            this.stream.on('readable', tryRead);
            this.stream.on('error', errorListener);
            this.stream.on('end', endListener);
            this.stream.on('timeout', timeoutListener);
            tryRead();
            // onCancel(() => {});
        }).finally(() => {
            this.stream.removeListener('readable', tryRead);
            this.stream.removeListener('error', errorListener);
            this.stream.removeListener('end', endListener);
            this.stream.removeListener('timeout', timeoutListener);
        });
    }
    readByteFlow(howMany, targetStream) {
        let tryRead;
        let errorListener;
        let endListener;
        let timeoutListener;
        return new bluebird_1.default((resolve, reject /*, onCancel*/) => {
            tryRead = () => {
                if (howMany) {
                    // Try to get the exact amount we need first. If unsuccessful, take
                    // whatever is available, which will be less than the needed amount.
                    // avoid chunk is undefined.
                    let chunk;
                    while (chunk = this.stream.read(howMany) || this.stream.read()) {
                        howMany -= chunk.length;
                        targetStream.write(chunk);
                        if (howMany === 0) {
                            return resolve();
                        }
                    }
                    if (this.ended) {
                        return reject(new Parser.PrematureEOFError(howMany));
                    }
                }
                else {
                    return resolve();
                }
            };
            endListener = () => {
                this.ended = true;
                return reject(new Parser.PrematureEOFError(howMany));
            };
            errorListener = function (err) {
                return reject(err);
            };
            timeoutListener = () => {
                this.stream.end();
                return reject(new Parser.TimeoutError());
            };
            this.stream.on('readable', tryRead);
            this.stream.on('error', errorListener);
            this.stream.on('end', endListener);
            this.stream.on('timeout', timeoutListener);
            tryRead();
            // onCancel(() => {});
        }).finally(() => {
            this.stream.removeListener('readable', tryRead);
            this.stream.removeListener('error', errorListener);
            this.stream.removeListener('end', endListener);
            this.stream.removeListener('timeout', timeoutListener);
        });
    }
    readError() {
        return this.readValue().then(function (value) {
            throw new Parser.FailError(value.toString());
        });
    }
    readValue() {
        return this.readAscii(4).then((value) => {
            const length = protocol_1.default.decodeLength(value);
            return this.readBytes(length);
        });
    }
    readUntil(code) {
        let skipped = Buffer.alloc(0);
        const read = () => {
            return this.readBytes(1).then(function (chunk) {
                if (chunk[0] === code) {
                    return skipped;
                }
                else {
                    skipped = Buffer.concat([skipped, chunk]);
                    return read();
                }
            });
        };
        return read();
    }
    searchLine(re) {
        return this.readLine().then((line) => {
            const match = re.exec(line.toString());
            if (match) {
                return match;
            }
            else {
                return this.searchLine(re);
            }
        });
    }
    readLine() {
        return this.readUntil(0x0a).then(function (line) {
            // '\n'
            if (line[line.length - 1] === 0x0d) {
                // '\r'
                return line.slice(0, -1);
            }
            else {
                return line;
            }
        });
    }
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    unexpected(data, expected) {
        return bluebird_1.default.reject(new Parser.UnexpectedDataError(data, expected));
    }
}
Parser.FailError = FailError;
Parser.PrematureEOFError = PrematureEOFError;
Parser.UnexpectedDataError = UnexpectedDataError;
Parser.TimeoutError = TimeoutError;
exports.default = Parser;
//# sourceMappingURL=parser.js.map