"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const debug_1 = __importDefault(require("debug"));
const events_1 = require("events");
const packet_1 = __importDefault(require("./packet"));
const bluebird_1 = __importDefault(require("bluebird"));
const protocol_1 = __importDefault(require("../protocol"));
const debug = (0, debug_1.default)('adb:tcpusb:service');
class PrematurePacketError extends Error {
    constructor(packet) {
        super();
        this.packet = packet;
        Object.setPrototypeOf(this, PrematurePacketError.prototype);
        this.name = 'PrematurePacketError';
        this.message = 'Premature packet';
        Error.captureStackTrace(this, Service.PrematurePacketError);
    }
}
class LateTransportError extends Error {
    constructor() {
        super();
        Object.setPrototypeOf(this, LateTransportError.prototype);
        this.name = 'LateTransportError';
        this.message = 'Late transport';
        Error.captureStackTrace(this, Service.LateTransportError);
    }
}
class Service extends events_1.EventEmitter {
    constructor(client, serial, localId, remoteId, socket) {
        super();
        this.client = client;
        this.serial = serial;
        this.localId = localId;
        this.remoteId = remoteId;
        this.socket = socket;
        this.opened = false;
        this.ended = false;
        this.needAck = false;
    }
    end() {
        if (this.transport) {
            this.transport.end();
        }
        if (this.ended) {
            return this;
        }
        debug('O:A_CLSE');
        const localId = this.opened ? this.localId : 0; // Zero can only mean a failed open
        try {
            // We may or may not have gotten here due to @socket ending, so write
            // may fail.
            this.socket.write(packet_1.default.assemble(packet_1.default.A_CLSE, localId, this.remoteId, null));
        }
        catch (error) { }
        // Let it go
        this.transport = null;
        this.ended = true;
        this.emit('end');
        return this;
    }
    handle(packet) {
        return bluebird_1.default.try(() => {
            switch (packet.command) {
                case packet_1.default.A_OPEN:
                    return this._handleOpenPacket(packet);
                case packet_1.default.A_OKAY:
                    return this._handleOkayPacket(packet);
                case packet_1.default.A_WRTE:
                    return this._handleWritePacket(packet);
                case packet_1.default.A_CLSE:
                    return this._handleClosePacket(packet);
                default:
                    throw new Error(`Unexpected packet ${packet.command}`);
            }
        }).catch((err) => {
            this.emit('error', err);
            return this.end();
        });
    }
    _handleOpenPacket(packet) {
        debug('I:A_OPEN', packet);
        return this.client
            .getDevice(this.serial)
            .transport()
            .then((transport) => {
            this.transport = transport;
            if (this.ended) {
                throw new LateTransportError();
            }
            this.transport.write(protocol_1.default.encodeData(packet.data.slice(0, -1))); // Discard null byte at end
            return this.transport.parser.readAscii(4).then((reply) => {
                switch (reply) {
                    case protocol_1.default.OKAY:
                        debug('O:A_OKAY');
                        this.socket.write(packet_1.default.assemble(packet_1.default.A_OKAY, this.localId, this.remoteId, null));
                        return (this.opened = true);
                    case protocol_1.default.FAIL:
                        return this.transport.parser.readError();
                    default:
                        return this.transport.parser.unexpected(reply, 'OKAY or FAIL');
                }
            });
        })
            .then(() => {
            return new bluebird_1.default((resolve, reject) => {
                this.transport.socket
                    .on('readable', () => this._tryPush())
                    .on('end', resolve)
                    .on('error', reject);
                return this._tryPush();
            });
        })
            .finally(() => {
            return this.end();
        });
    }
    _handleOkayPacket(packet) {
        debug('I:A_OKAY', packet);
        if (this.ended) {
            return;
        }
        if (!this.transport) {
            throw new Service.PrematurePacketError(packet);
        }
        this.needAck = false;
        return this._tryPush();
    }
    _handleWritePacket(packet) {
        debug('I:A_WRTE', packet);
        if (this.ended) {
            return;
        }
        if (!this.transport) {
            throw new Service.PrematurePacketError(packet);
        }
        if (this.transport && packet.data) {
            this.transport.write(packet.data);
        }
        debug('O:A_OKAY');
        return this.socket.write(packet_1.default.assemble(packet_1.default.A_OKAY, this.localId, this.remoteId, null));
    }
    _handleClosePacket(packet) {
        debug('I:A_CLSE', packet);
        if (this.ended) {
            return;
        }
        if (!this.transport) {
            throw new Service.PrematurePacketError(packet);
        }
        return this.end();
    }
    _tryPush() {
        if (this.needAck || this.ended) {
            return;
        }
        const chunk = this._readChunk(this.transport.socket);
        if (chunk) {
            debug('O:A_WRTE');
            this.socket.write(packet_1.default.assemble(packet_1.default.A_WRTE, this.localId, this.remoteId, chunk));
            return (this.needAck = true);
        }
    }
    _readChunk(stream) {
        return (stream.read(this.socket.maxPayload) || stream.read());
    }
}
Service.PrematurePacketError = PrematurePacketError;
Service.LateTransportError = LateTransportError;
exports.default = Service;
//# sourceMappingURL=service.js.map